NSWindowController cannot open NSWindow for second time - objective-c

I have a NSWindowController subclass, BBPreferencesWindowController
#implementation BBPreferencesWindowController
- (NSString *)windowNibName
{
return #"PreferencesWindow";
}
...
And a function in my AppDelegate that can open the window in the "PreferencesWindow.xib" through this controller.
This function is called from an NSMenuItem, attached to an NSMenu under an NSStatusItem item in the system menu bar.
#property (strong) BBPreferencesWindowController *preferencesWindow;
...
- (void)openPreferences
{
if (self.preferencesWindow == nil)
{
self.preferencesWindow = [[BBPreferencesWindowController alloc] init];
}
[self.preferencesWindow showWindow:self];
NSLog(#"%#", self.preferencesWindow.window.isVisible ? #"YES" : #"NO");
}
The window is showed fine the first time I click the NSMenuItem (although the NSLog line logs "NO"), but when I close the window and then try to re-open it by click on the NSMenuItem for a second time, the window won't open.
What am I missing?
Thanks!
Edit:
BBPreferencesWindowController doesn't have a custom init method. It does have a custom awakeFromNib (that gets called the first time)
- (void)awakeFromNib
{
[super awakeFromNib];
NSLog(#"Loaded!");
}

I found the reason why my BBPreferencesWindowController did not manage the window well: in the XIB, the File's Owner window outlet wasn't linked correctly.
Fixing this resolved all other problems as well.
Thanks for your help!

Related

Presenting modal dialogs from XIB in Cocoa: best/shortest pattern?

Below is my typical WindowController module for presenting a modal dialog (could be settings, asking username/password, etc) loaded from a XIB. It seems a bit too complex for something like this. Any ideas how this can be done better/with less code?
Never mind that it's asking for a password, it could be anything. What frustrates me most is that I repeat the same pattern in each and every of my XIB-based modal window modules. Which of course means I could define a custom window controller class, but before doing that I need to make sure this is really the best way of doing things.
#import "MyPasswordWindowController.h"
static MyPasswordWindowController* windowController;
#interface MyPasswordWindowController ()
#property (weak) IBOutlet NSSecureTextField *passwordField;
#end
#implementation MyPasswordWindowController
{
NSInteger _dialogCode;
}
- (id)init
{
return [super initWithWindowNibName:#"MyPassword"];
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self.window center];
}
- (void)windowWillClose:(NSNotification*)notification
{
[NSApp stopModalWithCode:_dialogCode];
_dialogCode = 0;
}
- (IBAction)okButtonAction:(NSButton *)sender
{
_dialogCode = 1;
[self.window close];
}
- (IBAction)cancelButtonAction:(NSButton *)sender
{
[self.window close];
}
+ (NSString*)run
{
if (!windowController)
windowController = [MyPasswordWindowController new];
[windowController loadWindow];
windowController.passwordField.stringValue = #"";
if ([NSApp runModalForWindow:windowController.window])
return windowController.passwordField.stringValue;
return nil;
}
The application calls [MyPasswordWindowController run], so from the point of view of the user of this module it looks simple, but not so much when you look inside.
Set tags on your buttons to distinguish them. Have them both target the same action method:
- (IBAction) buttonAction:(NSButton*)sender
{
[NSApp stopModalWithCode:[sender tag]];
[self.window close];
}
Get rid of your _dialogCode instance variable and -windowWillClose: method.
-[NSApplication runModalForWindow:] will already center the window, so you can get rid of your -awakeFromNib method.
Get rid of the invocation of -[NSWindowController loadWindow]. That's an override point. You're not supposed to call it. The documentation is clear on that point. It will be called automatically when you request the window controller's -window.
Get rid of the static instance of MyPasswordWindowController. Just allocate a new one each time. There's no point in keeping the old one around and it can be troublesome to reuse windows.

Can't close a window- why?

I have an application in which a window should be opened and closed when a checkbox is clicked on or off in a separate window. I can open it, but can't close it. I define a NSWindow in the windowControllerObject and try to close the NSWindow. The relevant code is:
buttonController.h
#interface buttonController : NSWindowController
{
NSButton *showAnswerBox;
infoWindowController *answerWindowController;
}
- (IBAction)showAnswer:(id)sender;
#end
buttonController.m
- (IBAction) showAnswer:(id) sender
{
if ([sender state] == NSOnState) {
if (!answerWindowController) {
answerWindowController = [[infoWindowController alloc] init];
}
[answerWindowController showWindow:self];
}
else {
[answerWindowController hideWindow];
}
}
infoWindowController.h:
#interface infoWindowController : NSWindowController {
IBOutlet NSWindow * infoWindow;
}
- (id) init;
- (NSWindow *) window;
- (void) hideWindow;
- (void) tsSetTitle: (NSString *) displayName;
#end
And in infoWindowController.m:
- (NSWindow *) window
{
return infoWindow;
}
- (void) hideWindow
{
[[self window] close];
}
The window opens, but it won't close. I've tried several variations, including orderOut on the infoWindowController. I'm sure I'm missing something dumb- what is it?
In IB, the only way I can even get the windows to open is if 'Open at launch' checked- shouldn't I be able to open them programmatically without that?
NSWindowController already defines a window property. You have effectively overridden the getter of that property by implementing your own -window method. The setter, though, is still the inherited version.
So, assuming you have connected the window outlet of the controller to the window in the NIB, the inherited setter is being called. That allows the inherited implementation of -showWindow: to work to show the window. But your -window method will return nil because the inherited setter does not set your infoWindow instance variable.
Get rid of your separate infoWindow property and getter. Just use the inherited window property and its accessors.
If you use NSWindowController it's better to use it's close method:
- (void) hideWindow
{
[self close];
}
or just:
[answerWindowController close];
But your code is also valid, just make sure that your [answerWindowController window] is not nil. If you load your window from xib you should initialize your window controller with the name of this xib: answerWindowController = [[AnswerWindowControllerClass alloc] initWithWindowNibName:#"YOUR WINDOW XIB NAME"];.
Also check that "Visible at launch" is unchecked for your window (it seems that it doesn't).

Can't get UITouch event from UIButton

Problem:
I can't get my UIButton to send my Class an Event call
Here is what I tried first:
I made a class which extends UIViewController, called Viewer
#import "Viewer.h"
#import "MainScreen.h"
#implementation Viewer
- (void)viewDidLoad
{
MainScreen* main = [[MainScreen alloc]init: self];
[main show];
[super viewDidLoad];
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end
then:
I created a class called MainScreen, 2 IBOutlets - singlePlayerButton and view
Viewer is an instance of UIViewController
buttonPressed has the return type of IBAction
MainScreen:
#import "MainScreen.h"
#import "Viewer.h"
#implementation MainScreen
{
IBOutlet UIButton* singlePlayerButton;
IBOutlet UIView* view;
Viewer* viewer;
}
- (id)init:(Viewer*) theViewer
{
self = [super init];
viewer = theViewer;
return self;
}
- (void)show
{
[[NSBundle mainBundle] loadNibNamed:#"MainScreenLayout" owner:self options:nil];
[viewer.view addSubview:view];
}
- (IBAction)buttonPressed:(id)sender
{
NSLog(#"A button was pressed!");
}
#end
Then I created an NIB called MainScreenLayout.xib
Here is what it looks like:
As you can see, I have four buttons but the only one that is connected to anything is the Single-Player button I also have the UIView connected to view.
Now, I linked the action to the Single-Player button like this:
buttonPressed is my method that I want to have called when the button is pressed, so I ctrl-clicked on First Responder and then clicked on the circle next to buttonPressed and then dragged it on top of the Single-Player button. Then the next dialog window popped up and I clicked on Touch up Inside. Here is what the Received Actions window looks like:
Now, I thought at this point it should work. I ran it on my iPhone Simulator and pressed the button.
Just so there is no question of whither or not I'm competent, here is a picture of me actually clicking the button:
I got no output whatsoever. Then I went to my code and looked at the method. This is what it looks like:
as you can see, the oval is not filled, meaning that the method is not paired. So naturally I thought it was being overridden by another method. So here is what the method looks like now:
- (IBAction)aVerySpecificMethodNameForMyButtonListener:(id)sender
{
NSLog(#"A button was pressed!");
}
Then I went back to my NIB window, unpaired my Single-Player button and then repaired it to aVerySpecificMethodNameForMyButtonListener. Now this is what it looks like:
Then I ran it again and still nothing when I pressed the button. Then I went back to the code:
(insert frustration here)
At this point I restarted Xcode and the Simulator and looked over everything again and it still didn't work.
This is what I tried next which seems about a billion times more simple:
- (void)show
{
[[NSBundle mainBundle] loadNibNamed:#"MainScreenLayout" owner:self options:nil];
[viewer.view addSubview:view];
[singlePlayerButton addTarget:self action:#selector(aVerySpecificMethodNameForMyButtonListener:) forControlEvents:UIControlEventTouchUpInside];
}
I replaced with my show method with the one above. The only thing different is the last line. I disconnected my method from the Single-Player button in the NIB. Then I ran it and I got excited for a second because I got some output. Here is what I got:
(lldb)
Very helpful output C debugger, as always. I spent the next few hours on SO trying to find out what I'm doing wrong. I've tried countless examples and none of them worked.
Does anybody see what I'm doing wrong here? Is it just Xcode being flakey?
Thanks for reading!! All relevant answers are welcomed!
UPDATE:
I also tried changing the declaration of UIButton to a property in the Header file:
#import <Foundation/Foundation.h>
#import "Viewer.h"
#interface MainScreen : NSObject
{
IBOutlet UIView* view;
Viewer* viewer;
}
#property (weak, nonatomic) IBOutlet UIButton* singlePlayerButton;
- (id)init:(Viewer*) theViewer;
- (void)show;
- (IBAction)aVerySpecificMethodNameForMyButtonListener:(id)sender;
#end
Thus here is what I changed in MainScreen.m to comply:
#import "MainScreen.h"
#import "Viewer.h"
#implementation MainScreen
- (id)init:(Viewer*) theViewer
{
self = [super init];
viewer = theViewer;
return self;
}
#synthesize singlePlayerButton;
- (void)show
{
[[NSBundle mainBundle] loadNibNamed:#"MainScreenLayout" owner:self options:nil];
[viewer.view addSubview:view];
[singlePlayerButton addTarget:self action:#selector(aVerySpecificMethodNameForMyButtonListener:) forControlEvents:UIControlEventTouchUpInside];
}
- (IBAction)aVerySpecificMethodNameForMyButtonListener:(id)sender
{
NSLog(#"A button was pressed!");
}
#end
Now there are no variable declarations inside of the MainScreen.m file, I moved them all to the Header. The circles next to singlePlayerButton and view are still filled in but the bubble next to the method is not. I ran the code again and got the same output.
So I'd have put this in a comment because its really just a suggestion however I dont have the rep yet so sorry to get your hopes up, but I believe the touch event's scope is only inside the UIView in which it happens. When the touch up event gets triggered it doesn't get passed to the UIView's handler (MainScreen class) and so the event's listener method does not get called... but that you are able to link it in the interface builder to the button is baffling to me, so i could be way off.
Anyways, where I think you should go next is to make your MainScreen a UIView object and see what that does for event handling of the buttons in the view.

Display window again after closing it and setting Focus

I have two short questions:
How is it possible to reopen a window after closing it.
How do I set the focus on the window I open.
It would be nice if someone can help me!
Here is the source code I have so far but I don't know how to go on:
The method calling the WindowController:
- (IBAction)openPreferences:(id)sender
{
[NSApp activateIgnoringOtherApps:YES];
if (NULL == preferences)
{
preferences = [[PreferencesController alloc] initWithWindowNibName:#"Preferences"];
}
[preferences showPreferenceWindow];
}
This is the Header of PreferencesController:
#import <Foundation/Foundation.h>
#interface PreferencesController : NSWindowController <NSWindowDelegate>
- (void)showPreferenceWindow;
#end
This is the Main of PreferencesController:
#import "PreferencesController"
#interface PreferencesController()
#end
#implementation PreferencesController
- (void)windowWillClose:(NSNotification *)notification
{
}
// display the preference window
- (void)showPreferenceWindow
{
[self.window makeKeyAndOrderFront:NSApp];
// TODO: window should be focused and if the user press the close button it should be displayed again
}
- (void) dealloc
{
[super dealloc];
}
#end
NSWindow has this handy method - (void)setReleasedWhenClosed:(BOOL)releasedWhenClosed. Set that to NO and you window can open unlimited amounts of times. As to your focus issue: It should already be working to bringing your window to focus.
But since you are using a preference window: may I suggest DBPrefsWindowController it is pretty old but it still works today.

firstResponder in NSViewController

I've got two classes. ManagingViewController, a subclass of NSViewController, and ViewController, a subclass auf ManagingViewController. In Viewcontroller I've got a NSTextField which I want to become the firstResponder, but I didn't manage that.
So it is nearly the same like the Chapter 29 in Hillegass' book Cocoa Programming for Mac OS X (Download of the book's examples) except of an NSTextField which is set to firstResponder.
Can anybody point me to the correct way?
You need to set the text field as the first responder by using -[NSWindow makeFirstResponder:].
Since this is an NSWindow method, it only makes sense after you’ve added the corresponding view to the window, i.e., after you’ve added the view as a subview inside the window view hierarchy. In the book’s example, this happens when you set the view as the content view of the box inside the window. For example:
- (void)displayViewController:(ManagingViewController *vc) {
// Try to end editing
NSWindow *w = [box window];
…
// Put the view in the box
NSView *v = [vc view];
[box setContentView:v];
// Set the first responder
if ([vc class] == [ViewController class]) {
[w makeFirstResponder:[(ViewController *)vc myTextField]];
}
}
This assumes ViewController exposes a getter method called -myTextField.
You can make this more generic by having your view controllers expose a method that returns the object that the view controller recommends as the first responder. Something like:
#interface ManagingViewController : NSViewController
…
- (NSResponder *)recommendedFirstResponder;
#end
#implementation ManagingViewController
…
- (NSResponder *)recommendedFirstResponder { return nil; }
#end
And, in your concrete subclasses of ManagingViewController, have -recommendedFirstResponder return the object that should be the window’s first responder:
#implementation ViewController
…
- (NSResponder *)recommendedFirstResponder { return myTextField; }
#end
Having done that, you can change your -displayViewController: to something like:
- (void)displayViewController:(ManagingViewController *vc) {
// Try to end editing
NSWindow *w = [box window];
…
// Put the view in the box
NSView *v = [vc view];
[box setContentView:v];
// Set the first responder
NSResponder *recommendedResponder = [vc recommendedFirstResponder];
if (recommendedResponder) [w makeFirstResponder:recommendedResponder];
}
Have you tried [[myTextField window] makeFirstResponder:myTextField]; ?
simple. Goto you xib file in interface builder. right click the first responder field. it will show the connection , remove the connection and connect it to the desired responder. let me know if this works