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 .
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
Intermediate Design Patterns in Swift
50 mins
- Getting Started
- Understanding the Game
- Why Use Design Patterns?
- Design Pattern: Abstract Factory
- Design Pattern: Servant
- Leveraging Abstract Factory for Gameplay Versatility
- Design Pattern: Builder
- Design Pattern: Dependency Injection
- Design Pattern: Strategy
- Design Patterns: Chain of Responsibility, Command and Iterator
- Where To Go From Here?
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.
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:
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:
- 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.
- 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.
- 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.
- Set up a tap gesture recognizer that invokes
handleTap
when the player taps the view. - 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:
- If configured to show fill, then fill in the view with the fill color.
- If configured to show an outline, then outline the view with the outline color.
- 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:
- Begin a turn as soon as the
GameView
loads. - 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. - Ask the
GameView
what size is available for each shape based on the current screen size. - Create a
SquareShapeView
for each shape, and size the shape by multiplying the shape’ssideLength
proportion by the appropriateavailSize
dimension of the current screen. - Store the shapes in a tuple for easier manipulation.
- Set the tap handler on each shape view to adjust the score based on whether the player tapped the larger view or not.
- 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? :]