I'm still picking up ObjC and I'm just trying to make sure I understand the concept of NSNotifications fully:
The [NSNotificationCenter defaultCenter] is a stationary object which is not the sender or the receiver. It merely routes an NSNotification, but in no way, shape, or form handles the event (by default).
Is that correct?
Theory:
Would that allow an AppDelegate to push a notification to the defaultCenter and have something further on in the responder chain / display list (e.g., UITableViewCell) pick up on the action?
Exactly. NSNotificationCenter is just the clearinghouse for the notifications. It keeps track of all the objects observing each notification, so that when a notification is posted, it can be routed to all the right observers.
And yeah, no reason why your AppDelegate can't post a notification that gets picked up by things like a UITableViewCell. NSNotifications are great for situations where an object has to send data to other objects, or tell them that something happened, and you won't know what the recipients should be until runtime.
Related
I have the following code in a Cocoa application:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSArray* arrayAppList = [[NSWorkspace sharedWorkspace] runningApplications];
}
My intention is to use KVO to detect an application when changes its state between inactive to active.
I read that I have to use the instance method -addObserver:forKeyPath:options:context:
And then use -observeValueForKeyPath:ofObject:change:context: to respond to change notifications.
I understand that -observeValueForKeyPath is a callback method where I can write code to respond to the properties changes I am interested in.
Nevertheless, I feel confused in how I must to use the addObserver method in order to be notified when the active property of the runningApplications change. Now, I am wondering where is the place to make the registration, for now I am using -applicationDidFinishLaunching but not sure if is the right place to do it. Additionally if I use the -observeValueForKeyPath callback method, I have to implement it in the class that inherits from NSObject and is the same class where I am registering the notification?
You should call the addObserver:… method on each object in the runningApplications array (using isActive as the key path).
Starting the observing after your app finishes launching sounds about right. Time-wise, that is. As for the place, there should be a separate class dedicated to these observations. By implementing the observation code right in the app delegate you would violate the single-responsibility principle (and that means headache in the long term).
The observeValueForKeyPath:… callback should be implemented by the object that called the addObserver:… methods.
So I'm building a program that features the use of the IKImageBrowserView component as a subview in an NSWindow. As a side note, I have a controller object called ImageBrowserController which subclasses NSWindow and is set as the delegate of the NSWindow object of my app.
I have sent IKImageBrowserView the message setCanControlQuickLookPanel:YES to enable it to automatically use the QuickLook functionality to preview image files when the IKImageBrowserView is a first responder to receive key events. Then it took me a while to figure out how to make the IKImageBrowserView a first responder which I finally got working by overriding acceptsFirstResponder inside my ImageBrowserController.
Now I understand that as the delegate to the NSWindow, ImageBrowserController has a place in the responder chain after the event gets triggered on NSWindow. And I understand that as a subview of NSWindow, IKImageBrowserView is in line to be passed events for event handling. What I don't get is where the connection is between the ImageBrowserController being a first responder and the event somehow making it to the IKImageBrowserView. I didn't set NSWindow or IKImageBrowserView as first responders explicitly. So why isn't it necessary for me to implement event handling inside my ImageBrowserController?
EDIT: So after reading the accepted answer and going back to my code I tried removing the acceptsFirstResponder override in my ImageBrowserController and the QuickLook functionality still triggered just like the accepted answer said it would. Commenting out the setCanControlQuickLookPanel:YES made the app beep at me when I tried to invoke QuickLook functionality via the spacebar. I'm getting the feeling that my troubles were caused by user error of XCode in hitting the RUN button instead of the BUILD button after making changes to my code (sigh).
Some of what you are saying regarding the interactions between your objects does not make sense, and it is hard to address your stated question without some background.
As you say, your window delegate has a place at the end of the responder chain, after the window itself. The key point I think you are missing is that GUI elements, such as your IKImageBrowserView, will be at the beginning of the chain, and any one of them in a given window could be the current firstResponder.
When your application gets an event, it passes it off to the key window (which is just the window which currently accepts "key" (i.e., "keystroke") events). That window begins by asking its firstResponder to handle the event. If that object refuses, it passes the event to its own nextResponder, usually its superview, which either handles it or passes it on, until the event has either been handled or passed all the way up to the window object itself. Only then will the window (if it does not handle the event itself) ask its delegate to handle the event.
This means that the connection between the window delegate and the IKImageBrowserView is only through the Responder Chain, and its nature is simply that if the view declines to handle any given event, the delegate may eventually be asked to handle it, if no other object in between them handles it first.
Your window delegate does not need to be a firstResponder. Nor does overriding acceptsFirstResponder on the window delegate have any effect on one of the window's subviews.*
Your window delegate also does not need to (and, indeed should not) be a subclass of NSWindow. All it needs is to be a subclass of NSObject which implements whatever methods from the NSWindowDelegate Protocol you are interested in, and methods to handle any events you might want to catch if they are not handled by other objects.
So, the answer to your explicit question at the end is (and I do not mean this sarcastically): you only need to implement event handling in your window delegate if you want it to handle events itself.
*: IKImageBrowserView already responds YES to acceptsFirstResponder. If there are no other subviews in your window, it will automatically be the firstResponder when your application starts. You can set this initialFirstResponder explicitly in Interface Builder by connecting that outlet on the window to whatever object you want.
I'm trying to implement a view controller for a custom NSOpenGLView based view (this is Cocoa, not Cocoa Touch).
The view is contained within a NIB loaded window but it does not have its own NIB. In fact the window contains multiple instances of the view.
I want to route mouse events to the controller instead of to the view. I would like for this to happen as soon as the user clicks within the corresponding view.
So how can this be done ?
I've tried having the view's becomeFirstResponder method call makeFirstResponder with the controller as argument. However that doesn't seem to work, the view still receives the mouse events instead of the controller if NSView::becomeFirstResponder returns YES. If it returns NO then neither of my classes receive the mouse events.
Of course I could implement the mouse event handling methods in the view and explicitly forward them to the controller but it seems like there should be a better way to handle this.
For general "first responder" status, I recommend Charles Parnot's MTViewController, an NSViewController subclass that uses KVO to make certain the controller is in the responder chain with no extra effort on your part.
However, in your case, you want mouse events too. There's really no way around this - your view will need to translate mouse events into controller interactions.
I am trying to update another windows when the one becomes visible. So I found the NSWindowDidExposeNotification and tried to work with it, so I wrote in my awakeFromNib:
// MyClass.m
- (void)awakeFromNib {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(mentionsWindowDidExpose:)
name:NSWindowDidExposeNotification
object:nil];
}
and implemented the method
// MyClass.h
- (void)mentionsWindowDidExpose:(id)sender;
// MyClass.m
- (void)mentionsWindowDidExpose:(id)sender {
NSLog(#"test");
}
But it never gets called which is odd. What do I do wrong here?
Generally speaking, you would set up your controller as the window's delegate in order to receive these notifications, like so:
// MyClass.m
- (void)awakeFromNib {
// note: this step can also be done in IB by dragging a connection
// from the window's "delegate" property to your `MyClass` object
[window setDelegate:self];
}
- (void)windowDidExpose:(NSNotification *)notification {
NSLog(#"test");
}
Although, after reading here and here, windowDidExpose may not be your best bet. I would recommend trying the windowDidBecomeKey delegate method instead. That one is posted whenever your window gains "focus" (starts responding to user input) which may be the right time to show your second window.
Update: (in response to comments)
Apple's documentation (quoted below) indicates that NSWindowDidExposeNotification is only valid for nonretained windows, which, according to the posts that I linked above, are quite uncommon.
NSWindowDidExposeNotification
Posted whenever a portion of a nonretained NSWindow object is exposed, whether by being ordered in front of other windows or by other windows being removed from in front of it.
The notification object is the NSWindow object that has been exposed. The userInfo dictionary contains ... the rectangle that has been exposed.
On a higher level, NSNotification objects are simply packages of data that get passed around between Cocoa classes and NSNotificationCenter objects. NSNotificationCenter objects are controllers that manage these packages of data and send them out to observers as required. There is usually no need to trap notifications directly. You can simply use KVC/KVO or pre-defined delegates in your classes and Cocoa handles all of the dirty details behind the scenes.
See Notification Programming Topics and Key Value Coding Programming Guide if you want to know more.
I've got a window which reflects the status of an NSOperation. How should I bind the NSProgressIndicator to the NSOperation's progress-property?
AppKit is not thread-safe. Any updates to a UI object must happen on the main thread. KVO doesn't dispatch observation messages across threads. So you'll need and another way of updating the progress indicator than just plain KVO.
In your NSOperation's main method, you'll need to dispatch progress messages periodically. The easiest thing to do would be to use NSNotificationCenter with a custom notification so that the main thread can listen for the updates. (Note that notifications are always delivered on the thread from which they were sent, so you'll need to use the performSelectorOnMainThread: method to make sure the notifications are delivered on the UI thread.)
In your main thread, you'll need to add your class as an observer to receive those notifications and update the progress indicator. If you want to use bindings for the progress indicator, you can bind it to a property on your controller object that you update when you receive progress notifications from the NSOperation.
Ben Copsey's ASIHTTPRequest wrappers do exactly this sort of thing.
The request is a subclass of NSOperation, and you can pass it a progress indicator when instantiated.
The code is available for you to look at, if you wanted to see how it works.