I have a NSWindow that hosts a WebView that Ive hooked up to a script handler.
Now, when the user clicks a button on a control on the WebView it calls a Objective C method on my object.
In this specific case, the action of the button is to try and close the window hosting the WebView
[[webView window] close];
This usually works, but sometimes i get a SEGFAULT or some other access violation as a result of the event loop trying to dispatcha mouse message to the now destroyed view.
The callstack is horrible when I try to close the window, the even loop has called the window has called the webView, has called my script delegate when I try and close the window. Destruction of an object from a callback from that object is generally, well, dangerous, but I can't figure out how windows should safely be closed as a result of users interacting with views on them.
Istead of closing, can't you try out the API
- (void)orderOut:(id)sender
just check whether your window is visible and orderout that window
if([[webView window] isVisible])
[[webView window] orderOut:self];
The callstack is horrible when I try to close the window, the even loop has called the window has called the webView, has called my script delegate when I try and close the window. Destruction of an object from a callback from that object is generally, well, dangerous, but I can't figure out how windows should safely be closed as a result of users interacting with views on them.
You can use performSelector:withObject:afterDelay: to put off closing the window until 0.0 seconds after the button hit.
In this specific case, the action of the button is to try and close the window hosting the WebView
[[webView window] close];
This usually works, but sometimes i get a SEGFAULT or some other access violation as a result of the event loop trying to dispatcha mouse message to the now destroyed view.
That's not likely. The event loop will only dispatch an event for a window that exists; if you have closed and thereby destroyed a window, no event can arrive at that window, nor at any view that may once have been in it.
It would help if you would edit your question to include the stack trace for that crash.
I know this is an old question, but I'm having a similar issue and I suspect the accepted answer is missing the point. I believe the window hosting the WebView is still one or more of the delegates of the WebView, and delegate methods are being called after the WebView finishes loading, which is after the window is closed.
I was looking for the right way to resolve this… I'll keep looking. :-)
Related
In my MAC app, in one use case, I prompt an window to the user and give him 2 options (say buttons Save and Cancel). I want to force the user to select either of the 2 buttons to close the window.
But currently I find that if the user hits "Command + w" key when window has the focus, the window gets closed. In the .xib resource file, I uncheck the "close" option but that only disables the close option in the window UI.
How do I make sure that my window ignores the "Command+w" key and stays as is without closing.
Have also tried removing the notification by adding below code in awakeFromNib method but did not help.
[[NSNotificationCenter defaultCenter] removeObserver:NSWindowWillCloseNotification ];
Have also tried to implement "windowShouldClose" delegate method and return NO, but this method is never called. The documentation too says that this method is not reliable.
You should use an NSAlert for this sort of prompt, probably run as a sheet on the window. That would avoid the problem of closing it.
In any case, the window's delegate can implement -windowShouldClose: to control if a window is allowed to close. You can make an object (often the window controller) be its delegate by declaring that it adopts the NSWindowDelegate protocol and connecting the window's delegate outlet to that object.
I recently had to solve a similar problem. I'm not sure that this is the 'right' way to do it. But it worked for my purposes, and might work for you.
By default, I think, the 'Close Window' (CMD+W) menu item is bound to the action 'performClose' on first-responder. If you remove this binding and instead bind to a custom IBAction on your application delegate or main window controller, it allows you to conditionally call the close method of the current key-window if it is not matching the instance that you want to keep alive.
#property (strong, nonatomic) MyWindowController *unstoppable;
-(IBAction)killActiveWindow:(id)sender
{
NSWindow *keyWindow = [[NSApplication sharedApplication]keyWindow];
if ([keyWindow isNotEqualTo: unstoppable.window]){
NSLog(#" CMD+W Closing Window %#",keyWindow.title);
[keyWindow close];
}
}
The question
What happens when one click on the red button to close the window attached to an NSDocument?
What methods are called on which object?
Can I hook up somewhere in the process?
Why I ask this question:
When I click on the red button here of the window attached to the NSDocument, the panel
does not show up. I want to debug this, and that's why I need to know the process of actions raised by this click.
More info
I put a breaking point in - (void)windowWillClose:. When it stops here, the window seems to be already closed and if I ask p (bool) [self isDocumentEdited], I get true.
I have the following method in my GameWindowController (subclass of NSWindowController):
- (void)windowWillClose:(NSNotification *)notification {
AppDelegate *delegate = [NSApp delegate];
[delegate removeGameWindowController:self];
}
The code for removeGameWindowController in AppDelegate is:
- (void)removeGameWindowController:(GameWindowController*)controller {
[self.controllers removeObject:controller];
}
self.controllers is an NSMutableArray with all my GameWindowControllers.
The above code seems to have a race condition. It will randomly crash with EXC_BAD_ACCESS when I close windows, almost every time if I close all windows at once.
My guess is that ARC is deallocating the window controller before or as removeGameWindowController: returns, leaving the window with a dangling pointer to the controller. I have tried adding controller.window.windowController = nil; to no avail.
For some reason, using the (BOOL)windowShouldClose:(id)sender delegate method instead as suggested in https://stackoverflow.com/a/11782844/344544 works, but is not an acceptable solution as it is not called upon quit.
How can I reliably remove my window controllers from the array of controllers after each window has closed? Is there some other delegate method which gets called or some NSNotification I can subscribe to which fire after a window has finished closing?
After lengthy investigation and step by step running in the debugger I figured out the source of the problem and a possible solution.
The window controller was indeed being released at some point after the end of removeGameWindowController: along with all its strong references which include the NSWindow. If the window was released before the stack had unwound back to the close call on the window itself, the program would crash while finishing the that function as self in this particular case is a dangling pointer.
I was unable to find a way to get notified after a window had closed, however it is likely such an approach would have had the exact same problem.
In order to ensure no reference to the window was left anywhere on the stack I queued the removal of the window controller from the array to happen as a subsequent event on the runloop:
- (void)removeGameWindowController:(GameWindowController*)controller {
[self.controllers performSelectorOnMainThread:#selector(removeObject:) withObject:controller waitUntilDone:NO];
}
I show my main window by calling
[window makeKeyAndOrderFront:self];
[NSApp activateIgnoringOtherApps:YES];
[window setIsVisible:YES];
[window display];
which works, but doesn't set the window to the key window right after this calls. I have to wait "some time" until [NSApp keyWindow] returns the actual window.
I'm wondering now, how long does this take and how can I force a window to become the key window immediately?
I think there are probably good reasons that makeKeyAndOrderFront isn't a synchronous call, namely there could be coordination involved with multiple windows and objects that NSApplication need to take care of to make it happen, so forcing window to become key immediately is probably not supported by Cocoa. This however may not be a problem depending on the problem you are trying to solve.
Now, my guess is that some of your methods depend on the window being key, and at the moment they are not happening properly because the window doesn't become key immediately. However, you can implement the NSWindowDelegate protocol, set yourself as window delegate, and override - (void)windowDidBecomeKey:(NSNotification *)notification method to find out when the window did become key. This should also be a global notification in case that works better for you.
For more details, check out apple docs at http://developer.apple.com/library/mac/#documentation/cocoa/reference/NSWindowDelegate_Protocol/Reference/Reference.html
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.