Question

I have a panel xib which is owned by class FooController. FooController also has outlets to the panel’s Cancel and Proceed buttons.

I later decided to recycle this panel for use by the application delegate’s termination routine. When I assigned selectors to the buttons, I found that the Proceed/Save button and a programmatically added DontSave button could accept as selectors methods that were defined in the application delegate class. But the Cancel button would cause “unrecognized selector” errors unless its selector was defined in the owner class, FooController.

Fine, that seemed logical. To be consistent, I set up the Proceed/Save and DontSave selectors in the FooController class too. But then they would generate “unrecognized selector” errors.

So the Cancel button requires its selector to be in the FooController class. The Proceed/Save and DontSave buttons require their selectors to be in the appDelegate class. But all three buttons are explicitly owned by FooController; as you can see in the code below, even the added DontSave button is explicitly assigned to the FooController-owned panel’s contentView:

- (void) adviseOfPendingChangesBeforeQuit {

// Open the panel.
[NSBundle loadNibNamed:@"panelConfirmation" owner:self.fooController];

// Add an extra "Don't Save" button.
NSButton *btnDontSave = [[NSButton alloc] initWithFrame:NSMakeRect(12.0f, 12.0f, 106.0f, 32.0f)]; 
[btnDontSave setTitle:NSLocalizedString(@"Don't Save", @"Don't Save")];
[btnDontSave setButtonType:NSMomentaryPushInButton];
[btnDontSave setBezelStyle:NSRoundedBezelStyle];
[btnDontSave setAction:@selector(dumpChangesAndQuitPerPendingConfirmPanel)]; // method defined in this, the appDelegate class
NSView *viewToReceiveNewButton = [self.fooController.panelForInput contentView];
[viewToReceiveNewButton addSubview:btnDontSave];
[btnDontSave release];

// Change the “proceed” button’s title to "Save", make it the default, and assign its action.
[self.fooController.btnProceed setTitle:NSLocalizedString(@"Save", @"Save")];
[self.fooController.btnProceed setKeyEquivalent:@"\r"];
[self.fooController.btnProceed setAction:@selector(saveAndQuitPerPendingConfirmPanel)]; // method defined in this, the appDelegate class

// Assign “Cancel” button's action.
[self.fooController.btnCancel setAction:@selector(callCancelQuit)];

// Finish setting up the panel and launch it.
// ...
}

I have noticed before that typical Cancel functions tend to work automatically. For instance, the Escape key automatically calls any button you have titled “Cancel.” Maybe there’s similar behind-the-scenes machinery at work here. If so, I wish I understood better what’s going on. As it stands now, I’m worried that these crisscrossing selectors might break some day, even though they all work OK for now. The inconsistency is troubling.

Was it helpful?

Solution

Actions should accept the sender as an argument. It should take the form:

- (void)someActionName:(id)sender;

Most of the standard calls work this way. If you want to use the same cancel mechanism the Escape key uses, use NSResponder's -cancelOperation: (note the colon at the end - it's the standard action-with-sender-as-the-sole-argument form).

Also, I don't see you setting targets for your buttons. The target-action mechanism means your button may have a target but should have an action. You're setting the buttons' actions but if you don't set their target, they're "nil-targeted", which means Cocoa climbs the responder chain looking for the first object that responds to the selector you set in the action.

This is the best I can tell you with the detail you provided. You'll need to be a lot more specific about your architecture (class names, exact error messages, more code if any is missing, etc.) for more detailed answers.

OTHER TIPS

If anybody is dealing with a similar issue, here’s the revised code, incorporating changes suggested by Joshua:

(1) setTarget is called. (setTarget is a method of NSButton’s superclass, NSControl.)

(2) A colon is added to the selectors, now that they take a “sender” arg.

- (void) adviseOfPendingChangesBeforeQuit {

// Open the panel.
[NSBundle loadNibNamed:@"panelConfirmation" owner:self.fooController];

// Add an extra "Don't Save" button.
NSButton *btnDontSave = [[NSButton alloc] initWithFrame:NSMakeRect(12.0f, 12.0f, 106.0f, 32.0f)]; 
[btnDontSave setTitle:NSLocalizedString(@"Don't Save", @"Don't Save")];
[btnDontSave setButtonType:NSMomentaryPushInButton];
[btnDontSave setBezelStyle:NSRoundedBezelStyle];
[btnDontSave setAction:@selector(dumpChangesAndQuitPerPendingConfirmPanel:)]; 
[btnDontSave setTarget:self]; 
NSView *viewToReceiveNewButton = [self.fooController.panelForInput contentView];
[viewToReceiveNewButton addSubview:btnDontSave];
[btnDontSave release];

// Change the “proceed” button’s title to "Save", make it the default, and assign its action.
[self.fooController.btnProceed setTitle:NSLocalizedString(@"Save", @"Save")];
[self.fooController.btnProceed setKeyEquivalent:@"\r"];
[self.fooController.btnProceed setAction:@selector(saveAndQuitPerPendingConfirmPanel:)]; 
[self.fooController.btnProceed setTarget:self];

// Assign “Cancel” button's action.
[self.fooController.btnCancel setAction:@selector(cancelQuit:)];
[self.fooController.btnCancel setTarget:self];

// Finish setting up the panel and launch it.
// ...
}

Here’s the revised declaration of one of the selectors (which previously took no args):

- (void) cancelQuit:(id)sender;

All this code is in the application delegate class, not FooController.

As for why the Cancel function was knocked off balance by the previous wobbly code when the other buttons worked fine — that will have to remain a mystery to me. But with all three buttons now set up in parallel and all performing as expected, I’m satisfied that the code is stable.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top