Using separate .nibs and getting logic out of App Delegate - objective-c

I have a document based app using the standard template. I have two auxiliary panels in Main Menu.xib, and my main logic is currently in the App Delegate, mainly through an IBAction in App Delegate triggered by a button on one of my panels. Everything works fine, but I know it should be organised better.
I have implemented a Preferences panel as suggested by Hillegass in Chapter 12. So:
Create a custom controller called AppController containing instance
of PreferenceController. This is instantiated in Main Menu.xib
Custom PreferenceController class which is subclass of
NSWindowController. This loads the Preferences.xib
Preferences panel created in Preferences.xib
Before I get too far in the app’s development, I want to be sure I’m organising things the right way.
I want to move my main logic out of App Delegate, possibly into App Controller. I want App Controller to be in charge of showing and hiding the various panels, and I want each panel to have its own .xib.
I have created two more subclasses of NSWindowController and made them ivars of the AppController, alongside the PreferencesController eg. Panel1Controller & Panel2Controller.
My problem is that interface builder is not letting me connect an IBAction in AppController to a button on one of my panels. It only lets me connect to the .xib file’s owner, i.e. Panel1Controller in the case of Panel1.xib.
If I put the logic in Panel1Controller, how do I get at one of the other panels (say Panel2Controller?) in order to hide it?
Am I going about this the right way?
Getting very confused….
Any help much appreciated!
Regards,
John

Just for simplicity sake I'd move all the nib elements controlled by the NSWindowController sub-classes out of the main nib and into nibs with the same name as the (NSWindowController) sub-classes that control them. DON'T expose IBOutlets or IBActions in the sub-class headers (they should be in a class extension ("#interface MyWindowController ()") in the source file for that sub-class.
Also, is the AppController a 2nd app delegate? Probably not what you want (there can only be one); you should merge its logic into the existing app delegate if that's the case.

I just came across this method.
This seems to do away with NSWindowController altogether, and make the AppController the file's owner of both .xibs. This way IB allows you to create outlets in AppController for each window, and contain actions.
I have created a very simple, two-window app using this method that hides one window when a button on the other is pressed. Before I go away and re-organise my main app, I want to be sure I'm doing this the correct, standard way, if there is one?
This page contradicts this method, by saying one window = one .xib + one NSWindowController subclass.
If you do it the latter way, how can one window talk to another, when you can't create outlets/actions in the AppController? Actions implemented in a window's NSWindowController class can't see outlets of another window, so how can they communicate?
This seems like pretty standard, basic stuff and yet I cannot find any sources which say which way is correct/best practice.

Another method I have read about here mentions using Notifications.
I'm still wondering though - which is the most common "accepted" method of loading two or more windows in separate .nibs and getting them to talk to each other? I'm surprise this info has been so hard to find.

If you're going to follow this pattern, separate AppDelegate and AppController, then your MainMenu.xib should not contain any window objects of any kind...it should just contain the application menu. Each additional window (NSWindow/NSPanel, etc.) gets its own .xib and its own NSWindowController.
There are two ways to assign references to your properties (IBOutlets) and methods (IBActions): 1) programatically, 2) via Interface Builder. Let's cover the second method!
To be able to wire things up from Interface Builder (IB) you will need a reference to the target object inside IB. For the MainMenu.xib file, this gets setup automatically: the MainMenu.xib contains an "AppDelegate" Object reference. The Object reference exposes the properties and methods in the AppDelegate class that are prefixed by the "IBAction" and "IBOutlet" macros. I write Object (with a capital O) because it is a widget available in the "Object Library" in IB.
You can easily create an instance of a custom objects inside a .xib file (via IB) by dragging an "Object" widget from the Object Library into your .xib. Then set the Object's class to that of your custom class. Once you've done this, the IBActions and IBOutlets in your custom class object will be available in IB. [Note: one thing to remember when doing this, is that when you load the xib, the object will be instantiated automatically. No need to alloc and init from within AppDelegate...you still have to call showWindow: on it].
As you mentioned, another approach is to simply have all of your additional .xib files owned by the AppController. That would be convenient, but it also gets 100% away from the architecture that you were trying to follow in the first place. In other words, if you're going to follow that style, why not just skip the separate AppDelegate and AppController in the first place, and just stick with the former (which would then be a Controller and Delegate).

Related

What are the correct connections for App Delegate and Application

Unannounced, the 2 notices in the image below were discovered while searching for why -applicationShouldTerminate: is not being called in AppDelegate.m on quit (Cmd+Q). It worked some time ago.
Hovering over the two yellow triangles reveals:
NSObject does not have an outlet named delegate.
The action 'terminate:' is not defined on 'NSObject'.
Xcode is not showing errors or warnings and the app builds.
-applicationShouldTerminateAfterLastWindowClosed: IS called within AppDelegate.m when the red dot of the window is clicked.
My experience with Cocoa is thin (learning). I've compared the connections for File's Owner, Application and App Delegate in 2 other projects, and think a missing window outlet might be the cause. The notices above point to something else.
I would like to make sure the connections are correct as a first step. How do I repair this?
Edit: Add image of Main Window Controller connections
Edit 2: Add image of File's Owner connections
In the main NIB of an application, the two placeholders, File's Owner and Application, both end up referring to the same object. That object is the application object, the sole instance of NSApplication or a custom subclass of it. The Application placeholder is always holding the place of the application object because that's its purpose. The File's Owner placeholder is holding the place of whatever object is specified as the NIB's owner when it is loaded at run time. When Cocoa loads the main NIB at application startup, it specifies the application object as the owner. For other NIBs, File's Owner will likely be some other object.
However, Interface Builder doesn't know that a given NIB is the main one. So, it treats those two placeholders independently.
I don't know why Interface Builder has taken to setting the class of the Application placeholder to NSObject. It should really be NSApplication. That's why Interface Builder doesn't realize that the application object has a delegate outlet and an action method -terminate:.
As it happens, the class of File's Owner is properly set to NSApplication.
So, there are two ways to fix this:
Set the class of the Application placeholder to NSApplication or, in the rare case that you're using a subclass, that subclass.
Disconnect those connection from the Application placeholder and connect them to File's Owner instead. This is the way that the standard project templates do it.
For any given main NIB, you should probably standardize on using one or the other but not both. That is, one or the other should have no connections. That way you avoid conflicting or redundant connections.

Cocoa - explanation of view handling

I'm learning Cocoa at the moment, and have followed 'generic' tutorials on getting a basic form with a button and label.
With the .xib, I've added an 'Object' (instance of NSObject subclass), and have also created a ViewController class, which I connect to my view by setting Custom Class to ViewController. I then code up my ViewController.h and .m files adding a pressedButton method, and a label (myLabel). This all works OK (ie. the label updates when the button is pressed).
My question is: what am I actually doing with this process in C++ terms (a language I'm more familiar with)? As I understood it, my 'Object' (set to class ViewController) represents an instance of the .xib file (may be wrong here), and with this set to the ViewController class, I'm able to make outlets and actions in ViewController.h/.m, but I'm still not sure what's really going on behind the scenes.
Lastly, the XCode template provides an AppDelegate class 'free'. Given I'm managing my controls via my ViewController class, what would this file/object be used for - is it for application specific things that do not relate to the view itself, or is it also used to manage the controls on the form too (like you see in some tutorials I think)?
I hope, I understood the first part of your question well. The .xib (xml verison of nib file) does not represent a class or object. It is rather used to create a view object with all its subviews (each an object) and link it properly to your view controller.
It is most common and automatically generated that way, that the underlying view within your nib file corersponds to self.view (from your view controller's point of view).
You could access each view created by the nib file by navigating though the subview hierarchy of self.view. Alternatively you could define tags within nib/IB (Interface builder) to access individual view objects more easily.
"Just for your convenience" the IBOutlets were introduced. You can define an IBOutlet in the interface of your view controller and link it using IB to the dedicated object that is created using the xib file.
I am saying "Just for your covenience" because it is not really neccessary, considering other ways of addressing your view objects. But it is more than convenient and therfore strongly recommended and stimply the standard way of doing it.
You could even add further views programmatically to a form/view that was created using IB. You can change properties of those views programmatically.
The view and the subvies are created in that very moment when your view controller is initialized with a nib file using the initWithNibName:bundle: method. Then the nib file (xib these days) is opened, its content is parsed and interpreted and all views are created and added to their superviews respectively and their properties and action: selectors etc. are set accordingly. Again, the whole process is designed for your convenience. You could even create all the views etc. programmatically - witout any nib file at all. The loadView method of your custom view controller would be the appropriate place of dong so.
Second question:
AppDelegate is designed for application wide actions etc. If you can manage everything fine within your view controllers then you would not need to change anything on the AppDelegate.
It is often used as a container for variables or functions of global character (which are then properties and members of the app delegate object). Sometimes you may see it neccessary to override some of the AppDelegates methods like application:didFinishLanuncing or applicationDidBecomeActive.
Strictly spoken, UIApplicationDelegate is no class that you subclass. It is a protocol that you implement. (Very much like interfaces within Java - sort of overcoming the lack of multiple inheritance that you are familiar with from C++).

bind the managedObjectContext outlet for a controller using Interface Builder?

I'm trying to set up a fairly simple view that presents a table to the user. This table is connected to an Array Controller, which I want to use to retrieve records from Core Data. For some reason, I can't seem to connect the 'managedObjectContext' outlet to anything else in my app. When I created my project, there was a property generated in my app delegate which returns the MOC I need, but I can't hook it up in Interface Builder, even after prepending "IBOutlet" to the declaration. Image below showing the available connection on both ends:
http://yada.im/uploads/image/screenshot/1108/7efebc90ca7187a537da9ae003dd5f3e.png
I'm sure that I'm missing some simple step here but I can't tell what piece of glue code I'm supposed to write that will allow me to hook this up more easily. For reference, I've tried dragging a line from the controller's moc outlet to every single source I could think of, and changed the "File's Owner" class to that of my application. Stumped here!
Typically in the template provided by XCode the managedObjectContext comes along with AppDelegate.
You have to bind the managedObjectContext reference of the array controller to the managedObjectContext in AppDelegate.
For this you have to make an object of AppDelegate inside the xib i.e., if its not already present.(Drag an object placeholder from your object library and make its class as AppDelegate)
This makes AppDelegate visible for binding inside that xib.
Next step is actually binding the managedObjectContext. Select your array controller and go to bindings inspector. In the parameters section select App Delegate from the drop down and check on "Bind to".
Fill the "Model Key Path" field with self.managedObjectContext. Now you will find the connection in the connections inspector also.
UPDATE:
The process of creating a new AppDelegate object is to be done only if it is not already present in the main nib file (but the stub generated always has the AppDelegate object in the main nib file).
For a non main nib file, if we follow the above approach, a new AppDelegate object will be created which won't be the NSApplication's delegate. Even though this can be solved by connecting delegate outlet of the application object proxy provided in each nib, the AppDelegate object still won't be the same.
The result is two different managedObjectContext talking to the same store. Although this might appear to work properly when the changes are saved at each step, this is not what we want.
To get the right AppDelegate object, i.e. the one used in the main nib file:
-instead of creating a new AppDelegate object, bind the managedObjectContext of the array controller directly through the application to its delegate. In other words the object to bind to will be the application object and the key path used will be self.delegate.managedObjectContext.
The way to add objects of your entity depends on the specific logic you want to implement.
The generic and easy solution would be, binding the fields for input to the array controller like you might have done for the table and then hooking up the array controller methods to the buttons inside the sheet.
Another option is sub-classing NSArrayController and over-riding the super class methods like add: to write your code (for opening your slide sheet maybe) before calling the super class method, [super add:sender] . Don't forget to specify this sub-class of NSArrayController as the class of your array controller in the xib.

How to use classes to control windows in Objective-C

I have been making applications for Mac with Objective-C for about a year now but due to not really understanding how to use classes properly I have only ever used the 'AppDelegate' files. I want to start using classes as soon as possible because from what I understand it's very bad practice to clump it in to one class. Basically, how do I have two windows, each controlled by it's own class. I understand how to make objects similar to NSString or something but don't understand how to have classes that control windows etc.
Thanks
Edit: Basically I want to know how to split up my application in to classes.
If I understand you correctly then you need to create individual controller classes sporting their own IBOutlets and IBActions and hook these up to your UI elements. To split up an existing application into smaller classes requires some knowledge of Object Oriented programming.
Alternatively, you might benefit from reading this (or a similar) book:
'Cocoa Programming for Mac OS X' by Aaron Hillegass.
Try looking for NSWindowController in the docs. You create a custom subclass of NSWindowController and a xib file for it. In the xib file, make sure you set the class on the File's Owner to your custom subclass, and make sure its window outlet is connected to the window in the xib. If all that sounds totally foreign, head for the books! =)
Then, in the code where you want to bring this window onto the screen, you create an instance of your custom subclass and associate it with the xib, like so:
MyCustomWindowController *controller = [[MyCustomWindowController alloc] initWithWindowNibName:#"myxib"]
[controller showWindow:self];
The xib loading system will hook up all your custom outlets and actions on the new controller, and you can show it or do other wonderful NSWindowController things.

Connecting delegate classes in Objective-C

I've got two controls in my Interface Builder file, and each of those controls I've created a separate delegate class for in code (Control1Delegate and Control2Delegate). I created two "Objects" in interface builder, made them of that type, and connected the controls to them as delegates. The delegates work just fine. My problem is, I need to share information from one delegate to the other delegate, and I'm not sure how.
What is the best way to do this? Combine the two delegates into one class, or somehow access a third class that they can both read? Since I'm not actually initializing the class anywhere in my code, I'm not sure how to get a reference to the actual instance of it (if there is an actual instance of it), or even access the "main" class that the project came with.
You can add outlets from either delegate to the other delegate. There are two ways to add an outlet to an object in IB (assuming you're using Xcode/IB version 3.0 or later:
If you have not generated the code for your delegate classes yet, select the desired delegate, then open the "Object Identity" tab in the IB inspector. Add a "Class outlet" of type NSObject. You should then be able to set this new outlet to the other delegate. Of course you will have to generate the code for your delegate class and add the generated source files to your Xcode project before you can load the nib.
If you've already generated the code for the delegate class (or added an NSObject to your NIB and set its Class to an existing class in your Xcode project), add an instance variable to the delegate class:
IBOutlet id outletToOtherDelegate;
As long as your Xcode project is open (as indicated by the green bubble in the lower-left of your NIB window), IB will automatically detect the new outlet and allow you to assign it to the other delegate object in your NIB.
Cocoa automatically connects these outlets at NIB load time. Once awakeFromNib is called on instances of your delegate objects, you may assume that all the other objects in the NIB have been instantiated and all outlets have been connected. You should not assume an order on calls to awakeFromNib, however.
I think you can create outlets on each one and cross-bind them so that they each have the same data all the time. If there's one model object they need to share, that's pretty tidy. I don't actually know how to do this; I think I saw it in an iPhone tutorial one time!
I don't have my Mac in front of me currently since I'm at work, but would it be possible to bind an instance of one delegate to a member of the other delegate? This would be similar to binding an NSArrayController to a member of another controller class, for example.
However, depending on what the delegate classes are doing, if the tasks are similar I would probably just combine them into once class. That would eliminate the problem altogether.