Forum Xamarin.Forms

Detect CollectionView row Item is Appeared?

LeoJHarrisLeoJHarris NZMember ✭✭✭
edited December 2019 in Xamarin.Forms

Hi all,

Im trying to figuire out how I can trigger event for row appearing on CollectionView, previously I used ListView.ItemAppearing Event on listview but CollectionView does not have this event unfortunately :neutral: so I need another way of detecting a row has appeared? I was using the Scrolled Event but it didnt quite achieve what I wanted since when the collectionview first displays there is no trigger fired, only when I actually scroll and not when items/rows first appear.

Basically I have a chat listview and when a row/message appears a http request sends to mark the message as seen so I rely on a trigger when row appears (user seen message) and not just scroll event.

Ive gottten this working on android but not on iOS, below on my view (actually an expandableView) i add effect with an event Appeared for view appearing:

<expandable:ExpandableView
                    ExpandAnimationLength="125"
                    Spacing="0">
                    <expandable:ExpandableView.Effects>
                        <effects:ViewLifecycleEffect Appeared="viewLifecycleEffect_AppearedAsync" />
                    </expandable:ExpandableView.Effects>
                    <expandable:ExpandableView.PrimaryView>
...

The effect in the shared project:

public class ViewLifecycleEffect : RoutingEffect
    {
        public const string EffectGroupName = "XFLifecycle";
        public const string EffectName = "LifecycleEffect";

        public event EventHandler<EventArgs>? Appeared;
        public event EventHandler<EventArgs>? Disappeared;


        public ViewLifecycleEffect() : base($"{EffectGroupName}.{EffectName}") { }

        public void RaiseAppeared(Element element)
        {
            Appeared?.Invoke(element, EventArgs.Empty);
        }

        public void RaiseDisappeared(Element element) => Disappeared?.Invoke(element, EventArgs.Empty);
    }

So finally in the iOS project I try to get notified when view is appearing/showing on screen/window but doesnt get called as views appear which should trigger per row as user scrolling:

public class IosLifecycleEffect : PlatformEffect
    {
        private const NSKeyValueObservingOptions _observingOptions = NSKeyValueObservingOptions.Initial | NSKeyValueObservingOptions.OldNew | NSKeyValueObservingOptions.Prior;

        private ViewLifecycleEffect _viewLifecycleEffect;
        private IDisposable _isLoadedObserverDisposable;

        protected override void OnAttached()
        {
            _viewLifecycleEffect = Element.Effects.OfType<ViewLifecycleEffect>().FirstOrDefault();

            UIView nativeView = Control ?? Container;

            _isLoadedObserverDisposable = nativeView?.AddObserver("superview", _observingOptions, isViewLoadedObserver);

            CGRect viewBounds = nativeView.Bounds;
            CGRect windowBounds = UIScreen.MainScreen.Bounds;

    // Dont think I need this, but only checks if window on screen.
            if (windowBounds.Contains(nativeView.ConvertRectToView(viewBounds, nativeView.Window)))
            {
                _viewLifecycleEffect?.RaiseAppeared(Element);
            }
        }

        protected override void OnDetached()
        {
            _viewLifecycleEffect.RaiseDisappeared(Element);
            _isLoadedObserverDisposable.Dispose();
        }

        private void isViewLoadedObserver(NSObservedChange nsObservedChange)
        {
            _viewLifecycleEffect?.RaiseAppeared(Element);
        }
    }

Maybe there is a better way then detecting on the View or perhaps on the CollectionView itself?

Best Answers

  • LeoJHarrisLeoJHarris NZMember ✭✭✭
    edited December 2019 Accepted Answer

    Hi @LandLu thanks works like a charm on iOS.

    For android I got this working with the below renderer:

    [assembly: ExportRenderer(typeof(EnhancedCollectionView), typeof(EnhancedCollectionViewRenderer))]
    namespace App.Droid
    {
        public class EnhancedCollectionViewRenderer : CollectionViewRenderer
        {
            private readonly Context _context;
    
            public EnhancedCollectionViewRenderer(Context context) : base(context)
            {
                _context = context;
            }
    
            private EnhancedCollectionView _enhancedCollectionView;
    
            protected override void OnElementChanged(ElementChangedEventArgs<ItemsView> elementChangedEvent)
            {
                base.OnElementChanged(elementChangedEvent);
    
                if (elementChangedEvent.NewElement is EnhancedCollectionView enhancedCollectionView)
                {
                    _enhancedCollectionView = enhancedCollectionView;
                ChildViewAdded += EnhancedCollectionViewRenderer_ChildViewAdded;
                }
            }
    
            private void EnhancedCollectionViewRenderer_ChildViewAdded(object sender, ChildViewAddedEventArgs e)
            {
               _enhancedCollectionView.RaiseAppeared(((RecyclerView)e.Parent).GetChildViewHolder(e.Child).AdapterPosition);
            }
        }
    }
    

    EnhancedCollectionViewRenderer_ChildViewAdded does get called even if the CollectionView is empty on first load but does also get called as rows become visible.

Answers

  • LeoJHarrisLeoJHarris NZMember ✭✭✭
    edited December 2019 Accepted Answer

    Hi @LandLu thanks works like a charm on iOS.

    For android I got this working with the below renderer:

    [assembly: ExportRenderer(typeof(EnhancedCollectionView), typeof(EnhancedCollectionViewRenderer))]
    namespace App.Droid
    {
        public class EnhancedCollectionViewRenderer : CollectionViewRenderer
        {
            private readonly Context _context;
    
            public EnhancedCollectionViewRenderer(Context context) : base(context)
            {
                _context = context;
            }
    
            private EnhancedCollectionView _enhancedCollectionView;
    
            protected override void OnElementChanged(ElementChangedEventArgs<ItemsView> elementChangedEvent)
            {
                base.OnElementChanged(elementChangedEvent);
    
                if (elementChangedEvent.NewElement is EnhancedCollectionView enhancedCollectionView)
                {
                    _enhancedCollectionView = enhancedCollectionView;
                ChildViewAdded += EnhancedCollectionViewRenderer_ChildViewAdded;
                }
            }
    
            private void EnhancedCollectionViewRenderer_ChildViewAdded(object sender, ChildViewAddedEventArgs e)
            {
               _enhancedCollectionView.RaiseAppeared(((RecyclerView)e.Parent).GetChildViewHolder(e.Child).AdapterPosition);
            }
        }
    }
    

    EnhancedCollectionViewRenderer_ChildViewAdded does get called even if the CollectionView is empty on first load but does also get called as rows become visible.

Sign In or Register to comment.