Just started learning Objective-C and Cocoa with the help of BNR's Cocoa Programming for Mac OS X (4th Ed.), and I'm working on a document-based application. I've read through Apple's developer documentation on the document architecture, and have chosen to subclass NSWindowController and override makeWindowControllers on my NSDocument subclass. I have a few reasons for doing this:
to separate model logic (in NSDocument subclass) from view logic (in NSWindowController subclass).
to customize the title of my document windows (Apple's developer documentation says that the proper way to do this without unwanted side effects is to subclass NSWindowController and override windowTitleForDocumentDisplayName:
Apple's documentation seems to strongly suggest subclassing NSWindowController for all but the simplest of applications, and mine is definitely not "simple"
So, my NSDocument subclass is a model controller, and my NSWindowController subclass is a view controller. Further, I understand that most of an application's "work" is done in the controller objects, as the views and models should be as application-agnostic and reusable as possible. Now comes my question: how do these two types of controllers interact to actually do this "work"?
For example, imagine I'm writing a spreadsheet application, and I want to have a menu item (or toolbar button) that brings up a sheet for creating a chart or graph from some of my data. In that sheet, the user will enter various parameters and options for how to create the chart or graph, then click "OK" (or whatever the button is called).
Who should respond to the menu item's action, the document (model controller) or the window controller (view controller)? The task of actually loading and showing the sheet seems decidedly "view-related", so it should go in the window controller, correct? But the controller for the sheet needs a model to display to the user (a Chart object, or maybe ChartInputs); where does that model get created and given to the sheet controller? Should the document respond to the menu item by creating the ChartInputs model object, then pass that to the window controller, which creates the sheet controller, passing it the model object, and shows the sheet? Or should the window controller respond to the menu item, request a new model object (perhaps through some sort of factory provided via dependency injection into the constructor of the window controller), then proceed with creating the sheet controller, passing the model, and showing the sheet?
What about after the user fills out the sheet and clicks "OK"? Where should control be returned to process the user's choices and actually create the chart -- window controller, document, or both? What about logic to validate the user's inputs after they click "OK", but before the sheet is dismissed (in case something is invalid)?
To begin, consider windowless operation of your NSDocument. For example, you might create a utility application which shares your NSDocument class, opening documents for scripting, printing, or other manipulation, but without presenting your primary document window. Imagine your NSDocument class reused for that application – and put the logic you wouldn't want into your window controller. This way, the NSDocument subclass is primarily responsible for activities which affect the document's state.
These are the responsibilities of the model-controller (NSDocument subclass):
Serialization and deserialization
Loading and saving
Manipulating the document's state
Managing and dispatching print views
Monitoring the document for changes by others
Refreshing supporting data sources – sources which affect the document model – and applying changes to the model and document as needed
Managing an activity log related to the document
Owning the undo manager
Exposing the essential model objects to the view controller
Creating window controllers
Facilitating some editing behaviors, such as a change to one property triggering creation or removal of objects
If you're using Core Data, your managed object context, persistent store coordinator, and persistent store are part of the model-controller, not the model. Of course the managed objects themselves are part of the model.
This leaves these responsibilities to the model:
Helper methods for inserting, rearranging, and deleting model objects
Helper methods for accessing specific parts of the model
Data validation
Rendering the model to strings, in various formats
Serializing and deserializing oneself
Facilitating some editing behaviors, such as a change to one property triggering changes to other properties
On the other side, these are the responsibilities of the view-controller:
Manipulating the view to keep it in sync with the model
Manipulating the view to keep it in sync with the document state
Responding to localized actions, such as a button which adds or removes a model object
Presenting auxiliary views and responding to input in those views
Dispatching actions which are affected by selections in the UI, such as the selected rows in a table view
Managing auxiliary controllers used during editing, which are not related to the document itself (such as web-service data)
Serving as a data source for view objects
If you're using Cocoa Bindings, your bindings also part of the view controller.
This design produces a reasonable separation of responsibilities between the view-controller and model-controller. However, they both sit between the view and model. While this produces modularity it doesn't produce decoupling.
Though I did consider windowless operation, I largely arrived at this design pattern empirically – by placing similar code together and separating code that felt out-of-place. I'm curious if others post authoritative sources or references which agree or disagree about how to do this.
To take up your example, I'd suggest this design:
EditorWindowController creates ChartParameters object and gives it a reference to the document's model:
[[ChartParameters alloc] initWithWorkbook:self.document.workbook]
EditorWindowController sets up the new-chart view, which probably has its own NewChartViewController. It passes the ChartParameters and document objects to the NewChartViewController and displays the window.
The ChartParameters object is responsible for validating the user's choices. The NewChartViewController needs to manipulate the view to keep it in sync with the validation result. (Avoid letting the user make a mistake: don't wait until the end to validate the input.)
When the view finishes, the NewChartViewController asks the model to create a new chart using the given parameters:
[self.document.workbook addChartWithParameters:self.chartParameters]
If you want the not-yet-a-chart object to be part of your document, you can do is this way instead:
EditorWindowController asks document model to create a new chart object:
Chart *newChart = [self.document.workbook addChart]
Thew new chart should have a flag set, indicating it's not ready for display.
EditorWindowController sets up the NewChartViewController, passes it the chart, displays the window.
Chart object validates the users's choices and the NewChartViewController keeps the view in sync.
When finished, tell the chart it's ready for display. Or if the user cancels, remove it.
In either of these designs, NewChartViewController is a model-controller and view-controller in one, localized for its particular task.
Related
If I am using an instance of NSArray to populate a pop-up button, where in terms of MVC does that NSArray need to be initialised? I'm guessing it would fall under Model, however if that's the case, how do I initialise the array? Do I start a new implementation file to contain the array? (Obviously don't want to use my app delegate file as that would fall under Controller, not Model.)
The "model" part of MVC is the data that the app stores, presents, and/or allows the user to manipulate. It would largely be the same whether your app was running on a Mac, an iPhone or whatever. The "view" is the UI. That is the things the user actually sees on screen. The controller is the part that goes in between these two. It's responsible for implementing the specific behavioral logic for the app as well as "gluing" the view layer to the model layer.
So, with that said, the array of items to be displayed in a popup button may or may not be part of the model. It entirely depends on the specific UI you're implementing. If the selection is between a number of objects represented in the model, the array's contents would indeed be part of the model, but it still might be that the controller pulls the items out of the model in another form and turns them into an NSArray. It might also be a way to select between e.g. a fixed list of actions to be performed, in which case it's more properly part of the controller layer itself.
In other words, there's no one answer to your question. But, the likelihood is that the controller will at least provide the array in question to the UI, and may also be entirely responsible for its content. It all depends on exactly what you're trying to accomplish.
The initialization would happen within the model object, but that initialization would likely be called from a view controller (I wish these were just called controllers--there's no ModelController class.) Possibly in viewDidLoad but really wherever the best fit for your use case would require.
The model object should initialised by the controller object, usually in the viewDidLoad method. If a model object is owned by another model object (for example, if your custom model object has an NSArray instance variable, then your custom object is the parent and the NSArray is the child), then that child model object should be initialised in the initialised method of the parent model object.
I suppose your NSArray is a model object on its own, so it should be initialised in the viewDidLoad method of the controller object.
This is just one answer and not necessarily how everyone develop applications in objective C.
If I have an app with a small data model or with models scoped to their views, I will put the models on the AppDelegate or in the viewControllers themselves, if they are limited in scope to that view.
They will be initialized closest to where it makes sense in the app for that data.
Sometimes you will see a "FAT" viewController which represents a home screen controller or main screen controller and folks will pile the models on that class. Its very common.
But if I have an application with a large data model - lots of models that have lifetimes not scoped to the life of a view - then I will create a class in my application called *myAppNameHere*AppModel, and I will centralize the storage of application models, and use service classes as necessary to request data to populate/update models.
This is just one approach. And great question!
We are working with an application that uses prism and MVVM for healthcare tracking operations.
Within this application, we have registered views in primary regions, and scenarios in which clicking on a cell in a grid (in this case a xamdatagrid - Infragistics) will launch another region via the prism region popup behaviors constructs.
So, when we click on the cell, a cellactivation operation in the code behind for that view is called which then calls a method in the view model to .RequestNavigate to the view that is registered for the 'secondary' popup region (thus launching what appears to be a dialog over the existing application).
I am attempting to pass an object from the parent view (the values of the record in the grid) to the child view that is launched in the popup, and have found that none of the expected operations is working.
I have a mediator object that we are using to pass information back from children to parents, but this does not work the other direction because the publish/notify operations for the mediator require the child view to 'already' be instantiated before the parent publishes to pass that information.
I'm finding that the event aggregator structures built into the Prism.Events classes don't appear to be working either (I'm assuming because of the same publish/subscribe issues). I can publish an event through the event aggregator from the parent view and subscribe in the child view, but I have to launch the child view 'twice' in order to get the event tied to the event aggregator operation to actually fire.
I understand this is rather vague (minus the code), but I'm looking more for a start point, or anyone that's run into the same kinds of issues. The bullet point scenario is as follows:
Parent view already instantiated in existing region.
Clicking on cell in parent view instantiates (navigates to) child view in popup region.
Same click operation needs to pass an object from the parent view to the child view so that the child view can filter its own data based on that object.
Operations are constructed using Prism/MVVM.
Any help, suggestions, pointers, ideas would be awesome.
We had the same scenario and ended up with creating a UiService that are injected to our ViewModels. Our ViewModel base class has a SetModel method which the UiService calls when navigating, for sending parameters to the destination view.
By example:
Clicking a button in the existing View.
Source ViewModel calls UiService ShowView(data).
UiService calls RequestNavigate for the requested view.
UiService calls SetModel on the destination ViewModel (by accessing the DataContext of the requested view) to pass in the data from the source ViewModel.
Depending what you want to pass it can be pretty simple with INavigationAware
Sounds like you want to pass something like query string - parameters, etc. Code to open window will be like this:
var query = new UriQuery
{
{ "MailItemKey", this.SelectedMailItem.MailItemKey.ToString(CultureInfo.InvariantCulture) }
};
RegionManager.RequestNavigate(RegionNames.Popup, typeof(MailItemView).Name + query.ToString());
And inside MailItemViewModel which is our popup's VM code is like this:
public override void OnNavigatedTo(NavigationContext navigationContext)
{
var mailItemKey = int.Parse(navigationContext.Parameters["MailItemKey"]);
}
If you want to pass custom objects - it will be more complex. I would look for MVVM in the box samples - there is some code on how it's done. I had to re-work it whole lot and code is not really copy-pasteable.
Sounds like a perfect use of a RegionContext.
Parent view contains a region that can have multiple "child" views activated in it. They need to share some context or data. Set a region context and bind or pass whatever info all the "child" views in the Region will need.
See the Prism samples around RegionContext.
In a GUI application, I am using an MVC with a Passive View, as described here.
This pattern is yet another variation on model-view-controller and model-view-presenter. As with these the UI is split between a view that handles display and a controller that responds to user gestures. The significant change with Passive View is that the view is made completely passive and is no longer responsible for updating itself from the model. As a result all of the view logic is in the controller. As a result, there is no dependencies in either direction between the view and the model.
So far, my Controller registers as a listener to existing, static components created by the Passive View itself at initialization. Now, the Controller needs to dynamically create a variable amount of UI components, depending on the Model (concretely, right now I am talking of a grid of checkboxes - the grid's dimensions is variable).
Here is where my hesitation lies:
Should this dynamic UI creation code be implemented in the Controller? This would lead to less complex code resulting from keeping the View unaware of the Model, but a part of the presentation would be decided by the Controller...
Should the View propose a generic, Model-independent way to create the UI components on demand, let the Controller use it and register listeners to the retrieved UI components? Here the Controller would have to convert back and forth between Model objects and generic objects (concretely, strings, integers, ...).
Whenever a view needs dynamic control creation, it tends to be for a collection of something. This means your Presenter/Controller does not need to create all of the logic, but call a method on the view which will create the controls.
On the View:
void PopulateUserOptions(IEnumerable<String> options)
{
foreach (var item in options)
{
\\create and add your controls to the form
}
}
This way the controller is expressing when a controll should be created etc, but leaving it to the view to decide how to do it.
I'm developing a Mac Application. The application has a common source view on the left and a detail view on the right which is the main part of the whole window.
It's like a Master-Detail relationship, but each element in the source view require another detail view. In fact, I have designed a specific NSViewController for each element in the source view.
If I'm switching between these NSViewControllers, that means If I select another element in the source view, I remove the current view and add the view of the newly selected NSViewController. Everytime I change the NSViewController, its state will be lost. When the user comes back to that NSViewController, he has to start over.
My question now is: How can I save the state of the NSViewController, so that I can switch between these without losing its states and can continue where I have left?
Two considerations about your problem:
Keep model data in model classes. This means that you can always recreate a view controller and set its represented object provided the model classes have kept the changes made via the view controller. When you need to instantiate a view controller, set its represented object to (a representation of) a model class.
When removing a view from its superview, you do not necessarily need to release its corresponding view controller. Instead, you can keep strong references to all view controllers in your window controller/application delegate, so no state is actually lost.
Use NSArchiver. Implement archiving/unarchiving in your dealloc/init methods and store each view controller's state in a file named after the class (if you have one item per view controller policy). Otherwise think of some simple naming convention and use it.
Result should be an settings panel with an OutlineView and "add item", "add group" and "delete" buttons. The buttons add entries to an NSOutlineView. The data is stored in a NSMutableDictionary (or whatever is suitable). Sorting/DragDrop enabled for the OutlineView.
What is the best or most comfortable way for this (and write less code)?
Modifying NSMutableDictionary, NSOutlineView refreshes from NSMutableDictionary?
Modifying NSOutlineView, Result is stored in NSMutableDictionary?
... NSTreeController?
... CoreData?
What's best practice for that?
Thanks in advance!
This is a pretty broad question. You should always store your model data in a model object of some kind, be that a Core Data entity, an NSMutableDictionary or a custom object of your own creation. You should definitely NOT be storing the data in an NSTreeController or NSOutlineView instance, these are not model objects.
If you're using Core Data for the rest of your app and you need to persist the data that is manipulated by the outline view then this is a good choice, but it might be overkill if you have only simple requirements.
To control what is displayed in the outline view you can either use NSTreeController or your own controller object that responds to the NSOutlineView datasource and delegate protocols. In practice you might use both, as some things such as whether or not an item is a group item can only be controlled by the NSOutlineView delegate methods.
In my personal experience I've found that NSTreeController can be very difficult to deal with for anything beyond very simple tasks and I now longer use it, I find it's much simpler to just use the datasource methods in my own controller.
As far as modifying the contents of the outline view, you should always modify the model via a controller, you should never update the view directly. You would implement methods such as -add: in your controller or use the -add: method of NSTreeController if you're using it.
Your view's controller should then detect the change in the model and ask the view to update. The view controller and model controller can be the same object but they don't have to be. Key-Value Observing is a useful technology that can inform your controller of a change in the model.
Here's some sample code from Apple that you might find useful:
http://developer.apple.com/mac/library/samplecode/SourceView/
http://developer.apple.com/Mac/library/samplecode/AbstractTree/