Showing an NSPanel on demand - NSPanel not showing? - objective-c

Hey guys,
I thought this would be an incredibly simple thing to do, but it's proving trickier than I thought.
I want to be able to show an NSPanel that displays a circular progress bar and the name of the thing that's being processed.
So, I made an NSPanel containing those controls in IB, then I created a subclass of NSWindowController. Created outlets for the controls and linked those up.
However, when I try using this code to display the NSPanel, nothing happens:
[[[self controller] msgSubject] setValue:[msg subject]];
[[[self controller] window] setLevel:NSFloatingWindowLevel];
[[self controller] showWindow:self];
[[[self controller] window] makeKeyAndOrderFront:self];
[self controller] is a method that lazily instantiates the NSWindowController subclass. I tried adding the call to makeKeyAndOrderFront: in vain, but the panel still isn't popping up.
I tried debugging and what I found is that when -initWithWindow: is called, the NSWindow that's passed in as an argument has all zeroed out instance variables, leading me to believe there's some sort of IB linking issue going on here.
Any ideas? I'm guessing I missed something really obvious, but I can't for the life of me figure out what it is.

Please post the contents of the [self controller] method so we can begin to see exactly how you're creating and using the NSWindowController subclass. Are you creating it using the initWithWindow: method directly, or is that method (which is the designated initializer) being called indirectly from one of the other init methods? If you are calling it directly, that doesn't really make sense to me as you said you already created the window in nib file itself. If on the other hand, it's being called indirectly by -initWithWindowNibName:, then it would help to see that code.
NSWindowController's are primarily used/set up in 2 different ways. One way is to create an NSWindow programmatically, and then create the NSWindowController subclass and feed that window in as the window the controller will manage. The second, and more frequently used method, is to create a nib file that houses the window, which is what it sounds like you're trying to do. In this method, you generally use the -initWithWindowNibName: initializer. As long as you pass in the proper nib name (generally without the ".nib" part of the filename), and that nib file can be properly found at runtime, and the file's owner in this nib file is set to be the custom NSWindowController subclass, and the window outlet of this subclass is properly hooked up to your window, then you should be all set.
Might want to double-check to make sure that the nib file you want to load is actually in the app bundle. (I've occasionally forgotten to add it to the target so at runtime the nib file couldn't be located and so the -initWithWindow: method would always show a nil parameter).

Related

AppDelegate instantiated last on launch

I'm very comfortable with writing iOS apps, but OS X unexpectedly seems somewhat alien.
Here's the question upfront (read on for context):
When my application launches using the .xib set in the Main Interface field under the deployment info of my apps target, why does the AppDelegate get instantiated after the ViewControllers?
Context (no pun intended):
The reason I ask is because I'm using Core Data (spare me any heckling for this decision), and typically you keep a pointer to the MOC (Managed Object Context) in AppDelegate. One of my controllers is trying to get this MOC instance variable but the AppDelegate instance isn't around yet and therefore my app doesn't present data just after launch.
The AppDelegate and the two ViewControllers are in the .xib. The VCs are hooked to views inside a split view. They're trying to use the MOC in viewDidLoad to make queries. They are accessing the AppDelegate like this:
let delegate = NSApplication.sharedApplication().delegate as AppDelegate
let moc = delegate.managedObjectContext
This will crash as the .delegate property of the sharedApplication() returns nil.
I tried making an NSWindowController from the .xib in applicationDidFinishLaunching and removing the .xib from the Main Interface field, but then applicationDidFinishLaunching doesn't get called at all.
I've ensured that all the connections in IB for from the Application and the Files Owner (NSApplcation) delegate IBOutlets to the AppDelegate have been made.
UPDATE - 31/03/15
Stephen Darlington's answer below offers a good solution for my/this case. And as I understand it's actually better to setup the MOC in the way he's suggested.
If a correct answer arrives that explains why the AppDelegate is being instantiated at some later time in the launch process, I'll mark it correct instead of Stephen's. Thanks Stephen!
The "easy" solution would be to have managedObjectContext create a MOC if one doesn't exist (i.e., change it from a property to a method). That way which ever code gets there first the stack will be available.
(I'll spare the lectures about both creating the Core Data stack in the app delegate and accessing the app delegate like that!)
Here's another option without having to subclass NSApplication:
Don't put your view controllers in the .xib that you set as the Main Interface, just have the Main Menu (menu bar), AppDelegate and Font Manager in there.
Then make your view controllers in other .xibs.
Then in the applicationDidFinishLaunching method init your view controllers from their .xib files.
I also faced this issue with setting up Parse. To get around it, I simply subclassed NSApplication and set it as the Principle class in the Info.plist. In your NSApplication subclass, override the init methods and initialise Parse or anything else you need to, there.

How to use NSWindowController class

I am sorry if this seems trivial, but I am sure its a reasonable question to ask here.
I worked a lot around the NSWindowController class, and it seems the only way to get it
to work fully (for my purpose), is by creating a new xib-file along with it.
My question is, would it be somehow feasible to work with MainMenu.xib and the NSWindowController class and an instantiated object controller, to get interaction with the windows' content. So far without xib the only code segments getting executed are within awakeFromNib. The purpose being, I want to save xib-file space, complexity and have it easily integrate with a bigger project. Just fyi this is not a document-based project.
Should I choose a different subclass of NSObject other than NSWindowController? Or is it not possible?
The code required to run for the class to be working fully is as follows:
- (void) tableViewSelectionDidChange:(NSNotification *)notification
{
NSInteger selectedRow = [logsTableView selectedRow];
if ([directoryList containsObject:[directoryList objectAtIndex:selectedRow]])
{
NSString *logContent = [NSString stringWithContentsOfFile:[directoryList objectAtIndex:selectedRow]
encoding:NSUTF8StringEncoding
error:NULL];
if (logContent != NULL)
{
[logsTextView setString:logContent];
} else
{
[logsTextView setString:#"No permission to read log"];
}
}
}
NSWindowController usually wants to create the window it controls, which means you either need to give it a XIB file that contains the window to create or override the various window creation methods to customize the window in code. So it's probably not feasible to use an already-instantiated window from a different XIB with your NSWindowController.
That said, I almost always create a a XIB and an NSWindowController subclass for every window in my apps. Even the preferences window gets its own window controller class. The only exception would be extremely simple windows, but even now I'm struggling to think of a good example.
Your method isn't being called because window controller instance isn't set as the table view's delegate. The typical pattern here is to create your window in a XIB, set your window controller as the custom class of the File's Owner object, and then hook up the table view's delegate and dataSource outlets to File's Owner. This makes your window controller the table view's data source and delegate, and the connections will be established automatically when the XIB is loaded.

NSWIndowController creating Outlets sequence of nib-loading

I'm having big trouble with a nib containing a NSWindowController (as Files Owner) plus ArrayControllers bound to the Files Owner in the nib.
The subclassed NSWindowController seems ok, but the NSObjectControllers, NSArrayControllers and 'custom' NSObjects within the nib are all set to 0x0 -- after a nib load.
All controllers and objects are properly 'bound' in IB. The NSObject subclss has a initWithCoder. Is there further requirements for inits or awake. I should not that ALL inits are essentially doing nothing, but return super. Thats because all referenced objects are set (bound) within the nib.
It seems it has something to do with the sequence of nib loading. And, I was led to believe that IBOutlets were available BEFORE awakeFromNib, following a [super initWithWindowNibName:name];
Based on the docs,
In Mac OS X v10.5 and later, setting an outlet also generates a
key-value observing (KVO) notification for any registered observers.
These notifications may occur before all inter-object connections are
reestablished and definitely occur before any awakeFromNib methods
of the objects have been called.
Moreover,
If you need to configure the objects in your nib file further at load
time, the most appropriate time to do so is after your nib-loading
call returns. At that point, all of the objects are created,
initialized, and ready for use.
Am I wrong to believe that self = [super initWithWindowNibName:name] is my nib-loading call?
Some of my awakeFromNib are called only when the Window is shown. ie. [myWindowController showWindow: nil]; My window is opened as a sheet that is making a blocking call, thus it is not easy to access those arraycontrollers after a showWindow.
Am I wrong to believe that self = [super initWithWindowNibName:name]
is my nib-loading call?
-[NSWindowController loadWindow] is the method that actually loads the nib. Not that you should ever call it directly; -[NSWindowController window] is the one you should call instead, because the latter invokes -[NSWindowController windowWillLoad] and -[NSWindowController windowDidLoad] as well.
The documentation for NSWindowController states:
Although a window controller can manage a programmatically created
window, it usually manages a window in a nib file. The nib file can
contain other top-level objects, including other windows, but the
window controller’s responsibility is this primary window.
The upshot of this is that nothing in the nib will be loaded until the window in it is loaded.
This is a "gotcha" that used to get me a few times!

How can I initialize a custom GUI class?

I'm developing an iPad app.
Now I ran into the following problem. I created a "Custom Class" for a UIScrollView. So in my nib file I have added a default UIScrollView and set the Custom Class to MultiSelectView (which is the custom class I created). Screenshot here.
So in my MultiSelectView I have added some methods that I want to use. This works!
The issue is that I'm wondering how I can initialize certain objects that I need in these methods. It seems like - (id)initWithFrame:(CGRect)frame {} is not called, and neither is - (void) viewDidLoad {}.
Thus, is there a way to initialize a custom (GUI) class?
When you unarchive a view from a .xib file, it is not sent -initWithFrame:, as you've noticed. Instead, it's sent -initWithCoder:.
So, if you've got any UIView subclass in a .xib file that needs custom initialization, you'll need to override -initWithCoder: as well as (or instead of) -initWithFrame:.
Looks like you need initWithCoder, it is called when object is loaded from NIB
Or, better, awakeFromNib. The difference is that awakeFromNib is called when all outlets are connected.

Reasons for an IBOutlet to be nil

What are the reasons why an IBOutlet (connected) could be nil?
I have one in may application which is always nil, even if I recreate everything from scratch (declaration and control).
It could be that your nib is messed up, but I find a common reason is having two instances where you think you only have one, and the one you're using in your code is not the one you connected.
If you've also defined a loadView method that creates the view, it is possible based on how you initialize it. If you initialize it using alloc-init and the nib name is not the same as class name, then you can have a case where the outlet is nil. But Chuck's answer seems more reasonable to assume.
One reason I just got stung by: If the nib file is not included in the target resource files for some reason (like you had the targets unchecked when you added it to the project), Xcode doesn't throw an error but all the outlets from that nib are going to be null...
One possibility:
Suppose the IBOutlet container is a singleton object with a function like:
+ (singletonObject*) sharedInstance {
if(!gGlobalSingletonPointer) {
gGlobalSingletonPointer = [[singletonObject alloc] init];
}
return gGlobalSingletonPointer;
}
You create the singleton object "on demand" if it doesn't already exist.
You save a global pointer to it, as you create it, in that function.
If you also instantiate such an object in InterfaceBuilder, and connect its outlets, this object will be created without sharedInstance being called. If you subsequently call sharedInstance, a new object is created (sans IBOutlet connections).
The solution is to update the global pointer in singletonObject's init or awakeFromNib function.
Are you using a UINavigationController?
If so, open your MainWindow.xib in IB and make sure that your root controller's nib name is set correctly in the Attributes Inspector.
Why would this not be set correctly? One reason is the 'rename' refactoring doesn't update this, and then the internals won't find the nib with which to wire your UI. Or you renamed the nib yourself, and didn't update this field.
Are you doing something unusual with File's Owner? If you're not in one of the situations where the nib is loaded automatically (main nib loaded by application or nib loaded by view controller, document, or window controller), then you have to load the nib programmatically.