How to access NSArray instance belonging to MyDocument in another xib? - objective-c

In my document application I subclassed an NSArrayController which I referenced in MyDocument.xib bounding its content to File's Owner.entries.
entries is an NSArray I expose as an attribute of MyDocument class this way:
#interface MyDocument : NSDocument {
NSArray *entries;
}
-(NSArray *)entries;
-(void)setEntries:(NSArray *)newEntries;
#end
This works perfectly fine. Good.
Now I have designed another .xib, CSVEntries.xib which I open via a NSMenuItem through a NSWindowController and again, this works smoothly.
I can't figure out how to make this new window display the content of the MyDocument.entries instance in an NSTableView.
I have tried many things, but the problem basically is that any NSArrayController I put on CSVEntries.xib can not be bound to MyDocument.entries: CVSEntries.xib File's Owner is referring to something else not MyDocument (which is logical, I guess).
I also tried to add a copy of the NSArray instance to the NSWindowController which opens CVSEntries.xib, but since the window get's instantiated only when the user clicks on the menu, I ended up having an empty array.
I am searched around the Internet but could not find an appropriate answer, I just would like to figure out the proper way to approach the issue, I am sure two windows can communicate between eachother, maybe accessing their common parent (NSApplication instance)?
All newbie questions, I know :)
Update
I think I figured out where to look but still not how to. I think I have to make sure second window File's Owner is MyDocument but in the NSWindowController initWithWindowNibName:owner: I can't understand how to specify the MyDocument instance as the owner. Specifying it only in the xib file File's Owner seems not enough.
Update 2
Uhm...I am starting to think that I need to have two NSWindowController instances both instantiated by my NSDocument subclass (default MyDocument). With those in place maybe both the NSWindowControllers will have access to the MyDocument.entries NSArray and the two windows xib files will be able to be bound to File's Owner.entries.
Can someone confirm this? Thanks.

I reply to my own question here, basically stating that I have realized what I was asking is very much dependent on what the File's Owner is bound to. The design I implemented was not correct and I am therefore redesigning it to achieve my purpose.
Thanks everyone anyway, even no answers are an answer :)

Related

Using separate .nibs and getting logic out of App Delegate

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

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.

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.

How to programmatically access an NSTableView that was created in Interface Builder?

I am new to Objective-C and Cocoa programming (coming from a background of C/C++ development years ago on other platforms). I am writing an application to download remote data on a recurring basis (i.e. every X number of seconds), parse through it, sort/filter it into an NSArray, and display/update said data in an NSTableView. After reading a few books, a lot of Apple OS X Reference material, and experimenting I have managed to implement everything (the remote data download, parse/filter logic, in-memory storage, etc.) except actually updating the NSTableView with the data.
I am not sure if I am just missing something obvious or just how my application should be laid out following the MVC concept, or if Interface Builder's lack of actual code generation is just not what I am used to, but I cannot seem to determine how I can programmatically access/manipulate the NSTableView that was created in Interface Builder.
I tried (in Interface Builder) dragging a NSObject instance of my NSArray-based object in, where-then I can connect my NSTableView's Outlet/datasource, but this results in an another instance of my NSArray-based object (not connecting the NSTableView to my existing, programmatically-declared and instantiated object). Likewise, I thought to set my NSTableView's datasource programmatically, but I have not been able to determine how I can programmatically refer to the NSTableView object stored in the .xib/.nib file other than via a Tag (for which I have not been able to determine what object to call the viewWithTag: method from, after setting my NSTableView's Tag value in Interface Builder).
Any suggestions, advice, or guidance would be greatly appreciated. This feels like one of those things that will be very simple (and once I have it working in front of me, it will make a lot more sense), but I just cannot seem to get a starting point/example working.
You need to attach an instance variable in your table's controller class to the table in interface builder. Declare a table in your class like this:
IBOutlet NSTableView* myTable;
...
#property (nonatomic, retain) IBOutlet NSTableView* myTable;
And be sure to synthesize it.
In the connections tab of the info window in interface builder, connect your controller's new outlet to your table. Then when your view is loaded from the XIB, this outlet will be connected.
Hope that helps some.

IKImageBrowserView appears to not bind properly

my question relates to Interface Builder and an IKImageBrowserView not implementing bindings as I would expect.
I have a fairly long key path to get to an NSArrayController, the contents of which I want to display in the IKImageBrowserView.
This is the key path I am binding an NSTextField's Display Pattern binding to:
currentOrder.imagesArray.unvalidatedImages.arrangedObjects.#count of the AppDelegate.
This works fine and gives me the number of unvalidated items in the array of images belonging to the current order, which is what it is supposed to.
When I attempt to bind the following key path to the IKImageBrowserView's content: currentOrder.imagesArray.unvalidatedImages.arrangedObjects of the AppDelegate, Interface Builder gives no error but instead acts as if I hadn't entered anything. When I click back to the inspector, the bindings key path is blank again.
It does however log the following to the Console:
Ignoring exception related to working with bindings: NSUnknownKeyException, [<NSCustomObject> addObserver:<IKImageBrowserView ...> forKeyPath:#"currentOrder.imagesArray.unvalidatedImages.arrangedObjects" ...] was sent to an object that is not KVC-compliant for the "currentOrder" property.
My AppDelegate implements currentOrder as an #property retained and all sub-keys are also #properties. I can be sure that these properties are KVC-okay because the NSTextField above is able to read changes without a problem. Interestingly enough the IKImageBrowserView's selectionIndexes is able to bind to ...unvalidatedImages.selectionIndexes, it's only the content that can't.
I have implemented a workaround whereby I have placed an NSArrayController in my nib file and bound the Content Array to ...arrangedObjects then bound the IKImageBrowserView to the array controller but would be very happy to have a neater solution, or at least to know whether I am doing anything wrong.
Thank you!
Did you try implementing the methods of <IKImageBrowserDataSource> in your app delegate, and using the _dataSource connection instead of a binding? That works for me, and is how the Apple tutorial has you do it.
You basically only need to implement – numberOfItemsInImageBrowser: and – imageBrowser:itemAtIndex: to provide the data, which isn't too bad. The slightly trickier part is to implement an <IKImageBrowserItem> class to wrap your data, but even that doesn't require massive effort. The tutorial linked above should help a lot.