Best way to wire up NSMenuItems from Interface Builder? - objective-c

So I've spent some time checking out CocoaDev, reading the Cocoa docs on NSMenuItems, and doing some tests in Interface Builder.
In my application I have an application menu ([NSApp mainMenu]) designed in Interface Builder. I see three potential paths:
Put my action responders in the NSApplicationDelegate. This seems strange to me, partially because it's so far up the food chain, partially because it seems bolted on.
Create a sub-view(s) that would listen for the various NSMenuItem action messages. This would seem useful but it looks like in order for it to be in the responder chain there might be some magic I couldn't figure out.
Create an NSObject that listens for the specific application menu stuff, put it in the xib, and wire it up. This seems to me the best solution at the moment, because I can isolate stuff, and not depend upon the responder chain to reach a specific object. BUT I wonder if, when I get my app to a sufficient level of complexity, this may be a problem because it usurps the responder chain, which is there for perhaps a reason beyond just ease of use.
Sorry for the long question. Is there a preferred approach?
Thanks!

It really depends on the architecture of your application. As a general rule, implement actions wherever they make sense. The responder chain for action messages helps you in that regard.
If your application isn’t document-based, the responder chain for action messages goes like this:
Whichever responder is the first responder
View hierarchy
Window
Window controller
Window delegate
NSApp
Application delegate
I only use actions in the application delegate if they’re truly global for the entire application. Otherwise, I put them in the window controller (which is normally the window delegate as well) if they make sense for a specific window, or a view controller if they make sense for a specific view.
It’s worth mentioning that view controllers (subclasses of NSViewController) aren’t automatically inserted in the responder chain. I do that manually after adding the corresponding view to a superview. For instance, in an NSViewController subclass:
NSResponder *nextResponder = [[self view] nextResponder];
[[self view] setNextResponder:self];
[self setNextResponder:nextResponder];
This inserts self (an instance of a subclass of NSViewController) in the responder chain between the view and the original view’s next responder.
Note that there’s nothing inherently wrong with your third approach, namely having a specific target for (a subset of) action messages. The responder chain exists to give a chance for different objects to handle action messages because some actions can be context-dependent. For example, the actions under the File menu are normally applied to the window that’s currently the main window, so it makes sense to not have a specific target and use the responder chain instead. On the other hand, the actions under the ApplicationName menu are truly global—they don’t need to go through the responder chain, so you can hook them up to a specific target.

I usually just expose IBActions in the app controller (NSApp delegate), and wire the menu items up to those actions. This is a pretty standard way of doing things. If you have a lot of menu items, you can also break the functionality up into one or more controllers that are connected to the app controller, and wire the menu items up to them.

Related

Is there a way to disable sounds (such as "beeps") in my mac app?

I know I need to dig the reason why my app is beeping in the code, etc.
But I was wondering, is there a global setting to disable sounds all over my app screens?
this is very little information to go on, but usually your application is beeping when the responder chain comes up with no object that can respond to an event on the screen or keyboard.
For instance, if you type text in an active view and the view doesn't allow for text editing, the view sends the key down event to its super view. For a view this can end by the NSPanel or NSWindow or BSWindow controller. The last responder in the chain invokes the noResponderFor: method, which, when not implemented, will give a beep. If you don't want it to beep, override this method to do something else.
Based on your information I can't give you any other information.

MVC practice regarding adding targets to UIButton on View

I have a question regarding best practices when adding targets to a UIButton on a custom view. Currently, I create my UIButton in a loadup method of my view, and assign my view as the buttons target. My view is handling logic when the button is pressed, and it occurs to me that this is not good MVC.
So, I'd rather have my controller provide itself as target and an action for the button, however I'm not sure the best way to accomplish this.
The view could be initialized with a reference to the controller, or could get the controller using UIResponder's nextresponder, and set the target with this reference. However, this could result in circular retains, and would require my view to be aware of the methods that exist on my controller, which is more tightly coupled than I'd prefer.
Alternatively, I could create a setter for each uibutton on my view. However, this quickly becomes unwieldy if I have several buttons, or a view that contains a custom subview with buttons. I could also create properties for each button, but that would also be unwieldy and allows the controller access to more than it needs (it just needs to set targets, it doesn't need to be able to get any reference to the button).
Finally, I could create and add targets to all my buttons within the controller, and pass it to the view on initialization, but that seems to violate the roles of MVC as well.
It seems as if this is a common scenario, are any of these practices considered standard, or is there a better solution?
My personal belief is that in Cocoa custom views are allowed to have logic and state needed for them to operate, but that they should fully encapsulate the logic and state. That is, you should expose an interface to subviews, but not the subviews themselves. You should expose any private properties or subviews through a combination of delegates and properties as well as custom actions.
That being said, given that you provided no specifics on the purpose of your custom view, it is difficult to provide specifics on the best approach.
It's proper to target the view that owns the buttons. This way, if you need to do any view manipulation (enable/disable, highlight, popup, etc) you're in the right place. Also, only the view will know what the button is, so that, in your action, if you want to check what is sender, you can do it. But having your controller know about each individual button would seem to be a more egregious violation of MVC.
It's not inappropriate to have an accessor for your buttons. It can be handy to have the reference around at runtime. If you don't use it, it's hard to argue that there's harm in keeping around an extra id. As for hiding them in a private interface, that's fine, but unless you're publishing your API or working with morons, I don't know what harm there is in making the accessors public.
It's proper for your view to have a weak reference to your controller, and the button actions can be as simple as invoking one of your controller's methods. It's no big deal, and if you want to add some logic a little later, there's a spot for it.
Sounds like you're doing fine.
PS This is stupid:

Main Menu in different nibs

I have a Cocoa app with a ManMenu.xib, in which the main menu is stored and different other subviews in several xibs handling different tasks of my app. So how do I make my sub-xibs or rather their NSViewControllerController receive actions from the menu?
i have an idea but I don't know if it's the right way: I subclass NSViewController and add some actions like - (IBAction)undo or - (IBAction)redo use instances of it as the files owner. Further I add a pointer to my app delegate pointing at the actual view or rather its controller and the menu sends the action to that pointer.
Wise solution?
You should hook up your menu items to the First Responder. Their action messages will then travel along the responder chain until they reach something that responds to them.
You'll need to make sure your view controller is in the responder chain by setting it as the next responder of something else in the responder chain. I would suggest the window controller.
Speaking of the window controller, you probably should be handling undo there rather than in a view controller—it would be confusing for different views in the same window to have different undo lists. The only exception I can think of would be if the controlled views correspond to document tabs, like in Photoshop or Xcode.

Set a subview as the responder to calls to a parent UIView subclass

I've got a UIView that I've subclassed to be the main view used throughout my app. In it, I have two subviews: banner and container. Banner is basically a place to put an ad or a disclaimer or whatever. Container is meant to act as the primary view, to which you can add, remove and whatever as if it were the only view.
Right now, I'm just overriding the methods of the parent view and sending the calls to the container view. I'm wondering if there is an easier way to do this, without having to write out stuff like this for every method:
- (void)addSubview:(UIView*)view {
[container addSubview:view];
}
Maybe something that lets you delegate all method calls to the view to a specific subview, rather than responding to the method calls itself.
Anyone know if this is possible?
I'm a little confused by the question.
The responder chain is present and passes ui events up through all visible views on screen, by hierarchy. It may be useful to read a little about the responder chain, because by design it passes events from the deepest view to the highest (root) in that order, which is the opposite of the direction you're seeking (if I'm reading this right).
If you need to forward events from a superview to a subview, to respect principles of encapsulation, you should define appropriate actions in your subview's subclass interface, and then then configure your superview to target the actions in that subview/class.

Why does an NSWindow or NSView instance handle its own key events, and not its delegate?

As a newcomer to Cocoa, I am struggling to understand why the generic NSResponder subclasses implement key events the way they seem to do.
In my program, I have an NSWindow subclass which takes up the whole screen, and must necessarily handle key events. There are several major commands which can change the whole state of the program (e.g. pause a timer when the user hits the spacebar) which it does not make sense to have subviews like an NSTextField handle.
It seems to me that the delegate (controller) should get these events. Instead, I find I have to either write a bunch of messy glue code to have the window (via its keyDown: and interpretKeyEvents: selectors) notify the controller, or I have to just move a bunch of controller code to the NSWindow subclass itself.
This is messy and my gut tells me I'm missing something. Is there a cleaner solution?
If you've set it up correctly, the NSWindow's delegate will receive the messages. Cocoa uses the responder chain to forward messages from the first responder -- the key view for key messages, and the view that was clicked/hovered/etc. for mouse messages -- back through the superviews, up through the window, and eventually to the window's delegate. There's a pretty good diagram of the typical responder chain on Apple's site.
You really should never have to subclass NSWindow unless you're implementing some fancy window drawing or something else along those lines. Cocoa provides the NSWindowController class to behave as a controller for a window and its contents.
The usual pattern is to subclass NSWindowController and add your IBOutlets to it, and then use a NIB to lay out your window contents. You make your NSWindowController subclass the class of the File's Owner proxy in Interface Builder. And you also assign the window's delegate to the window controller so that it can become part of the responder chain. Finally, to create windows, you use NSWindowController's initWithWindowNibName: method, which automates loading the NIB with a new window controller as the file's owner.
I'd recommend reading up on window controllers in the Cocoa documentation, because they provide the missing link you're looking for.