4

I am new to iOS world and have run into an issue while trying to pass a value from a TableView back to the home controller.

The scenario that I am working on is

  1. Home Controller has a button
  2. Click of button opens a list of items in the second UIViewController
  3. User selects an item from list
  4. Selected item is added to another list on Home Controller

Really appreciate any pointers for this issue:

This is how I am preparing for Segue on Home

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    UIViewController *destination = segue.destinationViewController;
    if ([destination respondsToSelector:@selector(setDelegate:)]) { 
        [destination setValue:self forKey:@"delegate"];
    }

}

SecondController has a delegate id, so I am assuming that delegate is set as "respondsToSelector" returns true for "setDelegate"

Now, in SecondController when user selects an item I call didSelectRowAtIndexPath & viewWillDisappear methods to set the item and make the view disappear:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    POSAllItemsCell *cell = (POSAllItemsCell *) [tableView cellForRowAtIndexPath:indexPath];

    item = [NSDictionary dictionaryWithObjectsAndKeys:cell.name, @"Name", cell.price, @"Price", cell.code, @"Code", nil];

    [self dismissViewControllerAnimated:YES completion:NULL];

}

- (void)viewWillDisappear:(BOOL)animated { 
    [super viewWillDisappear:animated]; 

    if ([delegate respondsToSelector:@selector(setSelection:)]) {
        [delegate setValue:item forKey:@"selection"];
    }
}

Now the problem here is that respondsToSelector for setSelection returns false even though I do have setSelection method in my HomeController:

- (void) setSelection:(NSDictionary *)dict {
    if (![dict isEqual:selection]) {
        ......
    }
}

Apologies in advance if my question in not clear or is not well formatted. BTW this is for iOS 5 with Xcode 4.2

iwasrobbed
  • 46,496
  • 21
  • 150
  • 195
Vivek
  • 957
  • 4
  • 12
  • 18
  • Show how you've declared the delegate in the "home" view controller. – omz Apr 26 '13 at 21:59
  • Well, I might be missing something here, but I assumed that delegate "Auto" created for me when application started. This is how I have declared it in "second" controller : @property (weak, nonatomic) id delegate; – Vivek Apr 26 '13 at 22:04
  • If you log delegate, is it non-nil? – rdelmar Apr 26 '13 at 22:05
  • Why are you using key-value coding (`setValue:forKey:`) here? – newacct Apr 27 '13 at 03:09

2 Answers2

11

For delegation to work, you need to set it up like this:

In your FirstViewController.h header file, make sure you are declaring that the second view controller is conforming to the delegating view controller's protocol:

@interface FirstViewController : UIViewController <SecondViewControllerDelegate>

You can see that the delegate goes within the < > symbols in the header file of a view controller. If there are required delegate methods within that protocol, Xcode will show warnings if you don't define them in the implementation file.

Then you have your delegate method defined in the FirstViewController.m file:

- (void)setSelection:(NSDictionary *)dict {
    if (![dict isEqual:selection]) {
        ......
    }
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:@"someSequeIdentifierHere"]) {
        SecondViewController *sv = segue.destinationViewController;
        sv.delegate = self;
    }
}

You'll notice that instead of using UIViewController in the prepareForSegue method, you should just cast it to the actual view controller so you can set the properties on that. This way, you don't have to test if the view controller responds or not since it either has a delegate property defined or it doesn't (in which case, you need to add one).

To setup your original delegate protocol, you typically follow this format in your SecondViewController.h file:

@protocol SecondViewControllerDelegate <NSObject>
- (void)setSelection:(NSDictionary *)dict;
@optional
- (void)someOptionalDelegateMethodHere;
@end

@interface SecondViewController : UIViewController

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

@end

Delegates should almost always be weakly defined for ARC.

Then when you want to notify the delegate in SecondViewController.m

- (void)viewWillDisappear:(BOOL)animated { 
    [super viewWillDisappear:animated]; 

    if ([self.delegate respondsToSelector:@selector(setSelection:)]) {
        [self.delegate setSelection:item];
    }
}

Since delegate is defined as a public property in the .h file, you can reference it either as self.delegate or _delegate but referencing it as delegate makes me think you incorrectly defined it as a private instance variable instead.

About the only time this type of a pattern won't respond to respondsToSelector: is when you don't correctly assign that delegate to your FirstViewController

Hope this helps!

iwasrobbed
  • 46,496
  • 21
  • 150
  • 195
  • 1
    "you can reference it either as self.delegate or _delegate but referencing it as delegate makes me think you incorrectly defined it as a private instance variable instead." You're assuming he didn't explicitly `@synthesize` it. But if he did `@synthesize delegate;`, then he could indeed refer to the instance variable as `delegate`. – newacct Apr 27 '13 at 03:11
  • @iWasRobbed - It isn't necessary to check if the delegate responds to selector `setSelection:` because it has to since it implements the SecondViewControllerDelegate protocol and that method is required. – vacawama Apr 27 '13 at 04:20
  • @vacawama Xcode will warn you about not implementing a required delegate method, but that's it. You can still compile and run the program and it will fail when it tries to call that selector. So it "isn't necessary" unless you want to be sure to not crash the app. – iwasrobbed Apr 27 '13 at 13:59
  • @iWasRobbed - Building with warnings is a BAD idea. They're there for a reason. I'm not sure putting in extra code to save me from not paying attention to them is worth it. YMMV – vacawama Apr 27 '13 at 14:19
  • @vacawama Perhaps you should stop preaching to the choir on everyone else's answers... If you have a better answer to provide to a question, please do. – iwasrobbed Apr 27 '13 at 16:22
  • @iWasRobbed - Your answer is great. I was just wanting to tweak it slightly. I'm sorry if I offended you. I thought of editing your answer, but considered that to be highly presumptuous and rude, so I just added a comment. I'm sure our discussion is educational to others, which is the whole point of StackOverflow. – vacawama Apr 27 '13 at 16:38
0

Try this:

if ([destination respondsToSelector:@selector(setDelegate:)]) { 
    [destination performSelector:@selector(setDelegate:) withObject:self];
}
TotoroTotoro
  • 17,524
  • 4
  • 45
  • 76
  • or just `if ([destination respondsToSelector:@selector(setDelegate:)]) { [destination setDelegate:self]; }` – newacct Apr 27 '13 at 03:07
  • @newacct - That doesn't work because the compiler doesn't know that the destination declares the selector setDelegate and gives the error `No visible @interface for 'UIViewController' declares the selector 'setDelegate:'` – vacawama Apr 27 '13 at 03:57
  • @newacct - Your suggestion does work though if he declares destination to be of type `id` instead of `UIViewController *`. – vacawama Apr 27 '13 at 04:09