Search code examples
xcodemacoscocoanswindowibaction

IBAction for window becoming visible in cocoa for OS X


I have one window with a button in it. When I click on the button it opens another window (both windows are in the same xib file). I did this by binding the button in first window to the orderFront of the second window. This works as expected. Now I need a callback function (IBAction) to get triggered whenever the second window becomes visible (to make some changes to the second window based on settings in first window). I have the placeholder IBAction in the second window's controller class, but I can't seem to find a trigger for the window "becoming visible" to bind with it. Weird that there's nothing about callback functions on becoming visible for NSWindow anywhere (googling). This stuff is done with clicks in .NET and Visual Studio (I'm porting code from .NET). Am I going about this completely wrong or something?


Solution

  • I would say yes, from the perspective of standard Cocoa design you are trying to go about it a bit the wrong way, for two reasons. Sorry for the long answer, there's a lot to go over here on a conceptual level.

    First of all, when you want to know that particular things have happened to particular objects, the target/action mechanism is not the standard way to go about it in Cocoa unless the "things happening" are direct user actions such as clicks. The way that you have wired a button's action to show a window is fine; that is user-initiated (button press), and so an action is appropriate. But to know that the second window has changed state (become visible), it would be more typical to use either (1) delegation, or (2) notifications. Both of these are useful; read up on them in Apple's docs (I'm sure Google will help you here). For example, you could declare an object to be the delegate of your second window, and it would then receive messages when the window became key or main, when it changed screens, when it closed, etc. – whatever state changes you were interested in.

    However, there's a second twist here. If you look at the docs for the NSWindowDelegate protocol, you'll see delegate methods for things like windowShouldClose: and windowWillClose:, but no corresponding method like windowDidOpen: or windowDidOrderIn:. There are some delegate methods that might work for you, depending on exactly what you're doing, such as windowDidBecomeKey: or windowDidBecomeMain:, or maybe even windowDidExpose:, but no windowDidOpen: or similar. That, I think, is Apple sending you a message: your code really shouldn't care. So you should not use windowDidBecomeKey:, etc., unless they truly represent the event that you are interested in – and it sounds like they don't. Instead, you should think more deeply about what Apple is telling you by not including a windowDidOpen: message. Why would they leave out such an obvious delegate message??

    Here's my answer to that. Your second window has already been loaded, since it is in the same nib as the first window. Whether it is visible or not, at any given moment in time, is a detail that it is wise to avoid making assumptions about, and that is the reason, I would surmise, for Apple's delegate design. The window might get shown automatically by window restoration; it might come and go as a result of something outside of your app's code, with a technology like Exposé or Spaces or whatever Apple decides to do next; and you might make the window visible yourself in various different places in your code. You shouldn't need to worry about such details with code to set up the window at the last minute before it becomes visible to look right. If a window is in the window list, whether it is currently visible or not should not be the concern of your code in most cases; the window should be kept in a good state, such that it is happy to be shown without any need for last-minute fix-ups just before it becomes visible. According to Apple's design, your code should be worried about the moment when the window is first created from the nib, to set it up in the proper state – awakeFromNib is for that – and about the moment when the window closes (because typically, although not always, the window ceases to exist when it is closed, and so there is cleanup to be done) – windowWillClose: is for that. In between, your window should be kept in a good state. The work to do this should be quite minimal, since all drawing will be suppressed unless the window is actually onscreen.

    So when you write that you want "to make some changes to the second window based on settings in first window", the right design is likely to be to make those changes in the second window immediately in response to whatever changes in the first window occur. Detect the changes in the first window with actions connected to whatever user interface is changing, and in response to those actions, change the second window. Then when the second window gets shown, your code won't even need to know. This is a much cleaner design; it means, for example, that if the first window changes further after the second window has been shown, the second window will automatically react to those changes, whereas a "fix the second window up once just before it displays" design would not achieve that.

    If this doesn't seem right for what you're trying to do in your application, then you need to be more specific about what exactly you're trying to achieve. I would argue that in general it is the correct design according to the logic of Cocoa, if both windows are in the same nib. If it seems like the wrong design to you, then that is almost certainly an indication that the second window should not, in fact, be in the same nib as the first window, and that a new nib should get loaded for the second window, causing the second window to get configured by the controller that loads the nib for the second window, after the nib finished loading. (Or by a separate controller for the second window, or maybe by the second window itself in its awakeFromNib; there are many options here depending on your architecture.)

    I would urge you not to hack this in by subclassing NSWindow as another answer has proposed (that answer, now deleted, suggested subclassing and overriding a method such as makeKeyAndOrderFront: in order to more or less hack in a windowDidOpen: sort of functionality). Apple left out the windowDidOpen: delegate message for a reason; if it were just an absent-minded oversight it would have been added many years ago. The key to programming in Cocoa is to learn not to fight Apple. Cocoa is an extremely well-designed framework, in general (although there are classes that are exceptions :->), and if you learn to go with it instead of fighting it your life will be much easier in the end.