I've an application that holds a main window with a list of items, and from that window an undetermined number of windows can be opened. Each of those windows can hold several instances of a model object, with those instances listed in a drawer.
I started my project by making the (main window) list of items extend NSDocument and each other window extend NSWindowController. But functionally the main window is used once every blue moon, despite being the window that should pop-up when the users start the application, and the windows that extend NSWindowController are the ones used extensively by the user and also the ones that end up holding my "document".
Because of that I'm now having problems implementing methods such as New, Open and Save - I find myself writing a lot of code the manuals say should be implemented by the super class.
Because I'm at a cross-roads I wonder how I should implement my application. Should I re-factor my main window into a class that extends NSWindowController and launch it from the xib that holds main menu, or should I keep things the way they are and just override newDocument, openDocument, etc in order to get the desired functionality?
Just to help with the mental image, my application works like MSN - I've a main list with several items on it (the contact list on MSN), when I double click on an item I open a window (you open a chat to a user). My app goes one step further by keeping several instances of a model object for each "chat" window and each instance will be accessible by a table in a drawer.
You would subclass NSDocument in order to handle a type of document. It may be a general type, such as any image, or a specific type, such as PDF, but you need to create an NSDocument subclass to handle the type, since NSDocument itself doesn't know how.
I'm not sure why people subclass NSWindowController. It seems to work well enough as it is.
I've an application that holds a main window with a list of items, and from that window an undetermined number of windows can be opened. Each of those windows can hold several instances of a model object, with those instances listed in a drawer.
I started my project by making the (main window) list of items extend NSDocument and each other window extend NSWindowController.
That's wrong. If anything, your secondary windows are document windows. The primary window is not.
Make a new controller for the primary window. When the user opens an item in that window, tell the document controller to open the relevant file. You probably don't need to subclass NSWindowController for this.
If the items don't correspond to files, then your application is not document-based, and you should not pretend it is: Don't use NSDocument or NSDocumentController at all in this case.
Related
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).
Note: Using Objective-C, Cocoa, and Xcode.
At first, I did all my programming in the AppDelegate and had all user-interface elements such as windows in the same '.xib' (nib file). This worked great at first, but then as my application became more advanced with more "features", everything became extremely cluttered and the file too long for my liking.
I'm now trying to progress. I'm wondering how I should (properly and efficiently) go about having a multi-window project? My plan was to have a .xib file for every window, and put only necessary things in the AppDelegate. I would then have a core file for all necessary calculations and such to be used in my application and a Main Controller file to control outlets and actions from all windows in my app. However, I'm quite new to Objective-C and have been running into SO many issues and problems trying to set up Window Controllers and stuff.
Am I even on the right path? Am I doing it wrong? How should I manage a multi-window user-interface application in Xcode?
Thanks in advance.
For multiple windows, I think it's best to use an NSWindowController for each, with it's associated xib file for the window. I'm not sure what you mean by "a Main Controller file to control outlets and actions from all windows" -- each window controller will have outlets to its own window. You can't connect outlets across multiple xib files. You can have one window in the MainMenu.xib file that you get by default when you create a project, and use that to launch other windows perhaps, but it's hard to offer any more specific advice without knowing how all your windows relate to each other.
After Edit:
If you want to open another window, have a menu item's action method be something like this:
self.controller = [[WindowController alloc] initWithWindowNibName:#"WindowController"];
[self.controller showWindow:nil];
Here, I have a property called controller to keep a strong reference to the new window controller. If you don't do that, the controller will be deallocated, so if you have multiple windows, that you want to show at the same time, you'll need properties to hold on to them.
I'm working on an app that (among other things) uses UIImagePicker to grab an image from the device once the user has selected the SourceType by tapping the appropriate button. Different sections of the app will need to use this functionality, as well as the variable holding the image information once selected. When I first started the project I had all of my code to do this in a single class named ViewController. I'm now working on moving the individual sections of the app into their own classes, but I'd like to be able to have them all use the UIImagePicker functionality from a central location.
Along with the necessary UIImagePickerController methods and protocols, I have a method that presents a view with buttons for each available SourceType. Each of these buttons then send a message to methods to show the appropriate picker (or the camera). Once an image is selected, it is applied to a variable for use by the different sections.
I wanted to get suggestions on the best way to approach this before I went to deep down the wrong rabbit hole.
Thanks!
If a lot of your classes use this functionality, you can create a superclass (itself being a subclass of UIViewController).
This class will expose some method to launch the process you described, and some other to gather the information collected.
If you don't want to use inheritance, or you already to with another class, you can also create a separate class responsible for this process.
This class, which is not necessary a UIViewController, has to be instantiated and then called the same way the superclass described above.
I would like three menu items on the menubar with the keyboard shortcuts cmd-1, cmd-2, cmd-3. This I know how to do.
Each menu item would open up a different window (win1, win2, win3).
I want it so that only one instance of each window is permitted to be open at any one time (i.e. only one win1, one win2, etc).
How is this best approached?
If the windows in question are in the MainMenu.nib it's simple; attach the action to -makeKeyAndOrderFront: on each window.
If, on the other hand, they are not in MainMenu.nib, as is more likely the case if your application is structured in a sane fashion; things get a little more complicated. Long story short; you need to attach the menu item actions to appropriate methods on some manner of controller object (most likely your application delegate, although any controller that "sees" all the required nibs will do); and then have the controller in question send a similar message to its window.
This is generally a sensible approach, as you can have smaller controller objects attached to your windows that also act, if appropriate, as data sources for the various views in the windows in question.
It also allows for lazy loading of the windows, which is, at last count, a Good Thing™.
If this is some manner of document-oriented (not necessarily document-based) application, and the windows display some attribute of the currently selected "document" or piece of data; subclassing NSWindowController and loading the window controllers in your MainMenu.nib is probably a good place to start.
Note: If the objects responsible for controlling the windows live in the responder chain, they don't even need to be referenced in MainMenu.nib; you can just attach the appropriately-named IBActions (e.g. openDetailsWindow: or the like) used to open the windows to the virtual FirstResponder object. (Simply add the selectors to its list of known methods, and you're golden.)
Reedit: To make the window not appear in the windows menu, you can call [window setExcludedFromWindowsMenu:YES].
To check/uncheck the menu item is a bit tricker, as it requires your window controller actually knowing about the menu item; but as long as this is the case, it's quite simple, again; call -setState:, with the relevant state names (NSOffState, NSOnState), for example from the delegate methods called when the window is shown/closed. (This could, again, be encapsulated "inside" the application delegate; if you for whatever reason don't want your MainMenu.nib to contain the window controllers.)
I am currently working through the famous "Cocoa Programming for OSX" by Aaron Hillegaas.
In Chapter 12 he wants me to create an about window using
[BOOL] successful = [NSBundle loadNibNamed:#"About" owner:self];
which, by itself, works perfectly well. However, I am using the garbage collector and since I do not retain a pointer to that about window, it is garbage collected and thus disappears after a second or two. It works perfectly well if garbage collection is disabled.
Is there a way to create a window without holding a pointer to it and without having it eaten by the garbage collector?
You can retain the window with CFRetain, or use NSGarbageCollector's disableCollectorForPointer:. However, you can easily introduce a memory leak. Make sure whichever action you use to close the window also releases the window.
If the sender passed to the close action inherits from NSView, it will have a window property that you can use to get a pointer to the window.
However, this is not how Cocoa is designed to work. In Chapter 12 of Hillegaas' book, he has this to say:
When sent showWindow: for the first time, the NSWindowController automatically loads the nib file and moves the window on screen and to the front. The nib file is loaded only once. When the user closes the [window], it is moved off screen but is not deallocated. The next time the user asks for the [window], it is simply moved on screen.
If you deallocate the About window, your app will either crash or appear not to respond the second time someone opens it.
Edit: An alternative (but one that doesn't give you practice in loading nibs) is to add the About window an an NSWindowController to the main nib (make sure you uncheck the About window's "Visible At Launch" attribute). This makes a mess of Main.nib, but can be done entirely in Interface Builder. Connect:
the About controller's window outlet to the About window
the About controller's showWindow: action to the About menu item
if you've your own close button in the About window, connect it to the window's performClose: action.
As for how advisable this course is, Apple has this to say:
A very simple application might be able to store all of its user interface components in a single nib file, but for most applications, it is better to distribute components across multiple nib files. Creating smaller nib files lets you load only those portions of your interface that you need immediately. Smaller nib files results in better performance for your application. They also make it easier to debug any problems you might encounter, since there are fewer places to look for problems.