How to share model object between view controllers? - xcode

I created a basic OS X application with a storyboard to just draw circles in a custom view. The main window contains a NSSplitViewControllercontaining two sub-views (content and side bar like Apple Pages or Numbers have). The content view is a custom subclass of NSView for drawing circles while the side bar view contains standard controls. Both should be bound to a model object which holds the properties like number of circles, diameter and so on.
As I understand both subviews have their own controllers in any case. How do have a data model object (let's call it Circles) which both controllers reference so I can hook up key-value observation for redrawing my custom view on changing the controls' values?
My idea would be to create the model object in the common parent controller and pass it on to the children, but how to set that up in Interface Builder in Xcode 7.2?

Working off my comment. You can use the representedObject property of NSViewController to pass any object along to other view controllers. The one downside to this though is that the property is of type AnyObject? so making that work with Swift can be awkward. In your NSViewController subclass you can make a new property to store the data and give it the correct type, or you could make a protocol to define the computed property that serves as a wrapper around representedObject property.

Related

Cocoa OS X Bindings and Non-trivial data model

This project is to create an editor tool in Cocoa & Swift for Mac OS X that will edit a non-trivial data structure. A pared down schema looks like this:
Game
title : String
[ Room ]
Room
roomKey : String
roomName : String
roomDescription : String
[ Object ]
[ Exit ]
Object
objectDescription: String
Exit
destinationRoomKey : String
sourceRoomKey : String
The current implementation - the third go-around - has a single Document.xib file (the app is document based) and in that I'm hooking up a NSObjectController to the base game object loaded by the document, and an NSArrayController to the game objects array of rooms. There's an NSObjectController for the Room. I have not done the objects or exits yet.
The views are handled by a base root view controller, which swaps sub-views in and out as you go up and down the view hierarchy. On the view for the root game state, you click an "edit" button that slides in the table view for the list of rooms. Clicking a button in one of the room rows slides in a room detail view which has its own controller.
This is all working well enough. I have hooked up the object controller of the room so that it gets the selected room of the rooms array as its object, using Interface Builder bindings. I can do this because I have all the views, view controllers and data model controllers in the one XIB file.
However: now I am adding the game objects to this mix and the XIB file is getting very unwieldy. I really feel like I want to do this in separate XIB files, but when I tried that previously I was not able to hook up the controllers to each other. I tried manually writing code to load & save the data at the same time as the controllers had their view displayed and removed but this was flakey and error prone. So far the most elegant and robust result I have had is with this one XIB approach.
I looked at the programmatic API for binding but could not understand how to get it to work, or how to discover what the key path would look like. I suppose if it was possible to do the bindings programatically you could put the different parts in different XIB's and do the bindings at load time. But I could not find any examples of anyone doing that successfully and it seemed a road to madness.
At present I'm having no problems with Swift and its relations to Cocoa and Objective-C so if anyone has answers in Objective-C or Swift I'd be happy to hear them. I have not put Swift as a key word for this question as its not part of the problem.
I've seen the StackOverflow answer about hierarchical models, and its what I'm currently doing, so it doesn't help. The problem is that this approach gets unwieldy when there's several layers of master-detail.
I've also seen the StackOverflow answer about sharing controllers, and it was what I tried before and where I ran into the issue described there, that if you specify a controller object in a NIB it will get instantiated as an independent object. Hence why I have the huge-mega-NIB-of-death approach at present.
I could make the title of this question "cannot make programmatic bindings work" but I'm not sure that that is the right approach anyway.
Surely someone has done the job of making a non-trivial data model work with Cocoa before?
Your secondary NIBs should be view NIBs, their owners would be an instance of NSViewController or a custom subclass. That has a representedObject property. The NIB and its view controller class should be thought of as stand-alone, theoretically-reusable components. That is, in theory, that NIB could be used in multiple contexts to represent a particular kind of object. So, you should typically not want connections to other parts of your UI or their controllers, other than knowing what object this view is being loaded to represent.
Within the NIB, you can either bind to the File's Owner with a model key path that goes through representedObject or add an NSObjectController that binds to File's Owner's representedObject and then bind your views through that with controller key selection.
When you load such a secondary NIB, you would have to set its representedObject to the object it's supposed to represent, taken from the array controller's selection. This should be done in code, presumably the same code that decides it needs to load the NIB and does so.
If the design of your UI is such that a detail view needs to trigger a behavior that's best handled at a higher level — for example, a Room view needs to arrange for an Exit view be slid into the window, but not as a subview of its own view — the detail view controller should define a delegate protocol and implement a delegate property. For example, the Room view controller's delegate protocol might have a method -roomViewDidChangeSelectedExit:. The Room view controller would call that on its delegate, passing self. You would set some coordinating controller (perhaps the window controller) as the the detail view's delegate.
It's not clear to me if the "detail" views and the "master" views are visible simultaneously. That is, can the user change the object that the detail view is meant to show without backing up first? If so, there are a couple of approaches.
You could set up the bindings programmatically when the view is loaded. This would be the responsibility of the controller that loaded the detail view. It's not the responsibility of the detail view's controller. That doesn't have the higher-level perspective and knowledge to set up the binding. Anyway, you could do it like:
[detailViewController bind:#"representedObject" toObject:self.arrayController withKeyPath:#"selectedObjects.firstObject" options:#{ }];
Be sure to call -unbind: before the detail view controller is released.
The other way to do it is to simply observe the changed selection using a non-Bindings approach, and set the new representedObject in the code that gets triggered. For example, if your master view lets the user select an item in a table view, you would set up the table view's delegate (almost certainly already done) and implement -tableViewSelectionDidChange:. In that delegate method, query the newly-selected item and assign it to detailViewController.representedObject.

NSTableView & CoreData: Delete Object at clicked row

I am pretty new to Core Data and am currently working on a small (OSX) app that uses an NSTableView to organise objects. I would now like to delete a row/object with the click of a button on that targeted row.
I access the managed object within the table controller by calling [NSApp managedObjectContext] (still trying to figure out that dependency injection thing) but I can't easily delete an objectAtIndex: like I used to with the array (which has now been replaced by the core data stack, right?).
How do I identify the object to be deleted? And consequently, how can I cleanly remove it from the stack?
This is probably a really basic question but I couldn't find any resources on it. Bindings obviously don't work because the row does not get selected before the click occurs.
Any help is much appreciated!
Bindings would work, in that you could have the button's IBAction query the objectValue for the parent NSTableCellView. Once you have that objectValue, you could call the bound arrayController to delete the object, and then the cell/row would disappear.
So, if you have a tableCellView that has a delete button with an IBAction, within that IBAction, you could get the sender's superview, ensure it's an NSTableCellView, get the objectValue, and call [myArrayController removeObject:...]
As it says in the NSTableCellView class reference:
The objectValue is automatically set by the table when using bindings or is the object returned by the NSTableViewDataSource protocol method tableView:objectValueForTableColumn:row:.
This is actually a typical pattern with views in cocoa. objectValue or often representedObject are properties on the views that refer to the data model objects they represent, so if you have a view pointer from sender on the IBAction, you can get the related data model object. And if you're using bindings and a controller, you can then just have the controller remove that object.
With bindings, you will often create buttons that need IBActions attached, rather than some direct binding. But those IBActions can most definitely interact with the controller and not the view.
And with core data, array controllers are really slick vs. assuming you have to do it all programmatically.

Bindings for custom views for NSCollectionViewItem

I've got a NSCollectionView bound to my array of model objects, the NSView prototype to render items of the collection view is set up to use my custom NSView subclass.
At runtime a (generic) view is shown/instantiated in the collection view for each array element, all good.
However, I just can't figure out how to get to my array objects from the individual view instances to render the actual data that's specific to each element in the array.
I.e. how are we supposed to hook up data to the NSView prototype that is used to configure a 'cell' in the collection view?
Outlets don't seem to work for that particular view; they're all nil at runtime.
Bindings don't work with a vanilla NSView (apart from hidden and tooltip bindings for vanilla views..) - and we cannot add new bindings that would show up in IB for our NSView subclasses, can we?
Any hint appreciated!
Nevermind - I've settled with a custom NSCollectionView class overriding only
- (NSCollectionViewItem *)newItemForRepresentedObject:(id)object
to access the item view of NSCollectionViewItem instances created by super and set the required property there.

Custom NSViewController representedObject doesn't update when view selection changes

I've got a custom NSViewController that's also an NSOutlineViewDataSource. I also have a window with an NSOutlineView bound to an instance of my view controller as the data source, and the NSOutlineView bound to the view: property of the custom view controller.
The controller fills the outline view fine. However, selections within the view don't automatically update the representedObject property of the controller. The only thing that seems to trigger a change in representedObject is when I call setRepresentedObject: directly... which sort of defeats the purpose.
Any idea why that might be? representedObject is supposed to update automatically, right? Thanks in advance...
If I understand your question, you are not understanding what representedObject is for. The representedObject is the model object that represents all the data that a view controller is displaying. It is not the current selection of a view showing a collection of content. The representedObject for your custom top level view controller would be an NSArray or other collection class which contains the set of data you are displaying in your view controller. Changing the representedObject for your custom top level view controller should have the effect of swapping out for an entirely new data set if the pattern is being used correctly.
Typically the view controllers for the individual elements in a tabular type view would each have their own representedObject and this object would not change. For example if you were using an NSCollectionView each element in the view is controlled by an NSCollectionViewItem which is a subclass of NSViewController. The representedObject for each NSCollectionViewItem tells it what data to present in its little view.
NSOutlineView and its superclass NSTableView are different in that their cells are often NSCells rather than full fledged NSViews unless you choose to use them this way in Mac OS X 10.7 or newer. Even in this case, you don't typically have an NSViewController subclass managing each cell. So the view controller representedObject pattern is not used at the level of the individual element that the user would select. If what you want is to track the NSOutlineView's selection, there are many NSTableView methods that let you do this.

Cocoa - How to bind width of a NSView in InterfaceBuilder?

I'm trying to create a simple application that draws a grid in a custom view.
The custom view size is fixed (it doesn't depend on the size of the window).
The custom view is embedded in a scroll view to be able to explore the grid when the scroll view can't display the entire custom view.
Now i want to add sliders for controlling the grid parameters (nb raws, nb columns, tile width, tile height, ...), and these parameters influence the size of the custom view.
As an experiment, i'm trying to bind one slider's value to the width of my custom view but fail to find a way to do it.
How am i supposed to do this sort of things ?
Is it possible to do it in InterfaceBuilder ? I expected to find a width binding in Bindings Inspector Window but it's not there, curiously ;-)
Thanks.
You can't bind the width of a plain NSView, and binding to a property of a view is always a bad idea. View properties are seldom observable. Moreover, there is no width property; it's one member of the structure that is the value of the frame property, which you must set all at once or not at all.
As for exposing bindings in your custom view, you can do that, provided you keep the properties observable (which consists of little more than only changing the property's value using its setter). You'll need to expose the bindings in your view class's initialize method, and you'll need to write an IBPlugin.
See also the Cocoa Bindings Programming Topics.

Resources