Intro to Object-Oriented Design: Part 1/2

This tutorial series will teach you the basics of object-oriented design. In this first part: Inheritance, and the Model-View-Controller pattern. By Ellen Shapiro.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 5 of this article. Click here to view the first page.

Building out the User Interface

In VehicleListTableViewController.m, add the following import to the top of the file, just below the import for Vehicle:

#import "Car.h"

Next, add the following method between didReceiveMemoryWarning and #pragma mark - Table View:

#pragma mark - Data setup
-(void)setupVehicleArray
{
    //Create a car.
    Car *mustang = [[Car alloc] init];
    mustang.brandName = @"Ford";
    mustang.modelName = @"Mustang";
    mustang.modelYear = 1968;
    mustang.isConvertible = YES;
    mustang.isHatchback = NO;
    mustang.hasSunroof = NO;
    mustang.numberOfDoors = 2;
    mustang.powerSource = @"gas engine";
    
    //Add it to the array
    [self.vehicles addObject:mustang];
    
    //Create another car.
    Car *outback = [[Car alloc] init];
    outback.brandName = @"Subaru";
    outback.modelName = @"Outback";
    outback.modelYear = 1999;
    outback.isConvertible = NO;
    outback.isHatchback = YES;
    outback.hasSunroof = NO;
    outback.numberOfDoors = 5;
    outback.powerSource = @"gas engine";
    
    //Add it to the array.
    [self.vehicles addObject:outback];
    
    //Create another car
    Car *prius = [[Car alloc] init];
    prius.brandName = @"Toyota";
    prius.modelName = @"Prius";
    prius.modelYear = 2002;
    prius.hasSunroof = YES;
    prius.isConvertible = NO;
    prius.isHatchback = YES;
    prius.numberOfDoors = 4;
    prius.powerSource = @"hybrid engine";
    
    //Add it to the array.
    [self.vehicles addObject:prius];

    //Sort the array by the model year
    NSSortDescriptor *modelYear = [NSSortDescriptor sortDescriptorWithKey:@"modelYear" ascending:YES];
    [self.vehicles sortUsingDescriptors:@[modelYear]];
}

This simply separates the data setup methods and adds the method to construct your vehicle array.

Find awakeFromNib and add this code to the end of the method:

  // Initialize the vehicle array
  self.vehicles = [NSMutableArray array];

  // Call the setup method
  [self setupVehicleArray];

  // Set the title of the View Controller, which will display in the Navigation bar.
  self.title = @"Vehicles";

The above method executes when your Storyboard finishes constructing the nib for a particular UIViewController at runtime. It calls the setupVehicleArray method you just created, and sets the title of the VehicleListTableViewController to reflect its contents.

Build and run your application; you’ll see something that looks like the following:

Three Cars

The numbers you see represent memory addresses and will be different, but everything else should look the same.

The good news is that these objects are being recognized as Car objects. The bad news is that what’s being displayed isn’t terribly useful. Take a look at what’s set up in the UITableViewDataSource method tableView:cellForRowAtIndexPath:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

    Vehicle *rowVehicle = self.vehicles[indexPath.row];
    cell.textLabel.text = [rowVehicle description];
    return cell;
}

Here, you’re grabbing a UITableViewCell object then getting the Vehicle at the index in the self.vehicles array matching the row of the cell you're constructing. Next, you set that specific Vehicle’s description string as the text for the cell’s textLabel.

The string produced by the description method (which is inherited from NSObject) isn’t very human-friendly. You’ll want to define a method in Vehicle that describes what each Vehicle object represents in a way that is easy for your users to understand.

Go back to Vehicle.h and add the following new method declaration, below all the other basic method declarations, but above @end:

//Convenience method for UITableViewCells and UINavigationBar titles.
-(NSString *)vehicleTitleString;

Then, in Vehicle.m, add the following implementation, again below the basic method implementations:

#pragma mark - Convenience Methods
-(NSString *)vehicleTitleString
{
    return [NSString stringWithFormat:@"%d %@ %@", self.modelYear, self.brandName, self.modelName];
}

The method above takes three properties that should be used on every single Vehicle and uses them to describe the vehicle concisely.

Now, update VehicleListTableViewController's tableView:cellForRowAtIndexPath: method to use this new method on Vehicle as follows:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

    Vehicle *rowVehicle = self.vehicles[indexPath.row];
    cell.textLabel.text = [rowVehicle vehicleTitleString];
    return cell;
}

Build and run your application; it should look a little nicer now:

Three Vehicles With Readable Titles

However, if you select a Vehicle from the list, all you'll see are the same elements visible in the storyboard, with none of the details from the Vehicle you selected:

Before Data Hookup Detail

What's up with that?

Open VehicleDetailViewController.m; you'll see that while the UI was constructed in the Storyboard and all the IBOutlets were already hooked up to save you some time fighting with AutoLayout, none of the data is hooked up.

Note: You'll notice that several of the IBOutlets are set up in the VehicleDetailViewController.m instead of in the .h file, as they normally would be.

If you have properties you don’t want to be publicly available to other classes, you can always add them to your .m file in the private implementation. This is the @interface that’s declared at the top of a .m file and noted by the parentheses after the class name. For example, UIViewController() would be the private implementation of UIViewController.

Any @property declared in that interface can still be accessed as an IBOutlet (if properly annotated as such) in your Storyboard and within your .m implementation file, but it won't be accessible to any unrelated classes or to subclasses of your class.

Hooking Your Data to Your View

To hook up the data, update configureView in VehicleDetailViewController.m to take advantage of the vehicle set by the segue as follows:

- (void)configureView
{
    // Update the user interface for the detail vehicle, if it exists.
    if (self.detailVehicle) {
        //Set the View Controller title, which will display in the Navigation bar.
        self.title = [self.detailVehicle vehicleTitleString];
        
        //Setup the basic details string based on the properties in the base Vehicle class.
        NSMutableString *basicDetailsString = [NSMutableString string];
        [basicDetailsString appendString:@"Basic vehicle details:\n\n"];
        [basicDetailsString appendFormat:@"Brand name: %@\n", self.detailVehicle.brandName];
        [basicDetailsString appendFormat:@"Model name: %@\n", self.detailVehicle.modelName];
        [basicDetailsString appendFormat:@"Model year: %d\n", self.detailVehicle.modelYear];
        [basicDetailsString appendFormat:@"Power source: %@\n", self.detailVehicle.powerSource];
        [basicDetailsString appendFormat:@"# of wheels: %d", self.detailVehicle.numberOfWheels];
        
        self.vehicleDetailsLabel.text = basicDetailsString;
    }
}

Build and run your application; select a vehicle from the TableView and you'll see that the detail view is now showing the correct title and details, as so:

Basic vehicle details

Model-View-Controller Encapsulation of Logic

iOS and many other modern programming languages have a design pattern known as Model-View-Controller , or MVC for short.

The idea behind MVC is that views should only care about how they are presented, models should only care about their data, and controllers should work to marry the two without necessarily knowing too much about their internal structure.

The biggest benefit to the MVC pattern is making sure that if your data model changes, you only have to make changes once.

One of the biggest rookie mistakes that programmers make is putting far too much of the logic in their UIViewController classes. This ties views and UIViewControllers too closely to one particular type of model, making it harder to reuse views to display different kinds of details.

Why would you want to do implement the MVC pattern in your app? Well, imagine that you wanted to add more specific details about a car to VehicleDetailViewController. You could start by going back into configureView and adding some information specifically about the car, as so:

//Car-specific details
[basicDetailsString appendString:@"\n\nCar-Specific Details:\n\n"];
[basicDetailsString appendFormat:@"Number of doors: %d", self.detailVehicle.numberOfDoors];

But you’ll notice that there’s one minor problem with this:

Error with Car properties

VehicleDetailsViewController only knows about the properties of the main Vehicle superclass; it doesn't know anything anything about the Car subclass.

There are a couple of ways you can fix this.

The one that seems the most immediately obvious is to just import Car.h, so that VehicleDetailViewController knows about the properties of the Car subclass. But that would mean having to add a ton of logic to handle all the properties for every single subclass.

Any time you catch yourself doing that, ask yourself: "is my view controller trying to do too much?"

In this case, the answer is yes. You can take advantage of inheritance to use the same method to supply the string to be displayed for the appropriate details for each subclass.

Contributors

Over 300 content creators. Join our team.