Re: Conceptual MVC Help
Re: Conceptual MVC Help
- Subject: Re: Conceptual MVC Help
- From: Erik Buck <email@hidden>
- Date: Tue, 20 May 2008 10:42:36 -0700 (PDT)
Given your class:
// Popup.h
// TestPopup
#import <Cocoa/Cocoa.h>
@interface Popup : NSObject
{
NSArray *popupArray;
NSString *selectedGame;
}
@property (readwrite, copy) NSArray *popupArray;
@property (readwrite, copy) NSString *selectedGame;
@end
To start with, in my opinion you have a broken model from a MVC perspective. Having a class called "Popup" in the Model subsystem is a dead give away. The Model should not know or care how information is presented to users. The Model's only concern should be storing and manipulating data that is valuable and useful even if there is no particular user interface at all. The Model subsystem should probably not be concerned with the user's current selection either in my opinion, but others might disagree.
I am guessing that you are trying to implement a Model that internally manages, controls, implements, dare I say models, some games. One thing the Model knows about is which games are available. Perhaps the available games are loaded from a data file or stored in a database or obtained over a network. These are all proper roles for a Model.
If the set of games available is static and constant and will most likely never change, there is no need for a specialized Model object to store those games. The Model object might just be
NSSet availableGames = [[NSSet alloc] iniWithObjects::@"C9", @"F11", nil]];
If more complex logic is needed to determine the set of games available, there might be a class like the following:
@interface GameManager : NSObject
{
NSSet *availableGames;
}
@property (readonly, copy) NSSet *availableGameNames;
@end
The availableGames property should be declared readonly in the interface and re-declared readwrite in the implementation of the class. If the job of GameManager is to encapsulate the set of available games, no other object should be setting that property.
So what about selection? As it happens, NSArrayController is more than able to store information about the user's current selection. NSArrayController is (as you might imagine) properly part of the Controller subsystem in MVC. The job of the Controller is to mediate between the user interface, View, and the Model. An instance of NSArrayController is a good choice on this occasion. The NSArrayController instance will also take care of rearranging, sorting, and filtering Model data for presentation to the user, but that is another subject...
In Interface Builder, instantiate an instance of NSArrayController. Bind the content of the NSArrayController to the availableGames property of a handy GameManager instance. You will need to decide where in your application the GameManager instance will be created and what the key path to that instance will be. The key path might be through the nib file's owner for example. You will also probably want to limit the NSArrayController to single selection, no empty selection, no add or remove, etc.
Now that you have an NSArrayController instance that hides the underlying Model storage for a conceptual set of game names from which a user might select, you can use any View subsystem interface construct you want to present the game names. You might want a table view or a popup button or a collection view or a custom control. It doesn't matter and shouldn't. To hammer on the point, the View should have no knowledge of the Model other than perhaps some property key names. That way, you are free to change either the Model implementation or the View implementation without affecting the other.
I can hear you asking, if my Model doesnt know the users selected game, how can the Model start the selected game or configure the selected game or whatever it has to do?
Everything depends on what you want to do with the selected game. If the goal is to display a detail View that enables the user to configure the selected game before starting it, there might be more Controller layer objects that have bindings to the available games array controllers selectedObject. That is a common Masterß>Detail kind of user interface. This brings up an interesting question: why are we only storing available game NAMES? Why not use the Model to store available game Objects that among other things know their name ?
@interface Game : NSObject
{
NSString *name;
int numberOfPlayers;
float difficulty;
}
@property (readonly, copy) NSString *name;
@property (readwrite) int numberOfPlayers;
@property (readwrite) float difficulty;
- (NSString *)description; // implement to return [self name];
@end
@interface GameManager : NSObject
{
NSSet *availableGames;
}
@property (readonly, copy) NSSet *availableGames;
@end
@implementation GameManager
{
NSSet *availableGameNames;
}
@synthesize (readwrite, copy) NSSet *availableGames;
- (id)init
{
self = [super init];
if(nil != self)
{
[self setAvailableGames:[[[NSSet alloc] initWithObjects:[C9Game defualtGame], [F11Game defaultGame, nil] autorelease];
}
Return self;
}
@end
Guess what? We just changed the Model, and the NSArrayController and whatever View you used still work without change! Why is that? Because when the user interface object, lets call it a popup button, tries to display information about the arranged objects in an array controller, the description method is used to get the information, in this case the games name.
However, we can now implement a more full featured Masterß>Detail type View because the NSArrayControllers selectedObject will now be an instance of class Game. Notice how all of this worked thanks to dynamic typing. The NSArrayController didnt need to know the type of the content objects. The hypothetical popup button didnt need to know the type of the array controllers arranged objects. We were able to change the Model implementation without repercussion because of dynamic typing which conceals specific type information from the Controller and View subsystems.
So, the hypothetical Game configuration detail user interface can bind a text field to the available games array controllers selectedObject.numberOfPlayers. The text field could have an associated number formatter that limits the user input to reasonable values like numbers between 1 and 5000.
So, the Model still doesnt know about the users game selection. How is a game started ?
Bind a buttons target to the available game controllers selected object. Set the buttons action to startGame:. Implement a startGame: method in the Game class. When the buton is pressed, the selected Game instance will receive a startGame: message.
THIS WAS ALL TYPED IN AN EMAIL PROGRAM.
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden