2
votes

I would like to track each time a certain window appears (becomes visible to the user) in a OS X app. Where would be the most adequate place to call the tracker?

windowWillLoad, maybe?

I expected to find something like windowWillAppear but it seems I'm thinking too much iOS.

2
What do you mean by "shown"? How is the window coming into view and going out of view -- hiding, closing, loading, makeKey, makeMain? There are quite a few choices, and which ones are used would affect the answer to this question.rdelmar
For tracking purposes, I would define "shown" as "becomes visible to the user". makeMain is not a choice as not all windows can be main.hpique

2 Answers

1
votes

How about getting notification such as NSWindowDidBecomeMainNotification, By main I guess the one which is top most on screen directly visible by user.

see : Apple Documentation

1
votes

Yes, one would expect that a window would notify its delegate or its controller with a windowWillAppear or windowDidAppear message, or post a documented notification like NSWindowDidAppearNotification. But alas, none of those exist. I filed a bug report with Apple and was given the advice to use a storyboard and a view controller instead. This is unhelpful in legacy apps that already use a bunch of window controllers and xibs.

You could subclass NSWindow and override orderWindow:relativeTo: to send a notification. Most, but not quite all, of the messages that make a window show itself ultimately go through this method, including orderBack:, orderFront:, makeKeyAndOrderFront:, and -[NSWindowController showWindow:]. But orderFrontRegardless does not go through orderWindow:relativeTo:, so you would also want to override that for completeness.

Another way to be notified is to make a subclass of NSViewController that controls some view that's always visible in the window. The view controller will receive viewWillAppear and viewDidAppear.

If you're subclassing NSWindow or NSViewController already for some other reason, either of these is a reasonable solution.

If you're not subclassing NSWindow already, and don't have an NSViewController subclass for a view that's always visible in the window, then another way is to use Cocoa bindings to connect the window's visible binding to a property one of your objects. For example, I have a custom NSWindowController subclass. I gave it a windowIsVisible property:

@interface MyWindowController ()

@property (nonatomic) BOOL windowIsVisible;

@end

and I implemented the accessors like this:

- (BOOL)windowIsVisible { return self.window.visible; }

- (void)setWindowIsVisible:(BOOL)windowIsVisible {
    NSLog(@"window %@ became %s", self.window, windowIsVisible ? "visible" : "hidden");
}

and in awakeFromNib, I bind the window's visible binding to the property like this:

- (void)awakeFromNib {
    [super awakeFromNib];

    [self.window bind:NSVisibleBinding toObject:self withKeyPath:NSStringFromSelector(@selector(windowIsVisible)) options:nil];
}

When the window becomes visible, the setWindowIsVisible: setter is called with an argument of YES. Note that if the whole app is hidden and reappears, the setter is called again, even though it wasn't called with argument NO when the app was hidden. So be careful not to assume the window was previously hidden.

Also, the binding might create a retain cycle, so you should probably unbind it when the window is closed, unless you want to keep the window and controller around. Note that the window does post NSWindowWillCloseNotification when it's closing, so you don't need any special magic to detect that.