Intro to Object-Oriented Design in Swift: Part 1/2
Learn the basics of object-oriented design in Swift. In this first part, you’ll learn about objects and classes, inheritance, and the Model-View-Controller relationship. By Ray Fix.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Intro to Object-Oriented Design in Swift: Part 1/2
35 mins
- Getting Started
- The Basics Of Objects
- Minor Digression: Initialization and Properties
- Minor Digression: Class versus Struct
- Describing the Object
- A Minor Digression: Class Methods vs. Instance Methods
- Adding Basic Methods to Your Class
- Inheritance
- Overriding Methods
- Building out the User Interface
- Hooking Your Data to Your View
- Model-View-Controller Encapsulation of Logic
- Creating Subclasses via Inheritance
- Housing Logic in the Model Class
- Where To Go From Here?
Building out the User Interface
Open VehicleListTableViewController.swift and add the following method just after viewDidLoad
:
// MARK: - Data setup
func setupVehicleArray() {
// Clear the array. (Start from scratch.)
vehicles.removeAll(keepCapacity: true)
// Create a car.
var mustang = Car()
mustang.brandName = "Ford"
mustang.modelName = "Mustang"
mustang.modelYear = 1968
mustang.isConvertible = true
mustang.isHatchback = false
mustang.hasSunroof = false
mustang.numberOfDoors = 2
mustang.powerSource = "gas engine"
// Add it to the array
vehicles.append(mustang)
// Create another car.
var outback = Car()
outback.brandName = "Subaru"
outback.modelName = "Outback"
outback.modelYear = 1999
outback.isConvertible = false
outback.isHatchback = true
outback.hasSunroof = false
outback.numberOfDoors = 5
outback.powerSource = "gas engine"
// Add it to the array.
vehicles.append(outback)
// Create another car
var prius = Car()
prius.brandName = "Toyota"
prius.modelName = "Prius"
prius.modelYear = 2002
prius.hasSunroof = true
prius.isConvertible = false
prius.isHatchback = true
prius.numberOfDoors = 4
prius.powerSource = "hybrid engine"
// Add it to the array.
vehicles.append(prius)
// Sort the array by the model year
vehicles.sort { $0.modelYear < $1.modelYear }
}
This simply adds a data setup method to construct your vehicle array. Each Car is created and customized based on the values set on its properties.
In viewDidLoad()
add the following after its super.viewDidLoad()
:
setupVehicleArray()
title = "Vehicles"
The above method executes just after your view loads from the Storyboard. 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 like the following:
Your table view is populated with three entries of "Vehicle.Car". Vehicle is the name of the module (app, in this case) and Car is the name of the class.
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:)
:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
let vehicle = vehicles[indexPath.row]
cell.textLabel?.text = "\(vehicle)"
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 string representation as the text for the cell’s textLabel
.
The default string produced method 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 to Vehicle.swift and add the following property below your other properties:
// MARK: - Computed Properties
var vehicleTitle: String {
return String(format:"%d %@ %@", modelYear, brandName, modelName)
}
Until now, you have been using stored properties to keep track of attributes associated with your objects. This is an example of a computed property. This is a read-only property that isn't backed by a variable; instead, it runs the code inside braces and generates a fresh string every time.
Go back to VehicleListTableViewController.swift and update tableView(_:cellForRowAtIndexPath:)
to use this new property on Vehicle
as follows:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
let vehicle = vehicles[indexPath.row] as Vehicle
cell.textLabel?.text = vehicle.vehicleTitle
return cell
}
Build and run your application; it should look a little nicer now:
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:
What’s up with that?
Open VehicleDetailViewController.swift; you’ll see that while the UI was constructed in the Storyboard and all the @IBOutlets
are already hooked up to save you some time fighting with AutoLayout, none of the data is hooked up.
Hooking Your Data to Your View
To hook up the data, go to VehicleDetailViewController.swift and fill in the method configureView()
to take advantage of the vehicle set by the segue as follows:
func configureView() {
// Update the user interface for the detail item.
if let vehicle = detailVehicle {
title = vehicle.vehicleTitle
var basicDetails = "Basic vehicle details:\n\n"
basicDetails += "Brand name: \(vehicle.brandName)\n"
basicDetails += "Model name: \(vehicle.modelName)\n"
basicDetails += "Model year: \(vehicle.modelYear)\n"
basicDetails += "Power source: \(vehicle.powerSource)\n"
basicDetails += "# of wheels: \(vehicle.numberOfWheels)\n"
detailDescriptionLabel?.text = basicDetails
}
}
Build and run your application; select a vehicle from the table view and you’ll see that the detail view is now showing the correct title and details, as so:
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
basicDetails += "\n\nCar-Specific Details:\n\n"
basicDetails += "Number of doors: \(vehicle.numberOfDoors)" // ERROR
But you’ll notice that this does not work because the compiler doesn’t know what the property numberOfDoors
is. That property is associated with the Car
subclass and not part of the Vehicle
base class.
There are a couple of ways you can fix this.
One way is to have the view controller attempt to downcast the vehicle
to a Car
type using the as? Car
operation. If it succeeds, access the Car
properties and display them.
Any time you catch yourself downcasting, 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. This way the view controllers doesn’t need to concern itself with the details of specific subclasses.