Forum Xamarin.iOS

Subscribing to and unsubscribing to button event (TouchUpInside) in the cells of a UICollectionView?

CharlieFinlaysonCharlieFinlayson USUniversity ✭✭
edited July 2016 in Xamarin.iOS

I'm using reusable cells in a UICollectionView. As such I need to subscribe and unsubscribe eventhandlers for buttons in each cell to avoid multiple event fires. As I understand it, I could easily pass parameters to the handler with a Lambda Expression, but that would mean no unsubscribing.

So I've created a class for CustomEventArgs, so that I can send some cell specific information to my handler method (I haven't seen this done any where before):

internal class CustomEventArgs : EventArgs
{
    public DetailCell Cell {get; private set; }

    public int Position { get; private set; }

    public CustomEventArgs(DetailCell cell, int position)
    {
        this.Cell = cell;
        this.Position = position;
    }
}

I try to subscribe to this once the cell is created in the following override:

public override void WillDisplayCell(UICollectionView collectionView, UICollectionViewCell cell, NSIndexPath indexPath) { var cell = cell as DetailCell; if (cell != null) { var eventArgs = new CustomEventArgs(cell, (int)indexPath.Item); cell.EditButton.TouchUpInside += OnEditClicked(this, eventArgs); } }

But I'm getting a compile error saying that the void OnEditClicked method can't be converted to System.EventHandler. I try to set the return type of OnEditClicked to EventHandler but then I'm not exactly sure that this is correct, and if it is, I'm not sure what I'd return.

I'm pretty sure there is a gap in my knowledge, and that there is an easy fix here. Is it possible to subscribe to TouchUpInside the way that I'm doing, and if so, what am I missing to do so?

Answers

  • JF.0444JF.0444 USMember ✭✭✭

    For your above example you can't have cell.EditButton.TouchUpInside += OnEditClicked(this, eventArgs);

    it needs to be cell.EditButton.TouchUpInside += OnEditClicked;

    The main issue is that OnEditClicked is called by the UIButton so you can't supply your own arguments.

    In the past i've used the GetCell() or WillDisplayCell() methods to subscribe to events like normal and then override the PrepareForReuse() in the UICollectionViewCell class and remove the event to avoid multiple events being subscribed. (this requires you to subclass the UICollectionViewCell class)

    And if you absolutely need to track position and a cell reference you can use something like

    internal class CustomEventArgs : EventArgs
    {
        public DetailCell Cell { get; private set; }
    
        public int Position { get; private set; }
    
        public CustomEventArgs (DetailCell cell, int position)
        {
            this.Cell = cell;
            this.Position = position;
        }
    }
    
    public class DetailCell : UICollectionViewCell
    {
        CustomEventHandler HandlerForButton;
        int CellPosition;
    
        public override void PrepareForReuse ()
        {
            base.PrepareForReuse ();
    
            if (HandlerForButton != null)
            {
                EditButton.TouchUpInside -= ButtonPressed;
            }
            CellPosition = -1;
            HandlerForButton = null;
        }
    
        private void ButtonPressed (object sender, EventArgs args)
        {
            var eventArgs = new CustomEventArgs (this, CellPosition);
    
            if (HandlerForButton != null)
            {
                HandlerForButton (this, eventArgs);
            }
        }
    
        internal void GetCell (int position, CustomEventHandler handler)
        {
            HandlerForButton = handler;
            CellPosition = position;
            EditButton.TouchUpInside += ButtonPressed;
        }
    }
    
    public class CollectionViewSource : UICollectionViewSource
    {
        public override UICollectionViewCell GetCell (UICollectionView collectionView, NSIndexPath indexPath)
        {
            var cell = collectionView.DequeueReusableCell ("DetailCell", null) as DetailCell;
            cell.GetCell (indexPath.Row, ClickHandler);
            return cell;
        }
    
        private void ClickHandler (UICollectionViewCell sender, CustomEventArgs args)
        {
            var cell = sender as UICollectionViewCell;
    
            if (cell != null)
            {
                // do stuff
            }
    
            var yourArgs = args as CustomEventArgs;
    
            if (yourArgs != null)
            {
                // do stuff
            }
        }
    }
    
    internal delegate void CustomEventHandler (DetailCell sender, CustomEventArgs args);
    

    The key difference here is that I am explicitly calling the event handler with the custom args instead of letting the button supply the arguments

Sign In or Register to comment.