Search code examples
iosobjective-cdelegatesuitextfieldrespondstoselector

How to pass string from a text field (embedded in a table cell) in 2nd controller back to a label in 1st controller (also embedded in a table cell)?


Edit 1: I think I have figured out the problem, I just haven't been able to solve it. In SecondViewController.m where textFieldShouldReturn is defined, "if ([self.delegate respondsToSelector:@selector(passString:)])" returns false, since NSLog was not triggered.

Edit 2: Checking NSLogs of who is the delegate in both ViewControllers gave good insight to the problem. In the FirstViewController, prepareForSegue assigns the correct delegate. After the segue to SecondViewController, delegate becomes "null".

This is something I have been trying to achieve for a week without success. I do understand that I need to pass the data back up the two-layer navigation stack using delegates. However, I'm not sure why my code does not work (string does not get passed).

It would be also much appreciated if anyone can tell me (links and other resources) how to properly make a Form/Submission View (2nd Controller) for data entry that can be reused for to edit specific cell's labels in the Display View (1st Controller).

Here is my code:

SecondViewController.h

#import <UIKit/UIKit.h>

@protocol PassStringDelegate
- (void)passString:(NSString *)stringFromTextField;
@end

@interface SecondViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate>

@property (strong, nonatomic) IBOutlet UITableView *detailTable;

@property (weak, nonatomic) id <PassStringDelegate> delegate;

@end

SecondViewController.m

#import "SecondViewController.h"
#import "CustomCell.h" // My cell is custom built in IB.

@interface SecondViewController ()

@end

@implementation SecondViewController

@synthesize delegate;

@synthesize stringFromTextField;

- (void)viewDidLoad
{
    ...            
    NSLog(@"%@", self.delegate);                          // Returns "(null)".
}    

...

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Data Entry" forIndexPath:indexPath];
    [cell setEditing:YES animated:YES];
    cell.customTextField.clearsOnBeginEditing = NO;
    cell.customTextField.returnKeyType = UIReturnKeyDone;
    cell.customTextField.delegate = self;                 // customTextField is a custom textfield embedded in the custom cell.
    [self textFieldShouldReturn:cell.customTextField];    // This function supposedly will trigger passString:stringFromTextField when the cell is done with editing.

    return cell;
}

...

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    [textField resignFirstResponder];
    if ([self.delegate respondsToSelector:@selector(passString:)]) {
        [self.delegate passString:textField.text];
        NSLog(@"Sent back: %@", textField.text);          // I checked NSLog, this was never called.
    }
    return YES;
}

FirstViewController.h

#import <UIKit/UIKit.h>
#import "SecondViewController.h"

@interface FirstViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate, PassStringDelegate>

@property (strong, nonatomic) IBOutlet UITableView *table;

@property (copy, nonatomic) NSArray *myList;

@property (weak, nonatomic) NSString *obtainedString;

@end

FirstViewController.m

#import "FirstViewController.h"
#import "CustomCell.h"

@interface FirstViewController ()

@end

@implementation FirstViewController

@synthesize obtainedString;

...

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [self passString:obtainedString];
}

...

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Display" forIndexPath:indexPath];
    cell.customLabel.text = obtainedString;             // I want the label to display the text in the text field in the second controller.
    cell.detailCustomLabel.text = self.myList[indexPath.row];

    return cell;
}

...

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"segue"])
     {
        SecondViewController *detailedView = [[SecondViewController alloc] init];
        detailedView.delegate = self;
        NSLog(@"%@", detailedView.delegate);            // Returns "<SecondViewController: 0x10967b590>".
     }
}

...

- (void)passString:(NSString *)stringFromTextField
{
    obtainedString = stringFromTextField;
}

Please check my delegate path and calls to see if what I did was close to appropriate. Thank you very much!


Solution

  • Try this:

    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
        if ([segue.identifier isEqualToString:@"segue"]) {
            //SecondViewController *detailedView = [[SecondViewController alloc] init];
            //this creates a new object of SecondViewController that has nothing to do
            //with the segue
    
            //this should fix it:
            SecondViewController *detailedView = [segue destinationViewController];
            detailedView.delegate = self;
        }
    }
    

    You were creating a new object of SecondViewController and setting a delegate on it.
    Sadly, this object was not being used at all since the segue has already created an object and pushing that object instead of the one you instantiate here.