How to get a UITableViewCell from one of its subviews

I have a UITableView with a UITextField in each of the UITableViewCells. I have a method in my ViewController which handles the “Did End On Exit” event for the text field of each cell and what I want to be able to do is update my model data with the new text.

What I currently have is:

  • Array element checking in swift
  • Get text range of link in UITextView
  • Duplicate dylib warning in xcode
  • ARKit Session Paused and Not Resuming
  • Handling In-Call Status Bar with Custom Modal Presentation
  • Simple Gif like animation with Spritekit
  • - (IBAction)itemFinishedEditing:(id)sender {
        [sender resignFirstResponder];
    
        UITextField *field = sender;
        UITableViewCell *cell = (UITableViewCell *) field.superview.superview.superview;
    
        NSIndexPath *indexPath = [_tableView indexPathForCell:cell];
    
        _list.items[indexPath.row] = field.text;
    }
    

    Of course doing field.superview.superview.superview works but it just seems so hacky. Is there a more elegant way? If I set the tag of the UITextField to the indexPath.row of the cell its in in cellForRowAtIndexPath will that tag always be correct even after inserting and deleting rows?

    For those paying close attention you might think that I have one .superview too many in there, and for iOS6, you’d be right. However, in iOS7 there’s an extra view (NDA prevents me form elaborating) in the hierarchy between the cell’s content view and the cell itself. This precisely illustrates why doing the superview thing is a bit hacky, as it depends on knowing how UITableViewCell is implemented, and can break with updates to the OS.

    4 Solutions Collect From Internet About “How to get a UITableViewCell from one of its subviews”

    Since your goal is really to get the index path for the text field, you could do this:

    - (IBAction)itemFinishedEditing:(UITextField *)field {
        [field resignFirstResponder];
    
        CGPoint pointInTable = [field convertPoint:field.bounds.origin toView:_tableView];    
    
        NSIndexPath *indexPath = [_tableView indexPathForRowAtPoint:pointInTable];
    
        _list.items[indexPath.row] = field.text;
    }
    

    One slightly better way of doing it is to iterate up through the view hierarchy, checking for each superview if it’s an UITableViewCell using the class method. That way you are not constrained by the number of superviews between your UITextField and the cell.

    Something along the lines of:

    UIView *view = field;
    while (view && ![view isKindOfClass:[UITableViewCell class]]){ 
        view = view.superview;
    }
    

    You can attach the UITableViewCell itself as a weak association to the UITextField, then pluck it out in the UITextFieldDelegate method.

    const char kTableViewCellAssociatedObjectKey;
    

    In your UITableViewCell subclass:

    - (void)awakeFromNib {
        [super awakeFromNib];
        objc_setAssociatedObject(textField, &kTableViewCellAssociatedObjectKey, OBJC_ASSOCIATION_ASSIGN);
    }
    

    In your UITextFieldDelegate method:

    UITableViewCell *cell = objc_getAssociatedObject(textField, &kTableViewCellAssociatedObjectKey);
    NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
    //...
    

    I’d also recommend re-associating every time a cell is dequeued from the UITableView to ensure that the text field is associated with the correct cell.

    Basically in this case, I would prefer you to put the IBAction method into cell instead of view controller. And then when an action is triggered, a cell send a delegate to a view controller instance.

    Here is an example:

    @protocol MyCellDelegate;
    
    
    @interface MyCell : UITableViewCell
    
    @property (nonatomic, weak) id<MyCellDelegate> delegate;
    
    @end
    
    @protocol MyCellDelegate <NSObject>
    
    - (void)tableViewCell:(MyCell *)cell textFieldDidFinishEditingWithText:(NSString *)text;
    
    @end
    

    In a implementation of a cell:

    - (IBAction)itemFinishedEditing:(UITextField *)sender
    {
        // You may check respondToSelector first
        [self.delegate tableViewCell:self textFieldDidFinishEditingWithText:sender.text];
    }
    

    So now a cell will pass itself and the text via the delegate method.

    Suppose a view controller has set the delegate of a cell to self. Now a view controller will implement a delegate method.

    In the implementation of your view controller:

    - (void)tableViewCell:(MyCell *)cell textFieldDidFinishEditingWithText:(NSString *)text
    {
        NSIndexPath *indexPath = [_tableView indexPathForCell:cell];
        _list.items[indexPath.row] = text;
    }
    

    This approach will also work no matter how Apple will change a view hierarchy of a table view cell.