Search code examples
objective-cuitableviewreactive-cocoa

ReactiveCocoa connection to UICollectionViewCell subview


I have a UICollectionView which loads my custom UICollectionViewCell's that have a UITextField in them and a UILabel.

In my cellForItemAtIndexPath I am doing something like this:

        -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
    {

        BBCollectionViewTextFieldCell *textFieldCell = [collectionView dequeueReusableCellWithReuseIdentifier:[BBCollectionViewTextFieldCell reuseIdentifier] forIndexPath:indexPath];
        NSString *label = @"";

                switch (indexPath.item){
                    case 0:
                        label = @"Label One";
                        self.firstTextField = textFieldCell.textField;
                        textFieldCell.textField.text = self.viewModel.labelOneData;
                        break;
                    case 1:
                        label = @"Label Two";
                        self.secondTextField = textFieldCell.textField;
                        textFieldCell.textField.text = self.viewModel.labelTwoData;
                        break;
                    case 2:
                        label = @"Label Three";
                        self.thirdTextField = textFieldCell.textField;
                        textFieldCell.textField.text = self.viewModel.labelThreeData;
                        break;
                     }

                   textFieldCell.label.text = label;
                    textFieldCell.textField.delegate = self;
    return textFieldCell; 
    }

I then use the normal UITextFieldDelegate methods to handle text input doing something like this:

-(void)textFieldDidEndEditing:(UITextField *)textField{

if (textField == self.firstTextField){
   //Do something with it
}
//And so on for the rest... 
}

So far so good and all works...

So what's the issue?

The issue is if I reload the UICollectionView the following happens:

  • self.firstTextField will have data in it that belongs to self.thirdTextField

Or any random combination thereof. The UILabel's are all correct - however it seems the actual UItextFields have gotten mixed up. the first UICollectionViewCell's UitextField will actually have data from another cell's textField.

At first I figured it was a reuse issue - however, since my cells never scroll off screen and the data is pretty static, (There were always X amount of cells, no less or more ) - so it can't be a reuse issue.

I reworked the code so that I have no UitextField Properties in my UIViewController where this code sits - and rely on indexPath to get the textField. Like this in cellForItemAtIndexPath

switch (indexPath.item){
                case 0:
                    label = @"Label One";
                    textFieldCell.textField.text = self.viewModel.labeloneData;
                    break;

and:

    -(void)textFieldDidEndEditing:(UITextField *)textField{

NSIndexPath *indexPath = [self.collectionView indexPathForCellContaininView:textField];

    if(indexPath.item == 0){
       //Do something with it
    }
    //And so on for the rest... 
    }

This solves the problem however it is not what I want to do. I need the UItextField properties in my UIViewController

I declared the UitextField Properties in the implementation like so:

@property (strong, nonatomic)  UITextField *firsttextField;

I also posted a very similar issue but using UITableView instead and it was concluded its related to cell Reuse - however I don't believe it is anymore (Again, setup in that question's code is almost identical to this question - same problem though)

UITextField in UITableViewCell - reuse issue

Where the issue is I think

I don't think it is to do with the the way UITableView or UiCollectionView is reusing the cells. I think the issue is somewhere in the viewController's code and the instances of my UItextField properties.. I may be off base here though.

I know I listed a possible workaround - however I would like to get to the bottom of this issue and find how why it's happening.


Solution

  • Keeping pointers to subviews of UITableView/UICollectionView cells is fraught because the cells are reused when scrolling -- and potentially scrambled on reload. Upon reuse, the subview which you thought was at one index path is now at another.

    The most common solution for this is outlets in a custom cell. Others keep a tag up-to-date every time a cell is configured. Others favor a quick search up the view hierarchy (say, upon a button tap) to work out which index path a subview corresponds to.

    But since ReactiveCocoa is all about model-view connection it wants a pair of pointers for the duo. Fortunately it also provides and end condition that corresponds to cell reuse. This gist demonstrates how to build reactive connection for table view cell's subview. Copied here ...

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {    
        UITableViewCell *cell =
            [tableView dequeueReusableCellWithIdentifier:REUSABLE_CELL_ID];
        UILabel *label = (UILabel *)[cell viewWithTag:VIEW_TAG];
        Model *someModel = [self getModelFromIndexPath:indexPath];
        //  `takeUntil:` makes the RACObserve() signal complete (and thus breaks the subscription)
        //  when the cell is recycled.
        RAC(label, text) = [RACObserve(someModel, someKey)
            takeUntil:cell.rac_prepareForReuseSignal];
        return cell;
    }