Search code examples
iosuilabeliboutlet

IBOutlet UILabel not updating after delegate call


Screencast: https://www.youtube.com/watch?v=ZwehDwITEyI

This is really bizarre. The problem is to do with a label outlet sitting in a custom-designed table cell. That cell is of my CustomCell class. (actually called RA_FormCell if you watch the screencast).

CustomCell.h

@property (weak, nonatomic) IBOutlet UILabel *dayOutlet;
-(void)controller:(id<CustomCellDelegate>)controller didUpdateDay:(NSString *)theDay;

CustomCell.m

// Method is called by a view controller
// (which is itself a delegate of the CustomCell class,
// hence the identifier you see below)

-(void)controller:(id<CustomCellDelegate>)controller didUpdateDay:(NSString *)theDay;
{
    NSLog(@"Method called") // confirms to me that method is called
    self.dayOutlet.text = @"Goodmorning";
    NSLog(@"%@", self.dayOutlet.text); // displays (null)
}

That final log does actually appear, so the method is definitely being called. I have discounted the following:

  1. self.dayOutlet.text is not written to elsewhere by any other method in the project
  2. dayOutlet is connected to the label in the storyboard (and the label is not connected to anything else)
  3. The label is not hidden underneath some accidental static label on the storyboard
  4. The cell attributes on the storyboard include its class as CustomCell
  5. No warnings or alerts in Xcode (I have been careful to avoid any circular imports)

Solution

  • The problem was that the controller:didUpdateDay: message was not sent to the correct instance of the cell class.

    This occurred because I had not correctly assigned this cell to be the delegate for the view controller. For anyone interested, in my screencast at 3:50, you can see that I have the following in cellForRowAtIndexPath:

    RA_FormCell *cell = [tableView dequeueReusableCellWithIdentifier:self.formCellArray[indexPath.row] forIndexPath:indexPath];
    self.delegate = cell
    

    However, this means that self.delegate got continually overwritten as the table cells were generated. As a result, my controller:didUpdateDay message was sent to the bottom cell of the table, and not the top one as I required.

    The solution was simple - there's no need to have this second delegate at all. Instead, when the cell delegates to the view controller, it should pass self into the message it delegates:

    id<CustomCellDelegate> strongDelegate = self.delegate;
    
    if ([strongDelegate respondsToSelector:@selector(customCell:didChangeDay1:)])
        [strongDelegate customCell:self didChangeDay1:[sender value]];
    

    Then, in the implementation of this method by the delegate, simply end it by changing the outlet directly:

    -(void)customCell:(RA_FormCell *)customCell didChangeDay1:(double)value
        // put logic here
        customCell.dayOutlet.text = @"No problem!";
    

    In general, there should rarely be a need for a two-way delegate structure. Keep it one way, from A to B, and just remember to have A pass self in any messages it sends to B. That way, B will know the object the message came from, and be able to communicate back to A.

    Thanks to Paulw11