views:

559

answers:

3

Hi,

I am creating a view controller that will be used to enter text information.

The view itself consists of a label, text field and two buttons in the navigation bar: "cancel" and "OK".

When user presses "cancel" I am just popping back to the root view controller.

But when he presses OK, I want first to call a function from the root view controller and only after that to pop back.

I tried to implement it in the following way:

The header:

@interface UserInputViewController : UIViewController {
    UILabel *textLabel;

    UITextField *textField;

    SEL OKButtonAction;
}

-(NSString*) getEnteredText;

-(UserInputViewController*) initWithTitle: (NSString*)title Text: (NSString*)text andOKButtonSelector: (SEL) OKButtonSelector;

@end

Implementation:

@implementation UserInputViewController

-(UserInputViewController*) initWithTitle: (NSString*)title Text: (NSString*)text andOKButtonSelector: (SEL) OKButtonSelector
{
    self = [self init];
    self.title = title;

    OKButtonAction = OKButtonSelector;

    textLabel = [ [UILabel alloc] initWithFrame: CGRectMake(20, 20, 280, 50)];
    [textLabel setText: text];
    [ [self view] addSubview: textLabel];

    textField = [ [UITextField alloc] initWithFrame: CGRectMake(20, 100, 280, 50)];
    [ [self view] addSubview: textField];

    return self;
}

-(NSString*) getEnteredText
{
    return [textField text];
}

-(void) popToRootViewController
{
    [ [self navigationController] popToRootViewControllerAnimated: YES ];
}

-(void) popToRootWithOKAction
{
    [ [self navigationController] popToRootViewControllerAnimated: YES ];
    [self performSelector: OKButtonAction];
}

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

    //Cancel button
    UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithTitle: NSLocalizedString(@"cancel button", @"") style: UIBarButtonSystemItemCancel target: self action: @selector(popToRootViewController) ];


    [ [self navigationItem] setLeftBarButtonItem: cancelButton animated: NO];
    [cancelButton release];

    //OK button
    UIBarButtonItem *OKButton = [[UIBarButtonItem alloc] initWithTitle: NSLocalizedString(@"ok button", @"") style: UIBarButtonSystemItemSave target: self action: @selector(popToRootWithOKAction) ];

    [ [self navigationItem] setRightBarButtonItem: OKButton animated: NO];
    [OKButton release];

}

And here is the root view controller methods:

-(void) OKButtonAction
{
    NSLog(@"text: %@", [newProfileDialog getEnteredText]);
    [newProfileDialog release];
}

-(void) add_clicked {
    newProfileDialog = [ [UserInputViewController alloc] initWithTitle: @"User name" Text: @"Please enter a new user name:" andOKButtonSelector: @selector(OKButtonAction)];

    [ [self navigationController] pushViewController: newProfileDialog animated: YES];
}

But when I compile it and press the OK button from the pushed view, I get an exception.

I am not yet familiar with selectors programming, so I've got hard time trying to figure out what am I doing wrong.

How can I achieve this goal?

Thanks.

+1  A: 

[self performAction:OKButtonAction] will call the OKBUttonAction selector on your object. Most likely you want to call it on a delegate. While this will work:

[[self.navigationController rootViewController] performSelector:OKButtonAction];

It isn't the way most frameworks accomplish it. The two approaches that I would recommend are:

  • Add a "id target;" instance variable, and pass this in your init method. Then you can use [delegate performSelector:OKButtonAction]. This decouples the view controller from the root view controller, allowing you to use this view controller in other circumstances as well.
  • Instead of a "target", I would also consider using a delegate and a Protocol instead of using a selector. This gives you strongly-worded methods, such as those found in other delegates like UITableViewDelegate.
NilObject
+1  A: 

What you're trying to do is called "delegation" — the action of the OK button is delegated to another object, in this case the root controller. Try changing your view controller's initializer to this:

-(UserInputViewController*) initWithTitle:(NSString*)title Text:(NSString*)text delegate:(id)delegate;

Store the delegate in an instance variable (no need to retain it) and in popToRootWithOKAction call:

if([delegate respondsToSelector:@selector(OKButtonAction)])
    [delegate performSelector:@selector(OKButtonAction)];

Delegate methods don't even need to be declared in your root controller's .h file. In UserInputViewController's header file, just add a second @interface:

@interface NSObject (UserInputViewControllerDelegate)
    -(void)OKButtonAction;
@end
Sidnicious
+1  A: 

I would recommend reading the section on selectors in the Objective-C Programming Language document on Apple's site.

Unless you're doing heavy introspection, there's little reason to actually declare a selector as a variable. Whenever one is required, use the @selector() directive to create it instead.

Martin Gordon