NSPopover deallocation when losing focus (OSX, Objective-C) - objective-c

I am on OSX (not iOS), Xcode 8.2, ARC enabled, Objective-C.
I have a view that opens a popover on a button click. Both are connected with a delegate and a protocol that allows access to the following methods (among others)
- (id)valueForKey:(NSString*)key;
- (void)setValue:(id)value forKey:(NSString *)key;
(I'm using this protocol very often and need to keep it as clean and unspecific as possible)
On the popover's view there's another button that opens an NSOpenPanel. Once the panel opens, the popover dissapears and get's deallocated - which is the preferred behaviour... usually. Unfortunately i need the openPanel to save to the parent view via the protocol methods. But if the popover gets deallocated those methods are not available anymore.
So i tried to create a strong reference to the value i want to save to before the openPanel and release it afterwards to keep the popover viewController in memory. But once i try to release that... it crashes.
#interface SHWildcardItemSettingImageViewController ()
#property (strong) NSString* customImagePathBookmark;
#end
#implementation SHWildcardItemSettingImageViewController
// Create strong reference to delegate (to not dealloc popoverViewController)
[self setCustomImagePathBookmark:[_delegate valueForKey:#"customImagePathBookmark"]];
// Setup open panel
NSOpenPanel *openPanel = [[NSOpenPanel alloc] init];
[openPanel setAllowsMultipleSelection:NO];
[openPanel setCanChooseDirectories:YES];
[openPanel setCanChooseFiles:NO];
[openPanel setCanCreateDirectories:YES];
[openPanel setPrompt:#"Choose folder"];
// Display the dialog box. If the OK pressed, process the folder.
if ( [openPanel runModal] == NSModalResponseOK ) {
// Gets list of all files selected
NSArray *files = [openPanel URLs];
// Create and save Bookmark for later use (_bookmarks is a custom NSObject)
[_delegate setValue:[_bookmarks bookmarkFromURL:[files objectAtIndex:0]] forKey:#"customImagePathBookmark"];
}
[self setCustomImagePathBookmark:nil]; // <---- Crashes here
#end
What would be the best way to keep the popover allocated while the NSOpenPanel is in focus WITHOUT using different NSPopoverBehaviours.
EDIT:
Sometimes you don't see it... thanks to #Ron Gahlot
- Solved with a simple bool check:
- (BOOL)popoverShouldClose:(NSPopover *)popover
{
// Check if imageSettings can close
if ([popover.contentViewController isMemberOfClass:[SHWildcardItemSettingImageViewController class]])
return ![[popover.contentViewController valueForKey:#"lockPopover"] boolValue];
return YES;
}

you use a bool variable. when openDialog is open return NO in
- (BOOL)popoverShouldClose:(NSPopover *)popover;

Related

Setting the NSImage of a NSImageView from a NSURL

Okay, so I'm a newbie trying to write a practice app where you click a button, an NSOpenPanel appears, you select an image file, and the image gets displayed in an NSImageView.
I've got the open panel working okay, and it returns an NSArray of NSURLs. I avoided using filename paths as the Apple docs said it was depreciated. I then try to make an NSImage object using initWithContentsOfURL, and then try to setImage of the NSImageView to the new image.
Here's what I think are the relevant parts of the implementation...
- (IBAction)openImage:(NSButton *)sender {
NSLog(#"%# was clicked", sender);
NSOpenPanel *panel = [[NSOpenPanel alloc] init];
if ([panel runModal] == NSModalResponseOK)
{
NSArray* selectedFile = [panel URLs];
NSLog(#"%# was selected", selectedFile[0]);
NSImage *theImage = [[NSImage alloc] initWithContentsOfURL:selectedFile[0]];
[imageViewer setImage:theImage];
}
}
And from the header:
#property (weak) IBOutlet NSImageView *imageViewer;
- (IBAction)openImage:(NSButton *)sender;
#end
When I try to set the image, Xcode says there's imageViewer is undeclared, and wants to correct it to _imageViewer. I tried following its advice and ran it, but then the app just crashes once I select a file from the open panel so something is obviously still not right.
The NSLog lines shows the button was clicked, and shows the correct URL of the file selected, but I'm having issues setting the NSImageView. It's probably something really simple but I can't seem to figure it out.
You need to declare the variable imageViewer in your headers (.h) interface , for example:
#interface AppDelegate : NSObject <NSApplicationDelegate> {
IBOutlet NSImageView *__weak imageViewer;
}
#property (weak) IBOutlet NSImageView *imageViewer;
- (IBAction)openImage:(NSButton *)sender;
#end
Then in your class (.m) just synthesize the variable:
#implementation AppDelegate
#synthesize imageViewer;
...
Since you hadn't declared the variable in the interface you were receiving the error.
Try this code.
- (IBAction)openImage:(NSButton *)sender {
NSLog(#"%# was clicked", sender);
NSOpenPanel *panel = [[NSOpenPanel alloc] init];
if ([panel runModal] == NSModalResponseOK)
{
NSArray* selectedFile = [panel URLs];
NSLog(#"%# was selected", selectedFile[0]);
NSURL *url = (NSURL *)selectedFile[0];
NSString *filePath = [url absoluteString];
NSImage *theImage = [[NSImage alloc] initWithContentsOfFile:filePath];
[imageViewer setImage:theImage];
}
}
Okay, so I feel stupid now. I'm still learning stuff so I wasn't sure why it was freezing up until I noticed Xcode kept switching to the "Debug" panel, which clued me into what might be happening.
I figured out I accidentally clicked a line number, which apparently sets breakpoints for debugging. I also might have somehow clicked "Toggle global breakpoint state" in the bottom Debug area.
Because I selected the line where the setImage was, it looked like the app crashed/froze whenever it hit that line, but it actually just paused the app to allow me to look at what's going on with the app and debug it. I deselected the lines and turned off the global breakpoint state button and everything runs fine now.
A newbie coder mistake from somebody not used to working in an IDE. :P

unlockFocus called too many times

I click on a NSButton (openPanel) which is located on the main window of my OS X app to show a NSPanel (myPanel) using the following method:
- (IBAction)openPanel:(id)sender {
[_myPanel makeKeyAndOrderFront:nil];
}
Everything works as expected except I get the following description in the debug area only the first time I click on the button (openPanel):
"unlockFocus called too many times. Called on NSButton: 0x610000141080."
The attributes of the Panel are selected as follows:
Style: Utility Panel,
Appearance:Title Bar and Shadow,
Controls: Close,
Behaviour: Restorable,
Memory: Deferred
I have looked through the web but can not find any explanation. Does anyone know why this happens or how to resolve it?
I had the same problem using XCode 7.0 beta 6 using storyboards and a segue to display a sheet. It seems some common problem which happens if you open the panel directly from the action.
To solve the problem, just open the panel from the main queue:
- (IBAction)openPanel:(id)sender {
dispatch_async(dispatch_get_main_queue(), ^{
[_myPanel makeKeyAndOrderFront:nil];
});
}
This will also solve similar problems from swift and storyboards. Use this swift code to call a segue:
#IBAction func onButtonClicked(sender: AnyObject) {
dispatch_async(dispatch_get_main_queue(), {
self.performSegueWithIdentifier("identifier", sender: self);
})
}
Not sure what is going on there but I would try this to show your NSPanel:
-(IBAction)openPanel:(id)sender {
[[NSApplication sharedApplication] beginSheet:_myPanel
modalForWindow:self.window
modalDelegate:self
didEndSelector:#selector(sheetDidEnd:returnCode:contextInfo:)
contextInfo:nil];
Also you should make sure that Release When Closed checkbox is activated for your NSPanel.
Search for it in the Interface Builder.
EDIT: Are you drawing something on your NSPanel ? Setting a NSColor or showing a NSImage ?
My guess is that you are displaying a NSImage and messed something up concerning this: Apple Doc: lockFocus
I have fixed the same problem with the following
In Header
#interface aWindowController : NSWindowController
{
/* other Vars */
NSOpenPanel *panel;
}
In .m
- (void)windowDidLoad {
[super windowDidLoad];
/* other stuff */
/* set Panel Properties */
panel = [NSOpenPanel openPanel];
[panel setCanChooseDirectories:YES];
[panel setCanChooseFiles:NO];
[panel setAllowsMultipleSelection:YES];
}
in NSButton IBACTION
- (IBAction)getFiles:(id)sender
{
NSArray *resultArry;
if([panel runModal] == NSFileHandlingPanelOKButton ){
resultArry = [panel URLs];
return resultArry;
}
return nil;
}
ARC will clean up the panel.
I had the same problem with a NSPopUpButton that called a Segue and #Flovdis answer worked for me.
This is the Objective C code I used:
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"MySegue" sender:self];
});

Proper usage windows

I am working on my first Cocoa Mac OS X program and wondering the best approach to showing the windows.
I have my AppController / MainMenu.xib as the main launch window but have the MainMenu.xib window unchecked for Visible At Launch. I do this because on application load I am checking to see if they are logged in. If not I want to display the Login.xib window instead of the MainMenu.xib. Once logged in, I would open the MainMenu.xib window and close the LoginController I have this in the - (void)applicationDidFinishLaunching:(NSNotification *)aNotification method.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSLog(#"app delegate");
[[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Defaults" ofType:#"plist"]]];
BOOL didAuth = NO;
GTMOAuth2Authentication *auth = [GTMClasses authForService];
if (auth) {
didAuth = [GTMOAuth2WindowController authorizeFromKeychainForName:kKeychainName authentication:auth];
}
if (didAuth) {
[[DataClass sharedInstance] setIsSignedIn:YES];
NSLog(#"Already signed in %#", auth);
NSLog(#"Window: %#", self.window);
// SHOW MainMenu.xib here
} else {
NSLog(#"Not signed in %#", auth);
loginController = [[LoginController alloc] initWithWindowNibName:#"Login" owner:self];
[[loginController window] makeKeyAndOrderFront:self];
}
}
I see that AppController's awakeFromNib gets called before the applicationDidFinishLoadingWithOptions. Would it be best to put that code in my awakeFromNib?
If not, what is the best way to open the MainMenu.xib window from the AppDelegate?
If you have a better approach, what would it be?
PS: AppController is a subclass of NSObject so I don't have access to windowDidLoad or windowWillLoad
awakeFromNib is the first method to get executed.
You can also use alloc or init methods.
You can put your login authentication codes there, without any problem.
You must have seen the application life-cycle, how and when what methods get loaded.

Best design pattern for a window opening another window in cocoa application

I am learning how to create osx applications with Cocoa/Objective-C. I am writing a simple app which will link together two different tutorials I have been going through. On start up a choice window loads with 2 buttons, one button loads one window and the other loads the other window. When either button is clicked the choice window closes.
The choice window controller object was added to the MainMenu.xib file so it is created at launch. The window is then opened using the awakeFromNib message.
I want the result of one button to open up the 'track controller' tutorial application from the ADC website. The action looks like this:
- (IBAction)trackButton:(id)sender {
TMTrackController *trackController = [[TMTrackController alloc] init];
[self.window close];
}
I added an init method to the TMTrackController class which looks like this:
- (id) init {
if (self = [super init]) {
[self showWindow];
TMTrack *myTrack = [[TMTrack alloc] init];
myTrack.volume = 50;
self.track = myTrack;
[self updateUserInterface];
return self;
}
else {
return nil;
}
}
- (void) showWindow {
if(!self.window) {
[NSBundle loadNibNamed:#"trackWindow" owner:self];
}
[self.window makeKeyAndOrderFront:self];
}
I am not sure this is the best way to be doing this as I know that the choiceController class will be released when it is closed thus getting rid of the TMTrackController class too. However even when I untick the 'release when closed' box of the ChoiceWindow.xib it breaks too.
What is the correct way to do this?
With xib s in the same project use:
#interface
#property (strong) NSWindowController *test;
#implementation
#synthesize test;
test = [[NSWindowController alloc] initWithWindowNibName:#"XIB NAME HERE"];
[test showWindow:self];
[home close];
It is not completely the same but this is my solution for such problems: Stackoverflow
Just ignore my statement in this answer regarding showing the window as a modal window. Everything else is still valid. This way you could have your personal window controller and it controls everything there is within the xib. This is a huge advantage for maintaining the project afterwards (and you keep to the application logic).

NSWindow created in IB won't show modally in the application

I have an application that when pressing a button it should show a modal window. I have some questions about it but I'm going to narrow it to what it's giving me a headache.
I want to add that I'm already doing something alike that when applied to this particularly case won't work. What I already do is to show modally a NSOpenPanel. This is the code for it.
This code is inside the appDelegate.
-(IBAction) respondToPublishAction:(id) sender {
NSArray *fileTypes = [NSArray arrayWithObjects:#"xml", #"txt", nil];
NSOpenPanel *oPanel = [NSOpenPanel openPanel];
NSString *startingDir = [[NSUserDefaults standardUserDefaults] objectForKey:#"StartingDirectory"];
if (!startingDir)
startingDir = NSHomeDirectory();
//Setting the attributes
[oPanel setAllowsMultipleSelection:NO];
[oPanel setTitle:#"XML file for publishing services"];
[oPanel beginSheetForDirectory:startingDir file:nil types:fileTypes
modalForWindow:[self window] modalDelegate:self
didEndSelector:#selector(openPanelDidEnd:returnCode:contextInfo:)
contextInfo:nil];
}
Now What I'm trying to do and that won't work
I created a xib file that has a HUD Window (how it's named in IB) that acts like a NSPanel. I created a windowcotroller that listen to a button (testButton). Here's the code:
#interface BrowserWindowController : NSWindowController {
}
-(IBAction) testButton:(id) sender;
#end
I set the file's owner of the xib file to be of the class BrowserWindowController and linked the button with the testbutton.
Back to the appDelegate when a button is clicked I want to show this Panel modally. Here's the code:
- (IBAction) respondToBrowseAction:(id) sender {
browsePanel = [[BrowserWindowController alloc] initWithWindowNibName:#"BroserWindow"];
}
I don't see any function in the API like NSOpenPanel to show this window modally.
Thx to anyone who might answer.
The reason there is no NSWindow API for running modally is because it's the application that is going into a modal mode. See How Modal Windows Work and Introduction to Sheets.