Referencing variables on NSApp delegate Cocoa - objective-c

So I'm a little confused here. I have a Cocoa app, in the appdelegate header I'm declaring a NSDrawer that I've connected in Interfacebuilder and whose contentView I'm setting programmatically depending on the context. The contentviews contain Buttons that are connected to various functions in the Appdelegate.
#property (strong) IBOutlet NSDrawer *theDrawer;
When my app starts app, and I inspect it in the Debugger "theDrawer" is not nil and correctly instantiated by the Interfacebuilder. In the
Now if the user clicks any button it turns out that references to [[NSApp delegate] theDrawer] will be ignored because theDrawer is nil. which doesn't make sense to me. I tried fix this by specifically setting the delegate when the app launches.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[NSApp setDelegate:self];
}
I've checked that self.theDrawer is not nil at that point. But even after I set the delegate explicitly, any future calls to [[NSApp delegate] theDrawer] are nil.
How can I make sure to access variables on my App delegate? My understanding was that calls to NSapp delegate will return a unique instance of the app.
It seems that when a user clicks on a button that this creates a new thread and NSApp delegate will return nil for all variables.
Any help appreciated

The application delegate is properly set in main in main.m which you'll find in the "Supporting Files" folder of the project. Generally it's embedded in NSApplicationMain() which I believe references NSMainNibFile in the plist and actually has the main nib own the app delegate object instance. However Apple's not totally clear on how all that magic works. Nonetheless you can't set the delegate in applicationDidFinishLaunching -- that's a delegate function!
But if you're using a NSMainNibFile and a non-document application, the app delegate is likely being set to an object in your main NIB... in that NIB, the "File's Owner" is also the application delegate class, and the other outlets are non-nil within [NSApp delegate] because it's the main NIB file's owner. From the NSMainNibFile you can create outlets in the App Delegate class, because it's the file's owner.
If you create a second NIB, there are issues accessing the delegate. You don't want to create a object, because that isn't the same object as [NSApp delegate]. You can use the Application placeholder object and bind with a keypath of "application.delegate." But you can't create outlets because the app delegate can't be the file's owner.
But that's irrelevant, because if you have a second NIB that you're creating outlets for, they belong in a custom viewcontroller or windowcontroller subclass that you've declared is that file's owner. Even if that second NIB is loaded immediately, well then the app delegate should instantiate a controller instance to load and own the NIB, if you want outlets, it must be a custom subclass.
Apple sort of breaks this pattern by making the app delegate class the owner of the main menu "window" in MainMenu.xib in non-document applications... but that is because the menu window's a bit special. Anything you do outside of MainMenu.xib is going to need a custom controller class to have outlets.
Additional discussion in this answer about app delegate instances.

Related

Why not enforce strict singleton application delegate object to use in NIBs?

I just ran myself round in circles, all coming down to having instantiated an app delegate object in a secondary NIB that wasn't the NSMainNibFile. Amazing how having two app delegates kicking around means you have separate managedObjectContexts.
Here's a thought-- could I make my application delegate class a singleton? And safely instantiate it in more XIBs? What would that break?
Also, there are some mentions on stackoverflow that [[UIApplication sharedApplication] delegate] is a "singleton" but it doesn't appear that UIApplicationDelegate protocol guarantees that, nor is the superclass UIResponder a singleton, either. So could I shoot myself in the foot in this regard on iOS as well?
[edit] Looks like you could nil out the delegateClassName in UIApplicationMain for iOS and have the main NIB load the delegate object, so you could create the App Delegate object pattern seen on OSX, if using a main NIB.
[edit2] Screenshot of what MainMenu.xib looks like for a new non-document application. The project gets created with this object, app delegate class gets created with a window property. The issue is getting that nice handy object in other NIBs, and that object being the same as [NSApp delegate]
Just do this in your existing App Delegate (There will only be one!)
// In the header file
+ (AppDelegate*) sharedInstance;
// In the body
+ (AppDelegate*) sharedInstance {
return (AppDelegate*) [[UIApplication sharedApplication] delegate];
}
Then anywhere you want to refer to your App Delegate, you can simply use [AppDelegate sharedInstance] followed by the property or instance method you want to call.
You shouldn't be using the app delegate for stuff to do with core data anyway. So making it an enforced singleton is pointless.
Ideally nothing should need to reference back to it at all.
Okay, after the question having been voted up, and then voted down to zero because of who-knows-why, I've continued to investigate my own answer. I think it's useful to make your app delegate classes true singletons so you can't cause headaches with NIBs. I can't see why it would be harmful. And I think if your app has a single user interface, it's not unreasonable to have the app delegate own the core data stack for all NIBs. However, the recommended design pattern would be to then have each window or view controller be passed the ManagedObjectContext pointer, and to access the MOC through the File's Owner placeholder rather than using an App Delegate object.
Yet on the other hand, things are different with the "Shared User Defaults Controller" singleton, which gets a special object in every NIB. We don't have to pass every controller a pointer to it so that every view can access it. It's just omnipresent. The app delegate is different. There's no "Shared App Delegate" object in every NIB. Yes, there are reasons to never talk to the app delegate in NIBs, but that's not an answer to the question.
So, an answer.
Singleton design patterns:
Covered long ago by Apple in this deprecated reference document-- Creating a Singleton Instance.
Turns out what I want my application delegate class to implement is the "strict" implementation, rather than having a factory method which could create other objects of the app delegate class. The one different feature here is having [NSApp delegate] be the master pointer rather than an app delegate class function.
The strict implementation has to override allocWithZone for my application delegate class (as alloc calls allocWithZone).
+ (MYAppDelegate*)allocWithZone:(NSZone *)zone
{
if ([NSApp delegate] == nil) return [super allocWithZone:zone];
return [NSApp delegate];
}
- (MYAppDelegate*)copyWithZone:(NSZone *)zone
{
return self;
}
Init just returning [super init] is fine, so it needs no override.
Seems to work. I'll update this if not.
[update] I have also been investigating NIB loading using NSBundle's loadNibNamed:owner:topLevelObjects: -- but it appears that I'd get an array back with a new app delegate object, even from that method. The method allows getting pointers to the top-level objects in the NIB without having otherwise created outlets for them. Still seems the best method to get an app delegate object in a XIB other than MainMenu is to use something like the code above.
[another update] Why it could be harmful: According to the the section "Top-level Objects in OS X May Need Special Handling" in this document, there's good reason for me to believe that, even with ARC, this answer of mine increases the retain count on [NSApp delegate], but heck if I feel okay doing a bridge and a release on the app delegate in dealloc for the window/view controllers that have a top-level object for the app delegate. Plus that means code outside the app delegate class.

Why is my custom delegate method not being called?

I have a viewController with 4 buttons (HomePage) and then a TabBarController with 3 viewControllers.
One of the TabBarController's viewControllers I want to be used as a way to get back to the "HomePage" via a tabBar icon. I have associated a custom class that I created called "HomeViewController" to that viewController. See diagram below
HomeViewController .H file.
I have created a protocol with a method "returnToHomepage"
HomeViewController .M file
As soon as the view is loaded it calls the delegate.
In my HomepageViewController .H file I have made sure that the file adheres the protocol.
HomepageViewController .M file
I instantiate an instance of HomeViewController and set delegate to self but
returnToHomePage method never gets called! Not sure what I'm missing...
I think that your're calling the delegate method before the delegate is set.
When you call alloc-init on the controller, it initializes and ViewDidLoad is called,... and THEN you set the delegate... so this
[self.delegate returnToHomepage];
is called before
homeVC.delegate = self;
The HomeViewController you're creating in viewDidLoad is not the same one that's actually being presented onscreen. You'll need to access it with your UITabBarController's viewControllers method and set it's delegate that way.

Cocoa - awakeFromNib is not called

I have an MainMenu.xib, and AppController is its file owner. I added -(void)awakeFromNib method which worked fine. Now, rounds of fixings down the road awakeFromNib stopped being called, and I can't figure out why. It owns the xib, so it should be called when it is unarchived. What's going on?
EDITED:
Well, I renamed awakeFromNib to something, and called that from init... that worked. Still confused as to why awakeFromNib is not. I also have a +(void) initialize method in there, could that be messing something up?
- (id)init {
self = [super init];
if (self) {
[self something];
}
return self;
}
-(void)something {
NSLog(#"yup");
}
Setting the class name of the File's Owner in the nib is only so you can tell Xcode what object's outlets and actions to show you so you can hook things up. It doesn't affect what object is actually the File's Owner when the app runs and the nib gets loaded.
The MainMenu nib's File's Owner is always the application object, no matter what class name you set for the FO in Xcode's inspector. Setting it to any class name but NSApplication[1] is wrong.
When you run your app, you should find error messages in the Console about any outlets or actions of the AppController that you tried to connect. They couldn't be connected because the application object doesn't have them.
Change the class name back in the nib editor, and create your AppController as a custom object in the MainMenu nib.
Well, I renamed awakeFromNib to something, and called that from init... that worked.
That means that init is getting called, which means you're calling it. That's a valid alternative to creating it in a nib, though you shouldn't override awakeFromNib if it's not in a nib or owning one.
Your choice: Continue creating the AppController using alloc and init, or remove that code and create it in the MainMenu nib instead.
[1]: Or, if you've subclassed NSApplication and changed the principal class of your app bundle to be that subclass, the name of that subclass.

Update UI from another Class Method - Cocoa

I would like to update the UI in my application from the AppDelegate, but whenever I call it as so:
Controller *object = [[Controller alloc] init];
[object methodHere];
It doesn't seem to update the UI. What am I doing wrong here? I have put in a NSLog to see if it was being called, and it is. Here is a sample project that shows the error.
Edit: Can someone just show me what to change to the project I provided. I just don't know what to type into my project so that I can change the value of a simple NSTextField from another class.
When you write [[Controller alloc] init], you are not accessing the Controller object that is in your nib. You are creating a new Controller object that is unconnected to anything else in your application.
Remember, every Controller object is not the same any more than every NSArray is the same. Just because you made one Controller in your nib that's connected to an NSTextField does not mean some random Controller that you just created shares that controller's connections.
What you need to do is give the delegate a reference to the Controller that's in the nib.
This is really simple, and Chuck's comments basically explain what you need to do, but I will lay out the code explicitly for you. In testAppDelegate.h:
#interface testAppDelegate : NSObject <NSApplicationDelegate> {
NSWindow *window;
// You can make an IBOutlet to any kind of object you
// want; it's just a way for you to get a reference
// in code to an object that has been alloc'd and
// init'd already by the xib mechanism.
IBOutlet Controller *controller;
}
Then go into your xib in InterfaceBuilder and hook up that outlet from your Test App Delegate object to your Controller object (these objects are already present in the xib).
In testAppDelegate.m:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// This is the key:
// _Don't_ alloc/init a new controller object. The
// objects in your xib are allocated and initialized
// by virtue of being in that file. You just need to
// give your AppDelegate a pointer to it, as above.
[controller setTextValue:#"hello"];
}
It's being called all right, but it's not connected to the interface. There should be a view controller of some sort defined in your appDelegate.h file, call the method on that object instead.
Update for more detail:
One way you could pull this off would be to simply save the Controller when you originally create it (and not release it until later.)
Simply put your own controller object into your .h file
Controller* myController;
And when you create the new view controller you want to flip to, simply set myController to reference that object, and later when you want to update the UI, simply call
[myController methodHere];
A bit clumsy, but it works. Just don't forget to release myController when you're done with that view.
The other idea I'd suggest looking into would be to alter the method you're passing to your delegate. That is, instead of having the method as
-(returnType)callDelegateToDoSomething;
put it in as
-(returnType)callDelegateToDoSomething:(id) sender;
You call the new method the same way, but your controller should automatically pass itself as an argument. Then, inside the method, simply use
[sender methodHere];
and it should hopefully work. (You may need to play around with it a little. I'm not an expert on delegates or the sender argument, but it's worth a shot.)

Pointer to NSWindow Xib after loading it?

In my code below, CustomWindow is a subclass of NSWindow.
CustomWindow *window = [[CustomWindow alloc] init];
if (![NSBundle loadNibNamed:#"NibName" owner:window])
[window center]; // doesn't work
How do you get a pointer to control your XIB after you load it so you can do things such as centering the NSWindow (I mean the serialised one that resides inside the XIB)?
What am i doing wrong here?
You should be using an NSWindowController subclass. NSWindowController is specifically designed to do exactly what you want to achieve and solves several problems that you will run into if you load the nib directly using the methods of NSBundle. You generally should always use an NSWindowController subclass to manage windows.
Create a subclass of NSWindowController:
#interface MyWindowController : NSWindowController {}
#end
#implementation MyWindowController
- (id)init
{
self = [super initWithWindowNibName:#"MyWindow"];
if(self)
{
//initialize stuff
}
return self;
}
//this is a simple override of -showWindow: to ensure the window is always centered
-(IBAction)showWindow:(id)sender
{
[super showWindow:sender];
[[self window] center];
}
#end
In Interface Builder, set the class of File's Owner to be MyWindowController and connect the window outlet of File's Owner to the window object in your nib.
You can then display the window by doing this:
MyWindowController* controller = [[MyWindowController alloc] init];
[controller showWindow:self];
In my code below, CustomWindow is a subclass of NSWindow.
CustomWindow *window = [[CustomWindow alloc] init];
if (![NSBundle loadNibNamed:#"NibName" owner:window])
[window center]; // doesn't work
How do you get a pointer to control your XIB after you load it so you can do things such as centering the NSWindow inside the XIB?
“centering the NSWindow inside the XIB” makes no sense (you would center it on the screen), unless you mean centering the NSWindow object that is inside the xib, in which case, why are you creating another NSWindow (CustomWindow) object outside of the xib?
Remember that a nib (or xib) is an archive of objects. If you want to use a window that you have in your nib, you need to create an outlet to point to that window, set the class of the File's Owner to be the class where you've added the outlet, hook up the outlet in IB, and appoint the object with the outlet as the File's Owner by passing it to the owner: argument. That object, as the owner, will then be responsible for working with the window. It may be (usually is, in my code) the same object that loads the nib.
Also, init doesn't work on NSWindow; you must use initWithContentRect:styleMask:backing:defer: or initWithContentRect:styleMask:backing:defer:screen:. Using init would only be valid if you've implemented init yourself in CustomWindow, and used one of those two selectors for the [super init…] message.
You probably don't want to make your window the File's Owner. Normally you would pass self or some controller object there. Then if self or that controller object has a CustomWindow IBOutlet, it will get hooked up when you call loadNibNamed:. Check out this post for example code.
A XIB is a container for objects, it's not equal to a window. You can't center a XIB, you can only center a window contained in a XIB.
Also, the objects in the XIB are created when you load it. You don't pass an object as owner that then stands in for one of the objects in the XIB, you instead use IBOutlets to get references to the new objects created when loading the XIB and then you can interact with them.
The File's Owner object is a special object in XIBs, as it's the only object that is not created and that you can specify by passing it to loadNibNamed:owner:. It's your gateway between the XIB-created objects and your application.
Usually, the owner object is some kind of controller class. Set the File's Owner's class in Interface Builder to your controller class, then define some IBOutlets in the class, they will show up in Interface Builder on the File's Owner and you can connect your objects in the XIB to them.
Finally, when you pass your controller object to loadNibNamed:owner:, Cocoa will connect your IBOutlets to the newly created objects and you can use them to interact with them, e.g. to center a window in your XIB.