Completion handler never called for NSSavePanel beginWithCompletionHandler - cocoa

When I open an NSSavePanel or NSOpenPanel instance with beginWithCompletionHandler: the handler is never called. Instead I see the panel appear for a fraction of a second, before it goes away again without letting the user select a file. When I open the panel with runModal it works just fine. Here the code:
NSSavePanel *savePanel = [NSSavePanel savePanel];
//[savePanel runModal]; // Works
[savePanel beginWithCompletionHandler:^(NSInteger result){
NSLog(#"DONE"); // Never called, dialog disappears right away
}];
Is there anything I'm missing here?
Thanks, Mark

Found the problem: in the above code, the savePanel instance is autoreleased as soon as the surrounding method ends. This causes the panel to disappear. The solution is to hold on to the panel reference until the completion block is called.

Related

Re-open an NSWindow after it has been closed?

I have a subclass of an NSWindowController called UpgradeWindowController.
So far this works for the first click; the window launches. However if you close that window, and click the button again to show upgrade window, nothing happens.
- (IBAction)showUpgradeWindow:(id)sender {
if (!self.upgradeController){
self.upgradeController = [[UpgradeWindowController alloc] initWithWindowNibName:#"UpgradeWindow"];
}
[self.upgradeController showWindow:self];
}
Any ideas? Thanks
Ok, the problem was the XIB. The file's owner needed to be connected to the Window.
Give you a suggestion, the window can be objects stored in a variable or array, although this is closed, but is actually hidden, the next time we need to use it, can go to check the window if the object is loaded, if loaded to call and display it. This can be more interesting and convenient.

Why is the NSStatusItem displaying multiple times?

A NSStatusItem has a NSMenu attached, and one of the buttons of the NSMenu opens a NSWindow. Whenever one of these buttons is clicked, the window opens as expected and works properly, but another display of the NSStatusItem is opened.
The NSStatusItem is a clock, so I can see that it is updating correctly. However, the cloned NSStatusItem doesn't have its own menu. If I push the button that makes the window more times, more cloned versions of the NSStatusItem pop up.
Everything works fine except for this.
That's not a whole lot of information to go off of, but there's nothing else I can think of that could potentially help you. I would be happy to provide more information or try something.
EDIT: Every time the button is clicked, awakeFromNib is somehow called, which is why another half-working NSStatusItem happens.
EDIT: Temporary workaround is to put the awakeFromNib method in a dispatch_once.
EDIT: Added method that is triggered when button is clicked, as suggested by #zpasternack
- (IBAction)preferences:(id)sender {
self.windowController = [[NSWindowController alloc] initWithWindowNibName:#"PreferencesWindow"];
[[self windowController] showWindow:self];
}
Is the NSStatusItem contained in the PreferencesWindow nib? That might explain it, since you're loading the nib each time the button is clicked.
Also, is there a reason you need to recreate that window each time the button is clicked? Maybe you could only do it the first time?
- (IBAction)preferences:(id)sender {
if( self.windowController == nil ) {
self.windowController = [[NSWindowController alloc] initWithWindowNibName:#"PreferencesWindow"];
}
[[self windowController] showWindow:self];
}

Removing reference to NSWindowController from AppDelegate on windowWillClose causes crash

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];
}

Close NSOpenPanel as soon as file/directory has been selected

I am new to NSOpenPanel/NSSavePanel/NSPanel. I am using NSOpenPanel to choose a directory whose files my app will iterate over and do some fairly time-consuming processing.
I can call -close on the panel, but that does not return focus to the main window. I have read a lot about "dismissing" the panel - but I haven't found any methods that "dismiss" rather than "close" a panel or a window.
Is it just that I need to spawn a background thread (NSOperation)?
This is what my -chooseDidEnd:returnCode:contextInfo:
-(void) chooseDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
[panel orderOut:self];
[panel release];
if (returnCode == NSFileHandlingPanelOKButton)
{
[progressIndicator startAnimation:self];
[self doLotsOfTimeConsumingWork:[[panel URL] path]];
[progressIndicator stopAnimation:self];
}
}
While the NSOpenPanel does go away, my NSProgressIndicator does not animate and the main window doesn't come alive until after -doLotsOfTimeConsumingWork: completes.
Update
Just looked at NSOperationSample code, and it is looking like that's the way to go.
Two notes:
First, in Cocoa, the event handling and drawing happens on the main thread. Hence it is never a good idea to synchronously call lengthy methods there (which is the reason for your unresponsive UI).
So yes, you should hand off computationally expensive tasks to a secondary thread from this method, like from any IBAction.
Second, calling [panel release] in that method violates Cocoa's rules of object ownership! So if you would be leaking the panel without that call, you should fix that in the method where you're creating the panel.

Closing an NSWindow

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. :-)

Resources