Best Practice for Manipulating UI Elements In Cocoa - cocoa

I'll start by saying that I'm new to cocoa development. I'm also surprised I didn't find a post about this already, but I've filtered through a number of posts now without success.
I have a set of elements that should change state based on the state of a long running algorithm.
Basically, I have a start button, a cancel button, and a next button. The initial state of the app would be start button enabled, cancel and next buttons disabled. The status of the algorithm should swap enabled / disabled on all the buttons as it progresses.
Every option for manipulating button state I have seen involves coding button.enabled into the controller code. I'm coming from an ASP .NET MVC background as I dive into Cocoa and this seems backwards to me. Shouldn't the view logic be separated from the controller logic in the MVC pattern?
To me, it seems I should be able to emit a couple boolean values as IBOutlets like algorithm running and algorithm success, and bind the button state at the view layer. Do I need to toss this idea? Or am I possibly missing something about the Cocoa version of design pattern (like the object I bind the view to should really be a view model, which interacts with a controller class)? Or, lastly, is there an easy way to accomplish what I'm talking about, and I've just missed it.

You don't need to code the enabled state of the button into your controller. What you can do is declare a BOOL property on your controller such as isBusy and then set this property to YES when you start your long operation and to NO when it's finished. You must do this using Key-Value Coding-compliant methods, which essentially means using the setter, so you'd call self.isBusy = YES;, for instance.
The reason you do this is because you can then use Cocoa Bindings to set up a binding on the UI controls. Go into the bindings inspector for one of your buttons, and bind the Enabled binding to your controller object with a key path of isBusy.
Cocoa bindings uses Key-Value Observing (KVO) to monitor the value of observed properties. When a change occurs in the isBusy property, the buttons that are bound to it will notice and change their enabled state in response.

You might be missing the delegate model of Objective-C. In the example you are giving you could have your controller object running the algorithm and updating its status to its delegate, in this case the view.
i.e your ViewController object will call the doSomething method from the ProgramController; and when its over ProgramController will invoque the somethingDidFinish method from its delegate, as defined in your ProgramControllerDelegate protocol)

Related

Target-Action with Xcode storyboards for desktop apps

I've got a button in a toolbar that I'd like to use to control the state (expanded/collapsed) of the right-hand-side split of a split-view. In a xib-based project this is trivial, but I'm using storyboards, and can't work out what the best approach is.
The key limitation is the fact that I'm not able to create target-action connections between objects in different scenes. As I see it, this leaves me with three options:
Have the window controller handle the expand/collapse request - this seems inappropriate - what's the window controller got to do with the state of the split view?
Have the window 'collect' the action and refer it on to the split-view controller for processing - this is better but to me it still seems like bad design to have the window controller in this process at all.
Add a custom action to the responder chain, and connect the button's selector to the responder chain. This is what I've done, but it still seems like a distant second-best compared with the xib-style approach - a direct drag-and-drop between objects.
I suppose as I was hoping that storyboards would just make everything easier - am I now right in thinking that this target-action dilemma is a genuine shortcoming of the storyboard approach, and that I just need to get used to one of the above?

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.

Cocoa bindings only update when window focus changes

I am using MonoMac to build a desktop download manager for Mac in C#.
My XIB has a Table View, whose columns are bound to an NSArrayController. The array controller is connected to my Main Window Controller through an IBOutlet. The array holds a bunch of HttpDownload objects, which derive from NSObject. These HttpDownload objects contain properties such as TotalSize, TotalDownloaded, Bandwidth, etc. I have decorated these properties with an [Export] attribute.
In the controller I add some HttpDownload objects to the NSArrayController using the AddObject method. A background process, started with Task.Factory.StartNew() begins the download asynchronously and updates the bound properties such as TotalDownloaded and Bandwidth as data is received.
I can see these new values being reflected in the Table View, but only once I've "forced" a UI update, for instance by causing the window to lose focus, gain focus, or by clicking on a button within the window.
I have tried setting Continuously Updates Value in IB, but this makes no difference (and reading the docs, I didn't think it should).
Does anyone know to make the UI update the bound values in "real time", instead of only when a window event occurs?
I figured this out shortly after I posted this question.
It seems that we need to manually call WillChangeValue() and DidChangeValue() for at least one of the keys that are being updated, for instance, when I updated the total downloaded:
WillChangeValue("DownloadedBytes");
DownloadedBytes += bytesRead;
DidChangeValue("DownloadedBytes");
In my case, calling these methods for just one of the updated keys seems to be enough to force an update of all the bound values.
For reference, in Objective-C these selectors are called [self willChangeValueForKey:#"keyname"] and [self didChangeValueForKey:#"keyname"].

Options for keeping models and the UI in sync (in a desktop application context)

In my experience I have only had 2 patterns work for large-scale desktop application development when trying to keep the model and UI in sync.
1-An eventbus approach via a shared eventbus command objects are fired (ie:UserDemographicsUpdatedEvent) and have various parts of the UI update if they are bound to the same user object updated in this event.
2-Attempt to bind the UI directly to the model adding listeners to the model itself as needed. I find this approach rather clunky as it pollutes the domain model.
Does anybody have other suggestions? In a web application with something like JSP binding to the model is easy as you ussually only care about the state of the model at the time your request comes in, not so in a desktop type application.
Any ideas?
I am currently using the event bus approach to synchronize the models and the UI in my application, but I have hit a hurdle with it in that it's difficult to make it very fine grained, for example, at the property level where you are just interested in knowing if property x of an object gets updated, and there are hundreds or thousands of such cases.
For such a fine grained control, you might want to checkout how KVC (Key Value Coding) and KVO (Key Value observing) works in Cocoa. It basically allows an object to observe any other object's properties as long as it uses some basic principles of KVC. The interested objects automatically get notified upon changes, and you don't have to explicitly notify the observing objects on each property change as that is taken care of by the underlying implementation of KVO. It is somewhat similar to the PropertyChange listeners in Java beans.
If there is too many observations going on, and writing the glue code to update models/views on property changes becomes problematic, you might want to take it a step further and have data-binding to keep models and views synchronized. Built upon the concepts of KVO, the idea is to bind properties of objects, so that a change in one automatically updates the other, and vice versa. For example, you could bind the text in SO's answer field, to the answer preview that we see right below.
.bind('answer.value', 'answerPreview.text')
Both happen to be view elements in this case, so data-binding is a generic approach, and can be used to bind objects more appropriately and not just UI with models.

How to get notifications of NSView isHidden changes?

I am building a Cocoa desktop application. I want to know when a NSView's isHidden status has changed. So far using target/action doesn't help, and I can't find anything in NSNotification for this task. I would like to avoid overriding the setHidden method, because then I'll have to override all the NSView derived class that I am using.
UPDATE: I ended up using KVO. The path for "isHidden" is "hidden", probably because the setter is "setHidden".
You could use Key-Value Observing to observe the isHidden property of the NSView(s). When you receive a change notification from one of these views, you can check if it or one of its superviews is hidden with -isHiddenOrHasHiddenAncestor.
A word of warning: getting Key-Value Observing right is slightly tricky. I would highly recommend reading this post by Michael Ash, or using the -[NSObject gtm_addObserver:forKeyPath:selector:userInfo:options] method from the NSObject+KeyValueObserving category from the Google Toolbox for Mac.
More generally, one can override viewWillMoveToWindow: or the other related methods in NSView to tell when a view will actually be showing (i.e. it's window is in the window display list AND the view is not hidden). Thus the dependency on KVO for the 'hidden' key used above is removed, which only works if setIsHidden has been called on that view. In the override, 'window' (or [self window]) will indicate whether the view is being put into a visible view hierarchy (window is non-nil) or being taken out of it (window is nil).
I use it for example to start/stop a timer to update a control from online data periodically - when I only want to update while the control is visible.
Could you override the setter method for the hidden property so that it will trigger some custom notification within your application?

Resources