Intermediate Design Patterns in Swift

Design patterns are incredibly useful for making code maintainable and readable. Learn design patterns in Swift with this hands on tutorial. By .

Leave a rating/review
Save for later
Share

Update 5/12/2015: Updated for Xcode 6.3 / Swift 1.2.

Beginner’s note: New to design patterns? Check out our two-part tutorial, Introducing iOS Design Patterns in Swift for the basics before reading on!

In this tutorial, you’ll learn to use design patterns in Swift to refactor a game called Tap the Larger Shape.

Having knowledge of design patterns is crucial to writing apps that are maintainable and bug free. Knowing when to employ each design pattern is a skill that you can only learn with practice. Where better to learn than this tutorial!

But what exactly is a design pattern? It’s a formally documented solution to a common problem. For example, consider the common problem of looping over a collection of items — the design pattern you use here is the Iterator design pattern:

var collection = ...

// The for loop condition uses the Iterator design pattern
for item in collection {
  println("Item is: \(item)")
}

The value of the Iterator design pattern is that it abstracts away the actual underlying mechanics of accessing items in the collection. Your code can access the items in a consistent manner regardless of whether collection is an array, dictionary or some other type.

Not only that, but design patterns are part of the developer culture, so another developer maintaining or extending your code will likely understand the Iterator design pattern. They serve as a language for reasoning about software architecture.

There are several design patterns that occur with great frequency in iOS programming, such as Model View Controller that appears in almost every app, and Delegation, a powerful, often underutilized, pattern that you certainly know if you’ve ever worked with table views. This tutorial discusses some lesser known but highly useful design patterns.

If you’re unfamiliar with the concept of design patterns, you might want to bookmark this page, and head over to iOS Design Patterns Tutorial for an introduction.

Getting Started

Tap the Larger Shape is a fun but simple game where you’re presented with a pair of similar shapes and you need to tap the larger of the two. If you tap the larger shape, you gain a point. If you tap the smaller shape, you lose a point.

It looks as though all that time you spent doodling random squares, circles and triangles as kid will finally pay off! :]

Get started by downloading the starter project and opening it in Xcode.

Note: You’ll want to use Xcode 6.3.1 and Swift 1.2 for maximum Swift compatibility and stability.

This starter project contains the full game. You’ll refactor the starter project throughout this tutorial and make use of design patterns to make your game more maintainable and more fun.

Build and run the project on the iPhone 5 simulator, and tap a few shapes to understand how the game plays. You should see something like the image below:

Tap the larger shape and gain points. :]

Tap the larger shape and gain points. :]

Tap the smaller shape and lose points. :[

Tap the smaller shape and lose points.  :[

Understanding the Game

Before getting into the details of design patterns, take a look at the game as it’s currently written. Open Shape.swift take a look around and find the following code. You don’t need to make any changes, just look:

import Foundation
import UIKit

class Shape {
}

class SquareShape: Shape {
  var sideLength: CGFloat!
}

The Shape class is the basic model for tappable shapes in the game. The concrete subclass SquareShape represents a square: a polygon with four equal-length sides.

Next, open ShapeView.swift and take a look at the code for ShapeView:

import Foundation
import UIKit

class ShapeView: UIView {
  var shape: Shape!

  // 1
  var showFill: Bool = true {
    didSet {
      setNeedsDisplay()
    }
  }
  var fillColor: UIColor = UIColor.orangeColor() {
    didSet {
      setNeedsDisplay()
    }
  }

  // 2
  var showOutline: Bool = true {
    didSet {
      setNeedsDisplay()
    }
  }
  var outlineColor: UIColor = UIColor.grayColor() {
    didSet {
      setNeedsDisplay()
    }
  }

  // 3
  var tapHandler: ((ShapeView) -> ())?

  override init(frame: CGRect) {
    super.init(frame: frame)

    // 4
    let tapRecognizer = UITapGestureRecognizer(target: self, action: Selector("handleTap"))
    addGestureRecognizer(tapRecognizer)
  }

  required init(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  func handleTap() {
  	// 5
    tapHandler?(self)
  }

  let halfLineWidth: CGFloat = 3.0
}
  

ShapeView is the view that renders a generic Shape model. Line by line, here’s what’s happening in that block:

  1. Indicate if the app should fill the shape with a color, and if so, which color. This is the solid interior color of the shape.
  2. Indicate if the app should stroke the shape’s outline with a color, and if so, which color. This is the color of the shape’s border.
  3. A closure that handles taps (e.g. to adjust the score). If you’re not familiar with Swift closures, you can review them in this Swift Functional Programming Tutorial, but keep in mind they’re similar to Objective C blocks.
  4. Set up a tap gesture recognizer that invokes handleTap when the player taps the view.
  5. Invoke the tapHandler when the gesture recognizer recognizes a tap gesture.

Now scroll down and examine SquareShapeView:

class SquareShapeView: ShapeView {
  override func drawRect(rect: CGRect) {
    super.drawRect(rect)

    // 1
    if showFill {
      fillColor.setFill()
      let fillPath = UIBezierPath(rect: bounds)
      fillPath.fill()
    }

    // 2
    if showOutline {
      outlineColor.setStroke()

      // 3
      let outlinePath = UIBezierPath(rect: CGRect(x: halfLineWidth, y: halfLineWidth, width: bounds.size.width - 2 * halfLineWidth, height: bounds.size.height - 2 * halfLineWidth))
      outlinePath.lineWidth = 2.0 * halfLineWidth
      outlinePath.stroke()
    }
  }
}

Here’s how SquareShapeView draws itself:

  1. If configured to show fill, then fill in the view with the fill color.
  2. If configured to show an outline, then outline the view with the outline color.
  3. Since iOS draws lines that are centered over their position, you need to inset the view bounds by halfLineWidth when stroking the path.

Excellent, now that you understand how the game draws its shapes, open GameViewController.swift and have a look at the game logic:

import UIKit

class GameViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()
    // 1
    beginNextTurn()
  }

  override func prefersStatusBarHidden() -> Bool {
    return true
  }

  private func beginNextTurn() {
    // 2
    let shape1 = SquareShape()
    shape1.sideLength = Utils.randomBetweenLower(0.3, andUpper: 0.8)
    let shape2 = SquareShape()
    shape2.sideLength = Utils.randomBetweenLower(0.3, andUpper: 0.8)

    // 3
    let availSize = gameView.sizeAvailableForShapes()

    // 4
    let shapeView1: ShapeView =
      SquareShapeView(frame: CGRect(x: 0,
                                    y: 0,
                                    width: availSize.width * shape1.sideLength,
                                    height: availSize.height * shape1.sideLength))
    shapeView1.shape = shape1
    let shapeView2: ShapeView =
      SquareShapeView(frame: CGRect(x: 0,
                                    y: 0,
                                    width: availSize.width * shape2.sideLength,
                                    height: availSize.height * shape2.sideLength))
    shapeView2.shape = shape2

    // 5
    let shapeViews = (shapeView1, shapeView2)

    // 6
    shapeViews.0.tapHandler = {
      tappedView in
      self.gameView.score += shape1.sideLength >= shape2.sideLength ? 1 : -1
      self.beginNextTurn()
    }
    shapeViews.1.tapHandler = {
      tappedView in
      self.gameView.score += shape2.sideLength >= shape1.sideLength ? 1 : -1
      self.beginNextTurn()
    }

    // 7
    gameView.addShapeViews(shapeViews)
  }

  private var gameView: GameView { return view as! GameView }
}

Here’s how the game logic works:

  1. Begin a turn as soon as the GameView loads.
  2. Create a pair of square shapes with random side lengths drawn as proportions in the range [0.3, 0.8]. The shapes will also scale to any screen size.
  3. Ask the GameView what size is available for each shape based on the current screen size.
  4. Create a SquareShapeView for each shape, and size the shape by multiplying the shape’s sideLength proportion by the appropriate availSize dimension of the current screen.
  5. Store the shapes in a tuple for easier manipulation.
  6. Set the tap handler on each shape view to adjust the score based on whether the player tapped the larger view or not.
  7. Add the shapes to the GameView so it can lay out the shapes and display them.

That’s it. That’s the complete game logic. Pretty simple, right? :]