Proper usage windows - objective-c

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.

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.

Xcode - I can't run the whole methods from AppDelegate

I'm using custom URL schemes to open my app, then get the link and run in a method. But I can't really run the method.
For example, I can't load web views or change labels or text fields. So how do I load web views and change labels?
AppDelegate.m:
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
if (!url) { return NO; }
NSString *URLopen= [[url host] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
ViewController *vc = [[ViewController alloc]init];
vc.URLschemeLink = URLopen;
[vc URLscheme];
return YES;
}
ViewController.h:
#interface ViewController : UIViewController<MFMailComposeViewControllerDelegate> {
NSString *URLschemeLink;
}
-(void)URLscheme;
#end
ViewController.m:
#implementation ViewController
#synthesize URLschemeLink;
-(void)URLscheme {
//for example:
label.text = #"Hello"; //nothing will happen
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.google.com"]]]; //Nothing will happen
NSLog ("Method Works Perfect"); //will happen
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Title:"
message:[NSString stringWithFormat:#"%#", URLSchemeLink]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
//UIAlertView will work perfectly and show the URLschemeLink from AppDelegate.
}
ok How to load the label/webview anyway?
i tested to pass a bool named runURLscheme (=true) from app delegate.
then i wrote in ViewDidLoad:
if(runURLscheme==true) {
[self URLScheme];
}
but this will not work, it will not run the URLscheme method.
How can i load the labels/webviews anyway?
The main views associated with view controllers are generally lazy loaded. Just because the view controller is initialised, it doesn't mean it's loaded its main view yet. Your views are only safe to access once viewDidLoad has been called.
Generally speaking, this pattern works well:
Store your data independently of any views that display it.
When your custom URL scheme runs, update this data.
Have a method on your view controller that updates its views from this data.
Call this method from viewDidLoad.
If there's a chance this data will be updated after your views have already been loaded (e.g. if you are receiving data from the network), then use notifications or KVO to call your view updating method again.

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

How to Display and Manage a Simple Application-Modal Dialog in Cocoa

I am not sure I am doing things the right way, or if I have it all hacked up.
I have a really simple test application (not document-based) I created for learning how to work with application-modal dialogs in Cocoa applications.
With the application project "TestModalDialog", I have a simple MainMenu.xib with the default view and a button, "Show Dialog", I added. I created a second XIB called TheDialog.xib that has "Cancel" and "OK" buttons. That xib has as its owner a class derived from NSWindowController called "TheDialogController"; the window outlet and delegate are connected to the controller.
Selection of "Show Dialog" on the main view will launch the dialog. Selection of "Cancel" or "OK" will dismiss the dialog. Here is the pretty simple code:
// TestModalDialogAppDelegate.h
// TestModalDialog
#import <Cocoa/Cocoa.h>
#class TheDialogController;
#interface TestModalDialogAppDelegate : NSObject <NSApplicationDelegate>
{
NSWindow *window;
TheDialogController* theDialogController;
}
#property (assign) IBOutlet NSWindow *window;
- (IBAction)showDialog:(id)sender;
#end
// TestModalDialogAppDelegate.m
// TestModalDialog
#import "TestModalDialogAppDelegate.h"
#import "TheDialogController.h"
#implementation TestModalDialogAppDelegate
#synthesize window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
theDialogController= [[TheDialogController alloc] init];
}
- (void)dealloc
{
if(nil != theDialogController)
[theDialogController release];
[super dealloc];
}
- (IBAction)showDialog:(id)sender
{
if(nil == theDialogController)
{
NSAlert* alert= [NSAlert alertWithMessageText:#"Dialog Error" defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:#"The dialog controller was not allocated."];
[alert runModal];
return;
}
NSInteger result= [NSApp runModalForWindow:[theDialogController window]];
// Do something with result....
}
#end
// TheDialogController.h
// TestModalDialog
#import <Cocoa/Cocoa.h>
#interface TheDialogController : NSWindowController
{
// BOOL userClickedCloseOrOk; // Removed based on answer.
// Should declare a common define - just being lazy.
NSInteger userClickedOk; // Added based on answer.
UInt16 timesShown;
}
- (IBAction)showWindow:(id)sender;
- (IBAction)closeDialog:(id)sender;
- (IBAction)okDialog:(id)sender;
//- (BOOL)windowShouldClose:(id)sender; // Removed based on answer.
- (void)windowWillClose:(NSNotification*)notification; // Added based on answer.
- (void)windowDidBecomeKey:(NSNotification*)notification; // To set title when modal.
#end
// TheDialogController.m
// TestModalDialog
#import "TheDialogController.h"
#implementation TheDialogController
- (id)init
{
self = [super initWithWindowNibName:#"TheDialog"];
userClickedOk= 0; // Added based on answer.
// userClickedCloseOrOk= FALSE; // Removed based on answer.
return self;
}
-(void)dealloc
{
// Do member cleanup if needed.
[super dealloc];
}
- (void)windowDidLoad
{
[super windowDidLoad];
// Initialize as needed....
[[self window] center]; // Center the window.
}
// Does not show with runModalForWindow.
- (IBAction)showWindow:(id)sender
{
// Just playing with the window title....
++timesShown;
NSString* newTitle= [NSString stringWithFormat:#"Shown %d Times", timesShown];
[[self window] setTitle:newTitle];
return [super showWindow:sender];
}
// This method no longer used for this solution based on the answer.
//- (BOOL)windowShouldClose:(id)sender
//{
// if(!userClickedCloseOrOk) // The user did not click one of our buttons.
// [NSApp abortModal];
// else
// userClickedCloseOrOk= FALSE; // Clear for next time.
//
// return TRUE;
//}
// Added based on answer.
- (void)windowWillClose:(NSNotification*)notification
{
[NSApp stopModalWithCode:userClickedOk];
userClickedOk= 0; // Reset for next time.
}
// Note - the title will update every time the window becomes key. To do the
// update only once per modal session, a flag can be added. There might be a better
// notification to catch.
- (void)windowDidBecomeKey:(NSNotification*)notification
{
++timesShown;
NSString* newTitle= [NSString stringWithFormat:#"Shown %d Times", timesShown];
[[self window] setTitle:newTitle];
}
- (IBAction)closeDialog:(id)sender
{
//userClickedCloseOrOk= TRUE; // Removed based on answer.
//[NSApp abortModal]; // Removed based on answer.
//[[self window] performClose:self]; // Removed based on answer.
[[self window] close]; // Know we want to close - based on answer.
}
- (IBAction)okDialog:(id)sender
{
userClickedOk= 1; // Added based on answer.
//userClickedCloseOrOk= TRUE; // Removed based on answer.
//[NSApp stopModal]; // Removed based on answer.
//[[self window] performClose:self]; // Removed based on answer.
[[self window] close]; // Know we want to close - based on answer.
}
#end
I had trouble with the modality - before I put in userClickedCloseOrOk and tests, if the user hit the close button (upper-left red dot), the dialog would close but the modal session was still running.
I realize I could just leave the close-button off the dialog to start with, but with it there, is what I have demonstrated a good way to catch that scenario, or is there a better way? Or, am I doing something wrong to start with, which creates that problem for me?
Any advice would be appreciated.
NOTE - Code from the original example commented out and replaced with code based on the answer. Also added a new notification handler.
In the okDialog: and closeDialog: methods call close instead of performClose: to avoid the window calling the windowShouldClose: delegate method. That way you don't need userClickedCloseOrOk.
Also I think you want to be calling stopModalWithCode: instead of stopModal since in the app delegate you seem to be interested in the result. And you can call stopModal or stopModalWithCode: instead of abortModal because you are always in the runloop when you call it (abort is for when you are outside of the modal runloop, like in another thread or timer's runloop).
In windowShouldClose: you are doing an action (abortModal) when you should only be answering the question "should this window close". The windowWillClose: delegate method is where you should do actions if you need to.
Sheets are useful when there is one window and it tells the user they can't do anything with it until they complete whatever is in the sheet. Application-modal windows are useful when you have multiple windows that the user can't interact with until they complete whatever is in the modal window or where there is an error that involves the whole application but is not tied to the content of one window. In their HIG Apple suggests avoiding the use of Application-modal windows whenever possible.
I have actually just been struggling with the same problem and found this link :
Stopping modal when window is closed (Cocoa)

How do you implement the Method makeKeyAndOrderFront:?

I am making a new window open and would like to implement the method makeKeyAndOrderFront: for the window, i was wondering what code i would need to enter to do this.
Here is some of the code I've already got to open the window:
File 1 (The First Controller)
#import "PreferenceController.h"
#implementation PreferenceController
- (id)init
{
if (![super initWithWindowNibName:#"Preferences"])
return nil;
return self;
}
- (void)windowDidLoad
{
NSLog(#"Nib file is loaded");
}
File 2 (The Action Opening The Window)
#import "Prefernces_Delegate.h"
#import "PreferenceController.h"
#implementation Prefernces_Delegate
- (IBAction)showPreferencePanel:(id)sender
{
// Is preferenceController nil?
if (!preferenceController) {
preferenceController = [[PreferenceController alloc] init];
}
NSLog(#"showing %#", preferenceController);
[preferenceController showWindow:self];
}
The reason I am trying to do this is it has been suggested by a friend to solve a window opening problem.
You don't want to implement -makeKeyAndOrderFront:, you want to call it on your window in order to bring it to front and make it the key window. What does your showWindow: method do?
Somewhere after [preferenceController showWindow:self];:
[self.window makeKeyAndOrderFront:self];
or did you mean add a method to the controller?
// you should use a different method name, cause it's not the
// controller that is made key and ordered front.
- (void)makeKeyAndOrderFront:(id)IBAction {
[self.window makeKeyAndOrderFront:self];
}
Message makeKeyAndOrderFront is sent only after initiating the main event loop [NSApp run]. You may try to send the message from the main view by implementing the method viewWillDraw:
- (void)viewWillDraw
{
[window makeKeyAndOrderFront: nil];
}