Chapter 6. Model-View-Controller in Xcode

The Model-View-Controller pattern (or MVC as it is more commonly known) is at the heart of modern object-oriented software development.

MVC can cause a lot of confusion in the mind of the new developer, but the principles governing its use are fairly easy to understand. More importantly for you as a Cocoa developer, MVC is absolutely fundamental to using Xcode and Cocoa for Mac OS X development. That is why it is now time to take a short detour away from tools to nail this particular technology. In this chapter you are going to cover the fundamentals underlying MVC, and in particular the way in which it applies to Cocoa development using Xcode tools. You will take a look under the hood of a variety of projects to explore their MVC credentials.

If you are already comfortable with the MVC architectural pattern, you may want to skip this chapter, though you may still find it useful to see how the principles are applied in Cocoa.

Understanding MVC

The Model-View-Controller pattern is one of a number of architectural and design patterns that emerged from the object-oriented design community. In fact, its origins can be traced back to Smalltalk, though it was first properly described by Trygve Reenskaug (for a potted history, see http://heim.ifi.uio.no/~trygver/themes/mvc/mvc-index.html).

The simple idea behind MVC is separation of concerns. This means that the components of a software system are separated (the word often used here is decomposed) into three categories:

  • Those that are concerned with managing structure and storage of data (the Model)

  • Those that are concerned with providing a user interface and interaction (the View)

  • Those that govern the flow of control in the application and the traffic of information between the View and the Model (the Controller)

The Controller is the lynchpin in this design: instances of the Model only communicate with the user interface via the Controller, for example.

Using MVC in your software design reduces the coupling between your objects (the degree to which one component in your design depends on another). Ideally, you want each component to behave as independently as possible. Doing this means it's easier to reuse and replace your components. For example, you could switch your data store from SQLite to MySQL without having to recode the mechanisms that use the data. Or you might want to display or use the information produced by your system using a web browser instead of a desktop program.

To see what this means when you design software in Cocoa, let's take a closer look at each part of MVC, with a couple of examples.

The Simplest Example

Even the simplest applications will have a Model, View, and Controller. Let's take a look at the simplest application you have built so far in this book, the simple Hello World program from Chapter 3.

If you followed along with this example, open up the project now (if you didn't create this project yourself, you can download the completed project from the book's page on http://www.apress.com).

Open the project and load the NIB file (MainMenu.xib). You'll remember that you created the user interface elements—a Text Field control to hold the text entered by the user, a Label control to hold the text produced by the program and a Button control to initiate the transformation. This collection of components, including the window itself, make up the View. You also then created some connections that determined the routes for carrying messages. These connections all involve the HelloController object. If you right-click (or Control-click) on HelloController in the Document window you will see its connections.

This is the Controller. You can see that the Button invokes an action method called sayHello:, and it does that by sending a message to HelloController (in effect, saying "run the sayHello method"). HelloController in turn sends messages to the Text Field (amounting to "tell me what you have") and (once it has the data back from the Text Field) to the Label (amounting to "display this"). The flow of messages is shown in Figure 6-1. So given that both the messages go from HelloController to these objects, how is the program to know which way around things have to happen? Well, go back to the Xcode project window and check out the sayHello method in HelloController.m (see Listing 6-1).

Message flow in Hello World

Figure 6-1. Message flow in Hello World

Example 6-1. The sayHello: Method

@implementation HelloController

- (IBAction)sayHello:(id)sender {
    [destinationTextField setStringValue:[sourceTextField stringValue]];
}
@end

The code in bold specifies what needs to happen. Set the content of the Label control (destinationTextField) to the content of the Text Field (sourceTextField).

So where in all of this is the Model? Well, this is a very simple example and the Model in this case is the Text Field into which the user types the message. Nevertheless the fundamentals are the same for most Cocoa programs you will write. The controller takes the data provided by the Model and carries out whatever processing is necessary to interact with the View.

In practice, as you will see in more complex examples, you will have a Controller corresponding to each View component that you need to manage, and an underlying Model.

A More Complex Example

One of the best-known example projects in Cocoa is the Currency Converter project. Let's try a variation on that example and build a temperature converter. This will allow you to enter a temperature value in the Fahrenheit or Centigrade boxes, and the converted value will appear in the other box. The completed project will look like Figure 6-2.

The Temperature Converter program running

Figure 6-2. The Temperature Converter program running

You're a seasoned veteran now, so there's no need to describe this in step-by-step detail. So, create a new Cocoa project (make sure the Core Data and Document-based application options are not checked) and call it TemperatureConverter. Create a new Objective-C class (and the corresponding header file) and call it Converter. At this stage, your project workspace looks like Figure 6-3:

TemperatureConverter project workspace

Figure 6-3. TemperatureConverter project workspace

Before building and wiring up the user interface, you are going to add the code. Let's think about the Model first. You have probably already guessed that this is the reason you created the Converter class—this is where your Model is going to live.

The Model for the TemperatureConverter program has to contain a variable for the original temperature, a method or methods for conversion and the Converter class. The conversion process takes an original temperature and returns a converted temperature.

Let's start by declaring the variable.

The Model needs to understand the data and the data manipulation logic. You are going to need methods to do the data conversion and properties to hold the data. Figure 6-4 shows how the messages flow in the application (for simplicity, this figure shows the conversion from Centigrade to Fahrenheit).

MVC and message flow in the TemperatureConverter application

Figure 6-4. MVC and message flow in the TemperatureConverter application

A consequence of this design is that the data and the logic for the conversion are stored in the Model. If you wanted to change to a different algorithm for the conversion, or to introduce a different conversion (say, to and from degrees Kelvin), that work would happen almost entirely within the Model. The Controller takes the values from the View (the user interface) and feeds those values into the Model. The resulting value from the conversion is then placed back in the View to update the converted temperature value.

Open Converter.h and make it look like Listing 6-2.

Example 6-2. Converter.h

#import <Cocoa/Cocoa.h>

@interface Converter : NSObject {
float originalTemp;
}

@property(readwrite) float originalTemp;

- (float)convertCToF;
- (float)convertFToC;

@end

Now open Converter.m and add code to make it look like Listing 6-3.

Example 6-3. Converter.m

#import "Converter.h"

@implementation Converter

@synthesize originalTemp;

- (float)convertCToF {
    return ((self.originalTemp * 1.8) + 32.0);
}
- (float)convertFToC {
    return ((self.originalTemp - 32.0) / 1.8);
}
@end

This is the Model for the application. The Model will receive an original temperature value, either in centigrade or Fahrenheit, and then the appropriate method will be called (by the Controller, as you will see shortly). After transforming it the Model returns the new temperature value.

Now let's move to the Controller. Open TemperatureConverterAppDelegate.h and add code to it as shown in Listing 6-4.

Example 6-4. TemperatureConverterAppDelegate.h

#import <Cocoa/Cocoa.h>
#import "Converter.h"

@interface TemperatureConverterAppDelegate : NSObject <NSApplicationDelegate> {

    IBOutlet NSTextField *centigradeField;
    IBOutlet NSTextField *fahrenheitField;
    Converter *temperatureConverter;
}

- (IBAction)convertTemperature:(id)sender;

@end

Note that the Controller imports Converter.h—this makes the Controller aware of the Model. The other declarations in this file are for the outlets that you will be building shortly in Interface Builder, and the method that is invoked by changes in the user interface. Now open the implementation file TemperatureConverterAppDelegate.m and update it to look like Listing 6-5.

Example 6-5. TemperatureConverterAppDelegate.m

#import "TemperatureConverterAppDelegate.h"

@implementation TemperatureConverterAppDelegate

@synthesize window;

- (void)awakeFromNib {
    fahrenheitField.floatValue = 32.0;
    centigradeField.floatValue = 0.0;
    tempConverter = Converter.new;
}

- (IBAction)convertTemperature:(id)sender {
    float temperatureF = fahrenheitField.floatValue;
    float temperatureC = centigradeField.floatValue;
    if(sender == fahrenheitField) {
        tempConverter.originalTemp = temperatureF;
        temperatureC = tempConverter.convertFToC;
        centigradeField.floatValue = temperatureC;
    } else if (sender == centigradeField) {
        tempConverter.originalTemp = temperatureC;
        temperatureF = tempConverter.convertCToF;
        fahrenheitField.floatValue = temperatureF;
    }
}

@end

The awakeFromNib method simply initializes some objects with starting values. The convertTemperature method takes the value from one Text Field, depending on which is the sender, sets the original temperature value and calls the appropriate method in the Model object, takes back the converted value and places it in the other Text Field.

Notice that the Controller is not aware of exactly how the conversion takes place—that is the concern of the Model.

Now let's turn to the View. Open MainMenu.xib and add two Text Field controls and two Label controls. Your Controller is already there (that's the Temperature Converter App Delegate). Make it look like Figure 6-2. Notice that there is no button—you aren't going to need one in this application.

Note that you don't have to tell Interface Builder about the Model object—this is MVC, and the Controller is the only component in the design that talks to the Model. All you need to do here is wire up the user interface to the Controller.

First, Control-click (or right-click) on the first Text Field control and drag a line to the Temperature Converter App Delegate. In the pop-up menu, choose convertTemperature: as the action. Do the same for the other Text Field—remember, either Text Field should act as the source. Of course, either Text Field should act as the outlet from the Controller too, so let's wire that up now. Control-drag a line from the Controller to one of the Text Field controls, and from the popup choose the appropriate outlet. Do this for the other Text Field, too.

One other nice touch you can add is to add the capability to tab between the Text Field controls. Control-click one Text Field and from the popup drag from the circle next to nextKeyView to the other Text Field. Do the same thing in the other direction.

The final thing to do is specify which Text Field is the first to be selected at startup. Control-drag from the Window icon in the Document window to one of the Text Fields (I chose the centigradeField) and from the popup choose InitialFirstResponder.

You're done. Save the NIB, quit Interface Builder and run the application. You should be able to enter a number in either field, and when you tab to the other field (or press Enter) the other field will show the converted temperature.

In closing, let's just review the architecture of the application (see Figure 6-4). The separation of concerns is very clear: the Model contains the logic that accepts, stores, modifies and returns data. The View contains the user interface objects. The Controller sits between them and manages the flow of information.

Summary

You should be starting to have a good picture now of how the Model-View-Controller pattern is used in Cocoa. It really is a core aspect of Cocoa development, and as you embark on new applications it is worth some initial planning to identify your Models, Controllers, and Views.

This was intended only to be a brief foray into underlying technologies, and there will be one or two similar diversions as the book goes on. For now, however, it's time to get back to the tools. In Chapter 7, you will take a detailed look at Xcode's debugging tools.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.144.170