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.

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

Creating Subclasses via Inheritance

First, go to Vehicle.swift and add a new computed property to get a details string:

var vehicleDetails: String {
  var details = "Basic vehicle details:\n\n"
  details += "Brand name: \(brandName)\n"
  details += "Model name: \(modelName)\n"
  details += "Model year: \(modelYear)\n"
  details += "Power source: \(powerSource)\n"
  details += "# of wheels: \(numberOfWheels)\n"
  return details
}

The method is similar to what you added to VehicleDetailViewController.swift, except it returns the generated string rather than display it somewhere directly.

Now, you can use inheritance to take this basic vehicle string and add the specific details for the Car class. Open Car.swift and add the override the property vehicleDetails:

override var vehicleDetails: String {
  // Get basic details from superclass
  let basicDetails = super.vehicleDetails
  
  // Initialize mutable string
  var carDetailsBuilder = "\n\nCar-Specific Details:\n\n"

  // String helpers for booleans
  let yes = "Yes\n"
  let no = "No\n"

  // Add info about car-specific features.
  carDetailsBuilder += "Has sunroof: "
  carDetailsBuilder += hasSunroof ? yes : no

  carDetailsBuilder += "Is Hatchback: "
  carDetailsBuilder += isHatchback ? yes : no

  carDetailsBuilder += "Is Convertible: "
  carDetailsBuilder += isConvertible ? yes : no

  carDetailsBuilder += "Number of doors: \(numberOfDoors)"

  // Create the final string by combining basic and car-specific details.
  let carDetails = basicDetails + carDetailsBuilder

  return carDetails
}

Car’s version of the method starts by calling the superclass’s implementation to get the vehicle details. It then builds the car-specific details string into carDetailsBuilder and then combines the two at the very end.

Now go back to VehicleDetailViewController.swift and replace configureView() with the much simpler implementation below to display this string that you’ve created:

func configureView() {
  // Update the user interface for the detail item.
  if let vehicle = detailVehicle {
    title = vehicle.vehicleTitle
    detailDescriptionLabel?.text = vehicle.vehicleDetails
  }
}

Build and run your application; select one of the cars, and you should now be able to see both general details and car-specific details as shown below:

Basic and car-specific details

Your VehicleDetailViewController class now allows the Vehicle and Car classes to determine the data to be displayed. The only thing VehicleDetailsViewController is doing is connecting that information up with the view!

The real power in this is evident when you create further subclasses of Vehicle. Start with a fairly simple one for a motorcycle.

Go to File\New\File… and select iOS\Source\Swift File. Name the file Motorcycle. Inside Motorcycle.swift, create a new subclass of Vehicle called Motorcycle as shown below:

class Motorcycle : Vehicle {

}

Since motorcycles can have either a deep booming engine noise, or a high-pitched whine of an engine noise, each Motorcycle object you create should specify which type of noise it makes. Add the following property and method to your new class:

  
var engineNoise = ""
  
override init() {
  super.init()
  numberOfWheels = 2
  powerSource = "gas engine"
}

Since all motorcycles have two wheels and are gas-powered (for the sake of this example, anything that’s electric-powered would be considered a scooter, not a motorcycle), you can set up the number of wheels and power source when the object is instantiated.

Next, add the following methods to override the superclass methods.

// MARK: - Superclass Overrides
override func goForward() -> String {
  return String(format: "%@ Open throttle.", changeGears("Forward"))
}

override func goBackward() -> String {
  return String(format: "%@ Walk %@ backwards using feet.", changeGears("Neutral"), modelName)
}
  
override func stopMoving() -> String {
  return "Squeeze brakes"
}

override func makeNoise() -> String {
  return self.engineNoise
}

Finally, override the vehicleDetails property in order to add the Motorcycle-specific details to the vehicleDetails as shown below:

override var vehicleDetails: String {
  //Get basic details from superclass
  let basicDetails = super.vehicleDetails
      
  //Initialize mutable string
  var motorcycleDetailsBuilder = "\n\nMotorcycle-Specific Details:\n\n"
      
  //Add info about motorcycle-specific features.
  motorcycleDetailsBuilder += "Engine Noise: \(engineNoise)"
      
  let motorcycleDetails = basicDetails + motorcycleDetailsBuilder
      
  return motorcycleDetails
}

Now, it’s time to create some instances of Motorcycle.

Open VehicleListTableViewController.swift, find setupVehicleArray() and add the following code below the Cars you’ve already added but above where you sort the array:

// Create a motorcycle
var harley = Motorcycle()
harley.brandName = "Harley-Davidson"
harley.modelName = "Softail"
harley.modelYear = 1979
harley.engineNoise = "Vrrrrrrrroooooooooom!"
    
// Add it to the array.
vehicles.append(harley)
    
// Create another motorcycle
var kawasaki = Motorcycle()
kawasaki.brandName = "Kawasaki"
kawasaki.modelName = "Ninja"
kawasaki.modelYear = 2005
kawasaki.engineNoise = "Neeeeeeeeeeeeeeeeow!"
    
// Add it to the array
self.vehicles.append(kawasaki)

The above code simply instantiates two Motorcycle objects and adds them to the vehicles array.

Build and run your application; you’ll see the two Motorcycle objects you added in the list:

Added Motorcycles

Tap on one of them, and you’ll be taken to the details for that Motorcycle, as shown below:

Motorcycle Details

Whether it’s a car or a motorcycle (or even a plain old vehicle), you can call vehicleDetails and get the relevant details.

By using proper separation between the model, the view, and the view controller and inheritance, you’re now able to display data for several subclasses of the same superclass without having to write tons of extra code to handle different subclass types. Less code written == happier developers! :]

Housing Logic in the Model Class

You can also use this approach to keep some of the more complicated logic encapsulated within the model class. Think about a Truck object: many different types of vehicles are referred to as a “truck”, ranging from pickup trucks to tractor-trailers. Your truck class will have a little bit of logic to change the truck’s behavior based on the number of cubic feet of cargo it can haul.

Go to File\New\File… and select iOS\Source\Swift File. Name the file Truck. Inside Truck.swift, create a new subclass of Vehicle called Truck as you did for Car and Motorcycle:

Add the following integer property to Truck.swift to hold the truck’s capacity data:

class Truck : Vehicle {
  var cargoCapacityCubicFeet: Int = 0
}

Since there are so many different types of trucks, you don’t need to create an initializer method that provides any of those details automatically. You can simply override the superclass methods which would be the same no matter the size of the truck.

//MARK: - Superclass overrides
override func goForward() -> String {
  return String(format:"%@ Depress gas pedal.", changeGears("Drive"))
}
  
override func stopMoving() -> String {
  return String(format:"Depress brake pedal. %@", changeGears("Park"))
}

Next, you’ll want to override a couple of methods so that the string returned is dependent on the amount of cargo the truck can haul. A big truck needs to sound a backup alarm, so you can create a private method.

Add the following private method code to your Truck class:

// MARK: - Private methods
private func soundBackupAlarm() -> String {
  return "Beep! Beep! Beep! Beep!"
}

Then back in the superclass overrides section, you can use that soundBackupAlarm() method to create a goBackward() method that sounds the alarm for big trucks:

override func goBackward() -> String {
  if cargoCapacityCubicFeet > 100 {
    // Sound a backup alarm first
    return String(format:"Wait for \"%@\", then %@", soundBackupAlarm(), changeGears("Reverse"))
  } else {
    return String(format:"%@ Depress gas pedal.", changeGears("Reverse"))
  }
}

Trucks also have very different horns; a pickup truck, for instance, would have a horn similar to that of a car, while progressively larger trucks have progressively louder horns. You can handle this with a switch statement in the makeNoise() method.

Add makeNoise() as follows:

override func makeNoise() -> String {
  switch numberOfWheels {
  case Int.min...4:
    return "Beep beep!"
  case 5...8:
    return "Honk!"
  default:
    return "HOOOOOOOOONK!"
  }
}

Finally, you can override the vehicleDetails property in order to get the appropriate details for your Truck object. Add the method as follows:

override var vehicleDetails: String {
  // Get basic details from superclass
  let basicDetails = super.vehicleDetails
  
  // Initialize mutable string
  var truckDetailsBuilder = "\n\nTruck-Specific Details:\n\n"
  
  // Add info about truck-specific features.
  truckDetailsBuilder += "Cargo Capacity: \(cargoCapacityCubicFeet) cubic feet"
  
  // Create the final string by combining basic and truck-specific details.
  let truckDetails = basicDetails + truckDetailsBuilder
  
  return truckDetails
}

Now that you’ve got your Truck object set up, you can create a few instances of it. Head back to VehicleListTableViewController.swift, find the setupVehicleArray() method and add the following code right above where you sort the array:

// Create a truck
var silverado = Truck()
silverado.brandName = "Chevrolet"
silverado.modelName = "Silverado"
silverado.modelYear = 2011
silverado.numberOfWheels = 4
silverado.cargoCapacityCubicFeet = 53
silverado.powerSource = "gas engine"
    
// Add it to the array
vehicles.append(silverado)
    
// Create another truck
var eighteenWheeler = Truck()
eighteenWheeler.brandName = "Peterbilt"
eighteenWheeler.modelName = "579"
eighteenWheeler.modelYear = 2013
eighteenWheeler.numberOfWheels = 18
eighteenWheeler.cargoCapacityCubicFeet = 408
eighteenWheeler.powerSource = "diesel engine"

// Add it to the array
vehicles.append(eighteenWheeler)

This will create a couple of Truck objects with the truck-specific properties to join the cars and motorcycles in the array.

Build and run you application; select one of the Trucks to verify that you can now see the appropriate Truck-specific details, as demonstrated below:

Truck-specific Details

That looks pretty great! The truck details are coming through thanks to the vehicleDetails computed property, inheritance, and overridden implementations.

Contributors

Ray Fix

Author

Over 300 content creators. Join our team.