Intermediate Design Patterns In iOS

Design Pattern: Abstract Factory

GameViewController is tightly coupled with the SquareShapeView, and that doesn’t allow much room to later use a different view to represent squares or introduce a second shape.

Your first task is to decouple and simplify your GameViewController using the Abstract Factory design pattern. You’re going to use this pattern in code that establishes an API for constructing a group of related objects, like the shape views you’ll work with momentarily, without hard-coding specific classes.

Click File\New\File… and then select iOS\Source\Swift File. Call the file ShapeViewFactory.swift, save it and then replace its contents with the code below:

import Foundation
import UIKit

// 1
protocol ShapeViewFactory {
  // 2
  var size: CGSize { get set }
  // 3
  func makeShapeViewsForShapes(shapes: (Shape, Shape)) -> (ShapeView, ShapeView)
}

Here’s how your new factory works:

  1. Define ShapeViewFactory as a Swift protocol. There’s no reason for it to be a class or struct since it only describes an interface and has no functionality itself.
  2. Each factory should have a size that defines the bounding box of the shapes it creates. This is essential to layout code using the factory-produced views.
  3. Define the method that produces shape views. This is the “meat” of the factory. It takes a tuple of two Shape objects and returns a tuple of two ShapeView objects. This essentially manufactures views from its raw materials — the models.

Add the following code to end of ShapeViewFactory.swift:

class SquareShapeViewFactory: ShapeViewFactory {
  var size: CGSize

  // 1
  init(size: CGSize) {
    self.size = size
  }

  func makeShapeViewsForShapes(shapes: (Shape, Shape)) -> (ShapeView, ShapeView) {
    // 2
    let squareShape1 = shapes.0 as! SquareShape
    let shapeView1 = 
      SquareShapeView(frame: CGRect(x: 0,
                                    y: 0,
                                    width: squareShape1.sideLength * size.width,
                                    height: squareShape1.sideLength * size.height))
    shapeView1.shape = squareShape1

    // 3
    let squareShape2 = shapes.1 as! SquareShape
    let shapeView2 =
      SquareShapeView(frame: CGRect(x: 0,
                                    y: 0,
                                    width: squareShape2.sideLength * size.width,
                                    height: squareShape2.sideLength * size.height))
    shapeView2.shape = squareShape2

    // 4
    return (shapeView1, shapeView2)
  }
}

Your SquareShapeViewFactory produces SquareShapeView instances as follows:

  1. Initialize the factory to use a consistent maximum size.
  2. Construct the first shape view from the first passed shape.
  3. Construct the second shape view from the second passed shape.
  4. Return a tuple containing the two created shape views.

Finally, it’s time to put SquareShapeViewFactory to use. Open GameViewController.swift, and replace its contents with the following:

import UIKit

class GameViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()

    // 1 ***** ADDITION
    shapeViewFactory = SquareShapeViewFactory(size: gameView.sizeAvailableForShapes())

    beginNextTurn()
  }

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

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

    // 2 ***** ADDITION
    let shapeViews = shapeViewFactory.makeShapeViewsForShapes((shape1, shape2))

    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()
    }

    gameView.addShapeViews(shapeViews)
  }

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

  // 3 ***** ADDITION
  private var shapeViewFactory: ShapeViewFactory!
}

There are three new lines of code:

  1. Initialize and store a SquareShapeViewFactory.
  2. Use this new factory to create your shape views.
  3. Store your new shape view factory as an instance property.

The key benefits are in section two, where you replaced six lines of code with one. Better yet, you moved the complex shape view creation code out of GameViewController to make the class smaller and easier to follow.

It’s helpful to move view creation code out of your view controller since GameViewController acts as a view controller and coordinates between model and view.

Nothing about your game’s visuals changed, but you did simplify your code.

If you were to replace SquareShapeView with SomeOtherShapeView, then the benefits of the SquareShapeViewFactory would shine. Specifically, you wouldn’t need to alter GameViewController, and you could isolate all the changes to SquareShapeViewFactory.

Now that you’ve simplified the creation of shape views, you’re going to simplify the creation of shapes. Create a new Swift file like before, called ShapeFactory.swift, and paste in the following code:

import Foundation
import UIKit

// 1
protocol ShapeFactory {
  func createShapes() -> (Shape, Shape)
}

class SquareShapeFactory: ShapeFactory {
  // 2
  var minProportion: CGFloat
  var maxProportion: CGFloat

  init(minProportion: CGFloat, maxProportion: CGFloat) {
    self.minProportion = minProportion
    self.maxProportion = maxProportion
  }

  func createShapes() -> (Shape, Shape) {
    // 3
    let shape1 = SquareShape()
    shape1.sideLength = Utils.randomBetweenLower(minProportion, andUpper: maxProportion)

    // 4
    let shape2 = SquareShape()
    shape2.sideLength = Utils.randomBetweenLower(minProportion, andUpper: maxProportion)

    // 5
    return (shape1, shape2)
  }
}

Your new ShapeFactory produces shapes as follows:

  1. Again, you’ve declared the ShapeFactory as a protocol to build in maximum flexibility, just like you did for ShapeViewFactory.
  2. You want your shape factory to produce shapes that have dimensions in unit terms, for instance, in a range like [0, 1] — so you store this range.
  3. Create the first square shape with random dimensions.
  4. Create the second square shape with random dimensions.
  5. Return the pair of square shapes as a tuple.

Now open GameViewController.swift and insert the following line at the bottom just before the closing curly brace:

private var shapeFactory: ShapeFactory!

Then insert the following line near the bottom of viewDidLoad, just above the invocation of beginNextTurn:

shapeFactory = SquareShapeFactory(minProportion: 0.3, maxProportion: 0.8)

Finally, replace beginNextTurn with this code:

private func beginNextTurn() {
  // 1
  let shapes = shapeFactory.createShapes()

  let shapeViews = shapeViewFactory.makeShapeViewsForShapes(shapes)

  shapeViews.0.tapHandler = {
    tappedView in
    // 2
    let square1 = shapes.0 as! SquareShape, square2 = shapes.1 as! SquareShape
    // 3
    self.gameView.score += square1.sideLength >= square2.sideLength ? 1 : -1
    self.beginNextTurn()
  }
  shapeViews.1.tapHandler = {
    tappedView in
    let square1 = shapes.0 as! SquareShape, square2 = shapes.1 as! SquareShape
    self.gameView.score += square2.sideLength >= square1.sideLength ? 1 : -1
    self.beginNextTurn()
  }

  gameView.addShapeViews(shapeViews)
}

Section by section, here’s what that does.

  1. Use your new shape factory to create a tuple of shapes.
  2. Extract the shapes from the tuple…
  3. …so that you can compare them here.

Once again, using the Abstract Factory design pattern simplified your code by moving shape generation out of GameViewController.

Design Pattern: Servant

At this point you can almost add a second shape, for example, a circle. Your only hard-coded dependence on squares is in the score calculation in beginNextTurn in code like the following:

shapeViews.1.tapHandler = {
  tappedView in
  // 1
  let square1 = shapes.0 as! SquareShape, square2 = shapes.1 as! SquareShape

  // 2
  self.gameView.score += square2.sideLength >= square1.sideLength ? 1 : -1
  self.beginNextTurn()
}

Here you cast the shapes to SquareShape so that you can access their sideLength. Circles don’t have a sideLength, instead they have a diameter.

The solution is to use the Servant design pattern, which provides a behavior like score calculation to a group of classes like shapes, via a common interface. In your case, the score calculation will be the servant, the shapes will be the serviced classes, and an area property plays the role of the common interface.

Open Shape.swift and add the following line to the bottom of the Shape class:

var area: CGFloat { return 0 }

Then add the following line to the bottom of the SquareShape class:

override var area: CGFloat { return sideLength * sideLength }

You can see where this is going — you can calculate which shape is larger based on its area.

Open GameViewController.swift and replace beginNextTurn with the following:

private func beginNextTurn() {
  let shapes = shapeFactory.createShapes()

  let shapeViews = shapeViewFactory.makeShapeViewsForShapes(shapes)

  shapeViews.0.tapHandler = {
    tappedView in
    // 1
    self.gameView.score += shapes.0.area >= shapes.1.area ? 1 : -1
    self.beginNextTurn()
  }
  shapeViews.1.tapHandler = {
    tappedView in
    // 2
    self.gameView.score += shapes.1.area >= shapes.0.area ? 1 : -1
    self.beginNextTurn()
  }

  gameView.addShapeViews(shapeViews)
}
  1. Determines the larger shape based on the shape area.
  2. Also determines the larger shape based on the shape area.

Build and run, and you should see something like the following — the game looks the same, but the code is now more flexible.

Congratulations, you’ve completely removed dependencies on squares from your game logic. If you were to create and use some circle factories, your game would become more…well-rounded. :]

Leveraging Abstract Factory for Gameplay Versatility

“Don’t be a square!” can be an insult in real life, and your game feels like it’s been boxed in to one shape — it aspires to smoother lines and more aerodynamic shapes

You need to introduce some smooth “circley goodness.” Open Shape.swift, and then add the following code at the bottom of the file:

class CircleShape: Shape {
    var diameter: CGFloat!
    override var area: CGFloat { return CGFloat(M_PI) * diameter * diameter / 4.0 }
}

Your circle only needs to know the diameter from which it can compute its area, and thus support the Servant pattern.

Next, build CircleShape objects by adding a CircleShapeFactory. Open ShapeFactory.swift, and add the following code at the bottom of the file:

class CircleShapeFactory: ShapeFactory {
  var minProportion: CGFloat
  var maxProportion: CGFloat

  init(minProportion: CGFloat, maxProportion: CGFloat) {
    self.minProportion = minProportion
    self.maxProportion = maxProportion
  }

  func createShapes() -> (Shape, Shape) {
    // 1
    let shape1 = CircleShape()
    shape1.diameter = Utils.randomBetweenLower(minProportion, andUpper: maxProportion)

    // 2
    let shape2 = CircleShape()
    shape2.diameter = Utils.randomBetweenLower(minProportion, andUpper: maxProportion)

    return (shape1, shape2)
  }
}

This code follows a familiar pattern: Section 1 and Section 2 create a CircleShape and assign it a random diameter.

You need to solve another problem, and doing so might just prevent a messy Geometry Revolution. See, what you have right now is “Geometry Without Representation,” and you know how wound up shapes can get when they feel underrepresented. (haha!)

It’s easy to please your constituents; all you need to is represent your new CircleShape objects on the screen with a CircleShapeView. :]

Open ShapeView.swift and add the following at the bottom of the file:

class CircleShapeView: ShapeView {
  override init(frame: CGRect) {
    super.init(frame: frame)
    // 1
    self.opaque = false
    // 2
    self.contentMode = UIViewContentMode.Redraw
  }

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

  override func drawRect(rect: CGRect) {
    super.drawRect(rect)

    if showFill {
      fillColor.setFill()
      // 3
      let fillPath = UIBezierPath(ovalInRect: self.bounds)
      fillPath.fill()
    }

    if showOutline {
      outlineColor.setStroke()
      // 4
      let outlinePath = UIBezierPath(ovalInRect: CGRect(
        x: halfLineWidth,
        y: halfLineWidth,
        width: self.bounds.size.width - 2 * halfLineWidth,
        height: self.bounds.size.height - 2 * halfLineWidth))
      outlinePath.lineWidth = 2.0 * halfLineWidth
      outlinePath.stroke()
    }
  }
}

Explanations of the above that take each section in turn:

  1. Since a circle cannot fill the rectangular bounds of its view, you need to tell UIKit that the view is not opaque, meaning content behind it may poke through. If you miss this, then the circles will have an ugly black background.
  2. Because the view is not opaque, you should redraw the view when its bounds change.
  3. Draw a circle filled with the fillColor. In a moment, you’ll create CircleShapeViewFactory, which will ensurethat CircleView has equal width and height so the shape will be a circle and not an ellipse.
  4. Stroke the outline border of the circle and inset to account for line width.

Now you’ll create CircleShapeView objects in a CircleShapeViewFactory.

Open ShapeViewFactory.swift and add the following code at the bottom of the file:

class CircleShapeViewFactory: ShapeViewFactory {
  var size: CGSize

  init(size: CGSize) {
    self.size = size
  }

  func makeShapeViewsForShapes(shapes: (Shape, Shape)) -> (ShapeView, ShapeView) {
    let circleShape1 = shapes.0 as! CircleShape
    // 1
    let shapeView1 = CircleShapeView(frame: CGRect(
      x: 0,
      y: 0,
      width: circleShape1.diameter * size.width,
      height: circleShape1.diameter * size.height))
    shapeView1.shape = circleShape1

    let circleShape2 = shapes.1 as! CircleShape
    // 2
    let shapeView2 = CircleShapeView(frame: CGRect(
      x: 0,
      y: 0,
      width: circleShape2.diameter * size.width,
      height: circleShape2.diameter * size.height))
    shapeView2.shape = circleShape2

    return (shapeView1, shapeView2)
  }
}

This is the factory that will create circles instead of squares. Section 1 and Section 2 are creating CircleShapeView instances by using the passed in shapes. Notice how your code is makes sure the circles have equal width and height so they render as perfect circles and not ellipses.

Finally, open GameViewController.swift and replace the lines in viewDidLoad that assign the shape and view factories with the following:

shapeViewFactory = CircleShapeViewFactory(size: gameView.sizeAvailableForShapes())
shapeFactory = CircleShapeFactory(minProportion: 0.3, maxProportion: 0.8)

Now build and run and you should see something like the following screenshot.
Lookee there. You made circles!

Notice how you were able to add a new shape without much impact on your game’s logic in GameViewController? The Abstract Factory and Servant design patterns made this possible.

Design Pattern: Builder

Now it’s time to examine a third design pattern: Builder.

Suppose you want to vary the appearance of your ShapeView instances — whether they should show fill and outline colors and what colors to use. The Builder design pattern makes such object configuration easier and more flexible.

One approach to solve this configuration problem would be to add a variety of constructors, either class convenience methods like CircleShapeView.redFilledCircleWithBlueOutline() or initializers with a variety of arguments and default values.

Unfortunately, it’s not a scalable technique as you’d need to write a new method or initializer for every combination.

Builder solves this problem rather elegantly because it creates a class with a single purpose — configure an already initialized object. If you set up your builder to build red circles and then later blue circles, it’ll do so without need to alter CircleShapeView.

Create a new file ShapeViewBuilder.swift and replace its contents with the following code:

import Foundation
import UIKit

class ShapeViewBuilder {
  // 1
  var showFill  = true
  var fillColor = UIColor.orangeColor()

  // 2
  var showOutline  = true
  var outlineColor = UIColor.grayColor()

  // 3
  init(shapeViewFactory: ShapeViewFactory) {
    self.shapeViewFactory = shapeViewFactory
  }

  // 4
  func buildShapeViewsForShapes(shapes: (Shape, Shape)) -> (ShapeView, ShapeView) {
    let shapeViews = shapeViewFactory.makeShapeViewsForShapes(shapes)
    configureShapeView(shapeViews.0)
    configureShapeView(shapeViews.1)
    return shapeViews
  }

  // 5
  private func configureShapeView(shapeView: ShapeView) {
    shapeView.showFill  = showFill
    shapeView.fillColor = fillColor
    shapeView.showOutline  = showOutline
    shapeView.outlineColor = outlineColor
  }

  private var shapeViewFactory: ShapeViewFactory
}

Here’s how your new ShapeViewBuilder works:

  1. Store configuration to set ShapeView fill properties.
  2. Store configuration to set ShapeView outline properties.
  3. Initialize the builder to hold a ShapeViewFactory to construct the views. This means the builder doesn’t need to know if it’s building SquareShapeView or CircleShapeView or even some other kind of shape view.
  4. This is the public API; it creates and initializes a pair of ShapeView when there’s a pair of Shape.
  5. Do the actual configuration of a ShapeView based on the builder’s stored configuration.

Deploying your spiffy new ShapeViewBuilder is as easy as opening GameViewController.swift and adding the following code to the bottom of the class, just before the closing curly brace:

private var shapeViewBuilder: ShapeViewBuilder!

Now, populate your new property by adding the following code to viewDidLoad just above the line that invokes beginNextTurn:

shapeViewBuilder = ShapeViewBuilder(shapeViewFactory: shapeViewFactory)
shapeViewBuilder.fillColor = UIColor.brownColor()
shapeViewBuilder.outlineColor = UIColor.orangeColor()

Finally replace the line that creates shapeViews in beginNextTurn with the following:

let shapeViews = shapeViewBuilder.buildShapeViewsForShapes(shapes)

Build and run, and you should see something like this:

Notice how your circles are now a pleasant brown with orange outlines — I know you must be amazed by the stunning design here, but please don’t try to hire me to be your interior decorator. ;]

Now to reinforce the power of the Builder pattern. With GameViewController.swift still open, change your viewDidLoad to use square factories:

shapeViewFactory = SquareShapeViewFactory(size: gameView.sizeAvailableForShapes())
shapeFactory = SquareShapeFactory(minProportion: 0.3, maxProportion: 0.8)

Build and run, and you should see this.

Notice how the Builder pattern made it easy to apply a new color scheme to squares as well as to circles. Without it, you’d need color configuration code in both CircleShapeViewFactory and SquareShapeViewFactory.

Furthermore, changing to another color scheme would involve widespread code changes. By restricting ShapeView color configuration to a single ShapeViewBuilder, you also isolate color changes to a single class.

Design Pattern: Dependency Injection

Every time you tap a shape, you’re taking a turn in your game, and each turn can be a match or not a match.

Wouldn’t it be helpful if your game could track all the turns, stats and award point bonuses for hot streaks?

Create a new file called Turn.swift, and replace its contents with the following code:

import Foundation

class Turn {
  // 1
  let shapes: [Shape]
  var matched: Bool?

  init(shapes: [Shape]) {
    self.shapes = shapes
  }

  // 2
  func turnCompletedWithTappedShape(tappedShape: Shape) {
    var maxArea = shapes.reduce(0) { $0 > $1.area ? $0 : $1.area }
    matched = tappedShape.area >= maxArea
  }
}

Your new Turn class does the following:

  1. Store the shapes that the player saw during the turn, and also whether the turn was a match or not.
  2. Records the completion of a turn after a player taps a shape.

To control the sequence of turns your players play, create a new file named TurnController.swift, and replace its contents with the following code:

import Foundation

class TurnController {
  // 1
  var currentTurn: Turn?
  var pastTurns: [Turn] = [Turn]()

  // 2
  init(shapeFactory: ShapeFactory, shapeViewBuilder: ShapeViewBuilder) {
    self.shapeFactory = shapeFactory
    self.shapeViewBuilder = shapeViewBuilder
  }

  // 3
  func beginNewTurn() -> (ShapeView, ShapeView) {
    let shapes = shapeFactory.createShapes()
    let shapeViews = shapeViewBuilder.buildShapeViewsForShapes(shapes)
    currentTurn = Turn(shapes: [shapeViews.0.shape, shapeViews.1.shape])
    return shapeViews
  }

  // 4
  func endTurnWithTappedShape(tappedShape: Shape) -> Int {
    currentTurn!.turnCompletedWithTappedShape(tappedShape)
    pastTurns.append(currentTurn!)

    var scoreIncrement = currentTurn!.matched! ? 1 : -1

    return scoreIncrement
  }

  private let shapeFactory: ShapeFactory
  private var shapeViewBuilder: ShapeViewBuilder
}

Your TurnController works as follows:

  1. Stores both the current turn and past turns.
  2. Accepts a ShapeFactory and ShapeViewBuilder.
  3. Uses this factory and builder to create shapes and views for each new turn and records the current turn.
  4. Records the end of a turn after the player taps a shape, and returns the computed score based on whether the turn was a match or not.

Now open GameViewController.swift, and add the following code at the bottom, just above the closing curly brace:

private var turnController: TurnController!

Scroll up to viewDidLoad, and just before the line invoking beginNewTurn, insert the following code:

turnController = TurnController(shapeFactory: shapeFactory, shapeViewBuilder: shapeViewBuilder)

Replace beginNextTurn with the following:

private func beginNextTurn() {
  // 1
  let shapeViews = turnController.beginNewTurn()

  shapeViews.0.tapHandler = {
    tappedView in
    // 2
    self.gameView.score += self.turnController.endTurnWithTappedShape(tappedView.shape)
    self.beginNextTurn()
  }

  // 3
  shapeViews.1.tapHandler = shapeViews.0.tapHandler

  gameView.addShapeViews(shapeViews)
}

Your new code works as follows:

  1. Asks the TurnController to begin a new turn and return a tuple of ShapeView to use for the turn.
  2. Informs the turn controller that the turn is over when the player taps a ShapeView, and then it increments the score. Notice how TurnController abstracts score calculation away, further simplifying GameViewController.
  3. Since you removed explicit references to specific shapes, the second shape view can share the same tapHandler closure as the first shape view.

An example of the Dependency Injection design pattern is that it passes in its dependencies to the TurnController initializer. The initializer parameters essentially inject the shape and shape view factory dependencies.

Since TurnController makes no assumptions about which type of factories to use, you’re free to swap in different factories.

Not only does this make your game more flexible, but it makes automated testing easier since it allows you to pass in special TestShapeFactory and TestShapeViewFactory classes if you desire. These could be special stubs or mocks that would make testing easier, more reliable or faster.

Build and run and check that it looks like this:

There are no visual differences, but TurnController has opened up your code so it can use more sophisticated turn strategies: calculating scores based on streaks of turns, alternating shape type between turns, or even adjusting the difficulty of play based on the player’s performance.

Design Pattern: Strategy

I’m happy because I’m eating a piece of pie while writing this tutorial. Perhaps that’s why it was imperative to add circles to the game. :]

You should be happy because you’ve done a great job using design patterns to refactor your game code so that it’s easy to expand and maintain.

Speaking of pie, err, Pi, how do you get those circles back in your game? Right now your GameViewController can use either circles or squares, but only one or the other. It doesn’t have to be all restrictive like that.

Next, you’ll use the Strategy design pattern to manage which shapes your game produces.

The Strategy design pattern allows you to design algorithm behaviors based on what your program determines at runtime. In this case, the algorithm will choose which shapes to present to the player.

You can design many different algorithms: one that picks shapes randomly, one that picks shapes to challenge the player or help him be more successful, and so on. Strategy works by defining a family of algorithms through abstract declarations of the behavior that each strategy must implement. This makes the algorithms within the family interchangeable.

If you guessed that you’re going to implement the Strategy as a Swift protocol, you guessed correctly!

Create a new file named TurnStrategy.swift, and replace its contents with the following code:

import Foundation

// 1
protocol TurnStrategy {
  func makeShapeViewsForNextTurnGivenPastTurns(pastTurns: [Turn]) -> (ShapeView, ShapeView)
}

// 2
class BasicTurnStrategy: TurnStrategy {
  let shapeFactory: ShapeFactory
  let shapeViewBuilder: ShapeViewBuilder

  init(shapeFactory: ShapeFactory, shapeViewBuilder: ShapeViewBuilder) {
    self.shapeFactory = shapeFactory
    self.shapeViewBuilder = shapeViewBuilder
  }

  func makeShapeViewsForNextTurnGivenPastTurns(pastTurns: [Turn]) -> (ShapeView, ShapeView) {
    return shapeViewBuilder.buildShapeViewsForShapes(shapeFactory.createShapes())
  }
}

class RandomTurnStrategy: TurnStrategy {
  // 3
  let firstStrategy: TurnStrategy
  let secondStrategy: TurnStrategy

  init(firstStrategy: TurnStrategy, secondStrategy: TurnStrategy) {
    self.firstStrategy = firstStrategy
    self.secondStrategy = secondStrategy
  }

  // 4
  func makeShapeViewsForNextTurnGivenPastTurns(pastTurns: [Turn]) -> (ShapeView, ShapeView) {
    if Utils.randomBetweenLower(0.0, andUpper: 100.0) < 50.0 {
      return firstStrategy.makeShapeViewsForNextTurnGivenPastTurns(pastTurns)
    } else {
      return secondStrategy.makeShapeViewsForNextTurnGivenPastTurns(pastTurns)
    }
  }
}

Here’s what your new TurnStrategy does line-by-line:

  1. Declare the behavior of the algorithm. This is defined in a protocol, with one method. The method takes an array of the past turns in the game, and returns the shape views to display for the next turn.
  2. Implement a basic strategy that uses a ShapeFactory and ShapeViewBuilder. This strategy implements the existing behavior, where the shape views just come from the single factory and builder as before. Notice how you’re using Dependency Injection again here, and that means this strategy doesn’t care which factory or builder it’s using.
  3. Implement a random strategy which randomly uses one of two other strategies. You’ve used composition here so that RandomTurnStrategy can behave like two potentially different strategies. However, since it’s a Strategy, that composition is hidden from whatever code uses RandomTurnStrategy.
  4. This is the meat of the random strategy. It randomly selects either the first or second strategy with a 50 percent chance.

Now you need to use your strategies. Open TurnController.swift, and replace its contents with the following:

import Foundation

class TurnController {
  var currentTurn: Turn?
  var pastTurns: [Turn] = [Turn]()

  // 1
  init(turnStrategy: TurnStrategy) {
    self.turnStrategy = turnStrategy
  }

  func beginNewTurn() -> (ShapeView, ShapeView) {
    // 2
    let shapeViews = turnStrategy.makeShapeViewsForNextTurnGivenPastTurns(pastTurns)
    currentTurn = Turn(shapes: [shapeViews.0.shape, shapeViews.1.shape])
    return shapeViews
  }

  func endTurnWithTappedShape(tappedShape: Shape) -> Int {
    currentTurn!.turnCompletedWithTappedShape(tappedShape)
    pastTurns.append(currentTurn!)

    var scoreIncrement = currentTurn!.matched! ? 1 : -1

    return scoreIncrement
  }

  private let turnStrategy: TurnStrategy
}

Here’s what’s happening, section by section:

  1. Accepts a passed strategy and stores it on the TurnController instance.
  2. Uses the strategy to generate the ShapeView objects so the player can begin a new turn.

Note: This will cause a syntax error in GameViewController.swift. Don’t worry, it’s only temporary. You’re going to fix the error in the very next step.

Your last step to use the Strategy design pattern is to adapt your GameViewController to use your TurnStrategy.

Open GameViewController.swift and replace its contents with the following:

import UIKit

class GameViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()

    // 1
    let squareShapeViewFactory = SquareShapeViewFactory(size: gameView.sizeAvailableForShapes())
    let squareShapeFactory = SquareShapeFactory(minProportion: 0.3, maxProportion: 0.8)
    let squareShapeViewBuilder = shapeViewBuilderForFactory(squareShapeViewFactory)
    let squareTurnStrategy = BasicTurnStrategy(shapeFactory: squareShapeFactory, shapeViewBuilder: squareShapeViewBuilder)

    // 2
    let circleShapeViewFactory = CircleShapeViewFactory(size: gameView.sizeAvailableForShapes())
    let circleShapeFactory = CircleShapeFactory(minProportion: 0.3, maxProportion: 0.8)
    let circleShapeViewBuilder = shapeViewBuilderForFactory(circleShapeViewFactory)
    let circleTurnStrategy = BasicTurnStrategy(shapeFactory: circleShapeFactory, shapeViewBuilder: circleShapeViewBuilder)

    // 3
    let randomTurnStrategy = RandomTurnStrategy(firstStrategy: squareTurnStrategy, secondStrategy: circleTurnStrategy)

    // 4
    turnController = TurnController(turnStrategy: randomTurnStrategy)

    beginNextTurn()
  }

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

  private func shapeViewBuilderForFactory(shapeViewFactory: ShapeViewFactory) -> ShapeViewBuilder {
    let shapeViewBuilder = ShapeViewBuilder(shapeViewFactory: shapeViewFactory)
    shapeViewBuilder.fillColor = UIColor.brownColor()
    shapeViewBuilder.outlineColor = UIColor.orangeColor()
    return shapeViewBuilder
  }

  private func beginNextTurn() {
    let shapeViews = turnController.beginNewTurn()

    shapeViews.0.tapHandler = {
      tappedView in
      self.gameView.score += self.turnController.endTurnWithTappedShape(tappedView.shape)
      self.beginNextTurn()
    }
    shapeViews.1.tapHandler = shapeViews.0.tapHandler

    gameView.addShapeViews(shapeViews)
  }

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

Your revised GameViewController uses TurnStrategy as follows:

  1. Create a strategy to create squares.
  2. Create a strategy to create circles.
  3. Create a strategy to randomly select either your square or circle strategy.
  4. Create your turn controller to use the random strategy.

Build and run, then go ahead and play five or six turns. You should see something similar to the following screenshots.

Notice how your game randomly alternates between square shapes and circle shapes. At this point, you could easily add a third shape like triangle or parallelogram and your GameViewController could use it simply by switching up the strategy.

Design Patterns: Chain of Responsibility, Command and Iterator

Think about the example at the beginning of this tutorial:

var collection = ...

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

What is it that makes the for item in collection loop work? The answer is Swift’s SequenceType.

By using the Iterator pattern in a for ... in loop, you can iterate over any type that conforms to the SequenceType protocol.

The built-in collection types Array and Dictionary already conform to SequenceType, so you generally don’t need to think about SequenceType unless you code your own collections. Still, it’s nice to know. :]

Another design pattern that you’ll often see used in conjunction with Iterator is the Command design pattern, which captures the notion of invoking a specific behavior on a target when asked.

For this tutorial, you’ll use Command to determine if a Turn was a match, and compute your game’s score from that.

Create a new file named Scorer.swift, and replace its contents with the following code:

import Foundation

// 1
protocol Scorer {
  func computeScoreIncrement<S: SequenceType where Turn == S.Generator.Element>(pastTurnsReversed: S) -> Int
}

// 2
class MatchScorer: Scorer {
  func computeScoreIncrement<S : SequenceType where Turn == S.Generator.Element>(pastTurnsReversed: S) -> Int {
    var scoreIncrement: Int?
    // 3
    for turn in pastTurnsReversed {
      if scoreIncrement == nil {
      	// 4
        scoreIncrement = turn.matched! ? 1 : -1
        break
      }
    }

    return scoreIncrement ?? 0
  }
}

Taking each section in turn:

  1. Define your Command type, and declare its behavior to accept a collection of past turns that you can iterate over using the Iterator design pattern.
  2. Declare a concrete implementation of Scorer that will score turns based on whether they matched or not.
  3. Use the Iterator design pattern to iterate over past turns.
  4. Compute the score as +1 for a matched turn and -1 for a non-matched turn.

Now open TurnController.swift and add the following line near the end, just before the closing brace:

private let scorer: Scorer

Then add the following line to the end of the initializer init(turnStrategy:):

self.scorer = MatchScorer()

Finally, replace the line in endTurnWithTappedShape that declares and sets scoreIncrement with the following:

var scoreIncrement = scorer.computeScoreIncrement(pastTurns.reverse())

Take note of how how you reverse pastTurns before passing it to the scorer because the scorer expects turns in reverse order (newest first), whereas pastTurns stores oldest-first (In other words, it appends newer turns to the end of the array).

Build and run your code. Did you notice something strange? I bet your scoring didn’t change for some reason.

You need to make your scoring change by using the Chain of Responsibility design pattern.

The Chain of Responsibility design pattern captures the notion of dispatching multiple commands across a set of data. For this exercise, you’ll dispatch different Scorer commands to compute your player’s score in multiple additive ways.

For example, not only will you award +1 or -1 for matches or mismatches, but you’ll also award bonus points for streaks of consecutive matches. Chain of Responsibility allows you add a second Scorer implementation in a manner that doesn’t interrupt your existing scorer.

Open Scorer.swift and add the following line to the top of MatchScorer

var nextScorer: Scorer? = nil

Then add the following line to the end of the Scorer protocol:

var nextScorer: Scorer? { get set }

Now both MatchScorer and any other Scorer implementations declare that they implement the Chain of Responsibility pattern through their nextScorer property.

Replace the return statement in computeScoreIncrement with the following:

return (scoreIncrement ?? 0) + (nextScorer?.computeScoreIncrement(pastTurnsReversed) ?? 0)

Now you can add another Scorer to the chain after MatchScorer, and its score gets automatically added to the score computed by MatchScorer.

Note: The ?? operator is Swift’s nil coalescing operator. It unwraps an optional to its value if non-nil, else returns the other value if the optional is nil. Effectively, a ?? b is the same as a != nil ? a! : b. It’s a nice shorthand and I encourage you to use it in your code.

To demonstrate this, open Scorer.swift and add the following code to the end of the file:

class StreakScorer: Scorer {
  var nextScorer: Scorer? = nil

  func computeScoreIncrement<S : SequenceType where Turn == S.Generator.Element>(pastTurnsReversed: S) -> Int {
    // 1
    var streakLength = 0
    for turn in pastTurnsReversed {
      if turn.matched! {
        // 2
        ++streakLength
      } else {
        // 3
        break
      }
    }

    // 4
    let streakBonus = streakLength >= 5 ? 10 : 0
    return streakBonus + (nextScorer?.computeScoreIncrement(pastTurnsReversed) ?? 0)
  }
}

Your nifty new StreakScorer works as follows:

  1. Track streak length as the number of consecutive turns with successful matches.
  2. If a turn is a match, the streak continues.
  3. If a turn is not a match, the streak is broken.
  4. Compute the streak bonus: 10 points for a streak of five or more consecutive matches!

To complete the Chain of Responsibility open TurnController.swift and add the following line to the end of the initializer init(turnStrategy:):

self.scorer.nextScorer = StreakScorer()

Excellent, you’re using Chain of Responsibility.

Build and run. After five successful matches in the first five turns you should see something like the following screenshot.

Notice how the score hits 15 after only 5 turns since 15 = 5 points for successful 5 matches + 10 points streak bonus.

Intermediate Design Patterns In iOS

Design Patterns in Swift

iOS Design Patterns – you’ve probably heard the term, but do you know what it means? While most developers probably agree that design patterns are very important, there aren’t many articles on the subject and we developers sometimes don’t pay too much attention to design patterns while writing code.

Design patterns are reusable solutions to common problems in software design. They’re templates designed to help you write code that’s easy to understand and reuse. They also help you create loosely coupled code so that you can change or replace components in your code without too much of a hassle.

If you’re new to design patterns, then I have good news for you! First, you’re already using tons of iOS design patterns thanks to the way Cocoa is built and the best practices you’re encouraged to use. Second, this tutorial will bring you up to speed on all the major (and not so major) iOS design patterns that are commonly used in Cocoa.

In this two-part tutorial, you will create a Music Library app that will display your albums and their relevant information.

In the process of developing this app, you’ll become acquainted with the most common Cocoa design patterns:

  • Creational: Singleton.
  • Structural: MVC, Decorator, Adapter, Facade.
  • Behavioral: Observer, and, Memento

Don’t be misled into thinking that this is an article about theory; you’ll get to use most of these design patterns in your app.

MVC – The King of Design Patterns

Model-View-Controller (MVC) is one of the building blocks of Cocoa and is undoubtedly the most-used design pattern of all. It classifies objects according to their general role in your application and encourages clean separation of code based on role.

The three roles are:

  • Model: The object that holds your application data and defines how to manipulate it. For example, in your application the Model is your Album class.
  • View: The objects that are in charge of the visual representation of the Model and the controls the user can interact with; basically, all the UIView-derived objects. In your application the View is represented by your AlbumView class.
  • Controller: The controller is the mediator that coordinates all the work. It accesses the data from the model and displays it with the views, listens to events and manipulates the data as necessary. Can you guess which class is your controller? That’s right: ViewController.

A good implementation of this design pattern in your application means that each object falls into one of these groups.

The communication between View to Model through Controller can be best described with the following diagram:

The Model notifies the Controller of any data changes, and in turn, the Controller updates the data in the Views. The View can then notify the Controller of actions the user performed and the Controller will either update the Model if necessary or retrieve any requested data.

You might be wondering why you can’t just ditch the Controller, and implement the View and Model in the same class, as that seems a lot easier.

It all comes down to code separation and reusability. Ideally, the View should be completely separated from the Model. If the View doesn’t rely on a specific implementation of the Model, then it can be reused with a different model to present some other data.

For example, if in the future you’d also like to add movies or books to your library, you could still use the same AlbumView to display your movie and book objects. Furthermore, if you want to create a new project that has something to do with albums, you could simply reuse your Album class, because it’s not dependent on any view. That’s the strength of MVC!

How to Use the MVC Pattern

First, you need to ensure that each class in your project is either a Controller, a Model or a View; don’t combine the functionality of two roles in one class. You’ve already done a good job so far by creating an Albumclass and an AlbumView class.

Second, in order to ensure that you conform to this method of work you should create three project groups to hold your code, one for each category.

Navigate to File\New\Group (or press on Command+Option+N) and name the group Model. Repeat the same process to create View and Controller groups.

Now drag Album.swift to the Model group. Drag AlbumView.swift to the View group, and finally drag ViewController.swift to the Controller group.

At this point the project structure should look like this:

Your project already looks a lot better without all those files floating around. Obviously you can have other groups and classes, but the core of the application is contained in these three categories.

Now that your components are organized, you need to get the album data from somewhere. You’ll create an API class to use throughout your code to manage the data — which presents an opportunity to discuss your next design pattern — the Singleton.

The Singleton Pattern

The Singleton design pattern ensures that only one instance exists for a given class and that there’s a global access point to that instance. It usually uses lazy loading to create the single instance when it’s needed the first time.

Note: Apple uses this approach a lot. For example: NSUserDefaults.standardUserDefaults(), UIApplication.sharedApplication(), UIScreen.mainScreen(), NSFileManager.defaultManager() all return a Singleton object.

You’re likely wondering why you care if there’s more than one instance of a class floating around. Code and memory is cheap, right?

There are some cases in which it makes sense to have exactly one instance of a class. For example, there’s only one instance of your application and one main screen for the device, so you only want one instance of each. Or, take a global configuration handler class: it’s easier to implement a thread-safe access to a single shared resource, such as a configuration file, than to have many class modifying the configuration file possibly at the same time.

How to Use the Singleton Pattern

Take a look at the diagram below:

The above image shows a Logger class with a single property (which is the single instance), and two methods: sharedInstance and init.

The first time a client asks for the sharedInstance, the instance property isn’t yet initialized, so you create a new instance of the class and return a reference to it.

The next time you call sharedInstance, instance is immediately returned without any initialization. This logic ensures that only one instance exists at one time.

You’ll implement this pattern by creating a singleton class to manage all the album data.

You’ll notice there’s a group called API in the project; this is where you’ll put all the classes that will provide services to your app. Create a new file inside this group by right-clicking the group and selecting New File. Select iOS > Cocoa Touch Class and then click Next. Set the class name to LibraryAPI, and the subclass to NSObject. Lastly choose Swift as the language. Click Next and then Create.

Now go to LibraryAPI.swift and insert this code inside the class definition.

//1
class var sharedInstance: LibraryAPI {
  //2
  struct Singleton {
    //3
    static let instance = LibraryAPI()
  }
  //4
  return Singleton.instance
}

There’s a lot going on in this short method:

  1. Create a class variable as a computed type property. The class variable, like class methods in Objective-C, is something you can call without having to instantiate the class LibraryAPI For more information about type properties please refer to Apple’s Swift documentation on
    properties
  2. Nested within the class variable is a struct called Singleton.
  3. Singleton wraps a static constant variable named instance. Declaring a property as static means this property only exists once. Also note that static properties in Swift are implicitly lazy, which means that Instance is not created until it’s needed. Also note that since this is a constant property, once this instance is created, it’s not going to create it a second time. This is the essence of the Singleton design pattern. The initializer is never called again once it has been instantiated.
  4. Returns the computed type property.

Note: To learn more about different ways to create a singleton in Swift refer to this: Github page

You now have a Singleton object as the entry point to manage the albums. Take it a step further and create a class to handle the persistence of your library data.

Now within the group API create a new file. Select iOS > Cocoa Touch class and then click Next. Set the class name to PersistencyManager, and make it a subclass of NSObject.

Open PersistencyManager.swift Add the following code inside the curly braces

private var albums = [Album]()

Here you declare a private property to hold album data. The array is mutable, so you can easily add and delete albums.

Now add the following initializer to the class:

override init() {
  //Dummy list of albums
  let album1 = Album(title: "Best of Bowie",
         artist: "David Bowie",
         genre: "Pop",
         coverUrl: "http://www.coversproject.com/static/thumbs/album/album_david%20bowie_best%20of%20bowie.png",
         year: "1992")
  
  let album2 = Album(title: "It's My Life",
         artist: "No Doubt",
         genre: "Pop",
         coverUrl: "http://www.coversproject.com/static/thumbs/album/album_no%20doubt_its%20my%20life%20%20bathwater.png",
         year: "2003")
  
  let album3 = Album(title: "Nothing Like The Sun",
         artist: "Sting",
         genre: "Pop",
         coverUrl: "http://www.coversproject.com/static/thumbs/album/album_sting_nothing%20like%20the%20sun.png",
         year: "1999")
  
  let album4 = Album(title: "Staring at the Sun",
         artist: "U2",
         genre: "Pop",
         coverUrl: "http://www.coversproject.com/static/thumbs/album/album_u2_staring%20at%20the%20sun.png",
         year: "2000")
  
  let album5 = Album(title: "American Pie",
         artist: "Madonna",
         genre: "Pop",
         coverUrl: "http://www.coversproject.com/static/thumbs/album/album_madonna_american%20pie.png",
         year: "2000")
         
  albums = [album1, album2, album3, album4, album5]
}

In the initializer, you’re populating the array with five sample albums. If the above albums aren’t to your liking, feel free to replace them with the music you enjoy. :]

Now add the following functions to the class:

func getAlbums() -> [Album] {
  return albums
}

func addAlbum(album: Album, index: Int) {
  if (albums.count >= index) { 
    albums.insert(album, atIndex: index)
  } else {
    albums.append(album)
  }
}

func deleteAlbumAtIndex(index: Int) {
  albums.removeAtIndex(index)
}

These methods allow you to get, add, and delete albums.

Build your project just to make sure everything still compiles correctly.

At this point, you might wonder where the PersistencyManager class comes in since it’s not a Singleton. You’ll see the relationship between LibraryAPI and PersistencyManager in the next section where you’ll look at the Facade design pattern.

The Facade Design Pattern

The Facade design pattern provides a single interface to a complex subsystem. Instead of exposing the user to a set of classes and their APIs, you only expose one simple unified API.

The following image explains this concept:

 

The user of the API is completely unaware of the complexity that lies beneath. This pattern is ideal when working with a large number of classes, particularly when they are complicated to use or difficult to understand.

The Facade pattern decouples the code that uses the system from the interface and implementation of the classes you’re hiding; it also reduces dependencies of outside code on the inner workings of your subsystem. This is also useful if the classes under the facade are likely to change, as the facade class can retain the same API while things change behind the scenes.

For example, if the day comes when you want to replace your backend service, you won’t have to change the code that uses your API as it wont’ change.

How to Use the Facade Pattern

Currently you have PersistencyManager to save the album data locally and HTTPClient to handle the remote communication. The other classes in your project should not be aware of this logic, as they will be hiding behind the facade of LibraryAPI.

To implement this pattern, only LibraryAPI should hold instances of PersistencyManager and HTTPClient. Then, LibraryAPI will expose a simple API to access those services.

The design looks like the following:

 

LibraryAPI will be exposed to other code, but will hide the HTTPClient and PersistencyManager complexity from the rest of the app.

Open LibraryAPI.swift and add the following constant properties to the class:

private let persistencyManager: PersistencyManager
private let httpClient: HTTPClient
private let isOnline: Bool

isOnline determines if the server should be updated with any changes made to the albums list, such as added or deleted albums.

You now need to initialize these variables via init. Add the initializer to the class next:

override init() {
  persistencyManager = PersistencyManager()
  httpClient = HTTPClient()
  isOnline = false
  
  super.init()
}

The HTTP client doesn’t actually work with a real server and is only here to demonstrate the usage of the facade pattern, so isOnline will always be false.

Next, add the following three methods to LibraryAPI.swift:

func getAlbums() -> [Album] {
  return persistencyManager.getAlbums()
}

func addAlbum(album: Album, index: Int) {
  persistencyManager.addAlbum(album, index: index)
  if isOnline {
    httpClient.postRequest("/api/addAlbum", body: album.description)
  }
}

func deleteAlbum(index: Int) {
  persistencyManager.deleteAlbumAtIndex(index)
  if isOnline {
    httpClient.postRequest("/api/deleteAlbum", body: "\(index)")
  }
}

Take a look at addAlbum(_:index:). The class first updates the data locally, and then if there’s an internet connection, it updates the remote server. This is the real strength of the Facade; when some class outside of your system adds a new album, it doesn’t know — and doesn’t need to know — of the complexity that lies underneath.

Note: When designing a Facade for classes in your subsystem, remember that nothing prevents the client from accessing these “hidden” classes directly. Don’t be stingy with defensive code and don’t assume that all the clients will necessarily use your classes the same way the Facade uses them.

Build and run your app. You’ll see two empty views, and a toolbar. The top view will be used to display your album covers, and the bottom view will be used to display a table of information related to that album.

You’ll need something to display the album data on screen — which is a perfect use for your next design pattern: the Decorator.

The Decorator Design Pattern

The Decorator pattern dynamically adds behaviors and responsibilities to an object without modifying its code. It’s an alternative to subclassing where you modify a class’s behavior by wrapping it with another object.

In Swift there are two very common implementations of this pattern: Extensions and Delegation.

Extensions

Adding extensions is an extremely powerful mechanism that allows you to add new functionality to existing classes, structures or enumeration types without having to subclass. What’s also really awesome is you can extend code you don’t have access to, and enhance their functionality. That means you can add your own methods to Cocoa classes such as UIView and UIImage!

For example, new methods that are added at compile time can be executed like normal methods of the extended class. It’s slightly different from the classic definition of a decorator, because a extension doesn’t hold an instance of the class it extends.

How to Use Extensions

Imagine a situation in which you have an Album object that you want to present inside a table view:
Where will the album titles come from? Album is a Model object, so it doesn’t care how you present the data. You’ll need some external code to add this functionality to the Album class, but without modifying the class directly.

You’ll create a extension that will extend the Album class; it will define a new method that returns a data structure which can be used easily with UITableView.

The data structure will look like the following:

To add a Extensions to Album, navigate to File\New\File… and select the iOS > Source > Swift File template, and then click Next. Enter AlbumExtensions and click Create.

Go to AlbumExtensions.swift and add the following extension:

extension Album {
  func ae_tableRepresentation() -> (titles:[String], values:[String]) {
    return (["Artist", "Album", "Genre", "Year"], [artist, title, genre, year])
  }
}

Notice there’s a ae_ at the beginning of the method name, as an abbreviation of the name of the extension: AlbumExtension. Conventions like this will help prevent collisions with methods (including possible private methods you might not know about) in the original implementation.

Note: Classes can of course override a superclass’s method, but with extensions you can’t. Methods or properties in an extension cannot have the same name as methods or properties in the original class.

Consider for a moment how powerful this pattern can be:

  • You’re using properties directly from Album.
  • You have added to the Album class but you haven’t subclassed it. If you need to sub-class Album, you can still do that too.
  • This simple addition lets you return a UITableViewish representation of an Album, without modifying Album‘s code.

Delegation

The other Decorator design pattern, Delegation, is a mechanism in which one object acts on behalf of, or in coordination with, another object. For example, when you use a UITableView, one of the methods you must implement is tableView(_:numberOfRowsInSection:).

You can’t expect the UITableView to know how many rows you want to have in each section, as this is application-specific. Therefore, the task of calculating the amount of rows in each section is passed on to the UITableView delegate. This allows the UITableView class to be independent of the data it displays.

The UITableView object does its job of displaying a table view. However, eventually it will need some information that it doesn’t have. Then, it turns to its delegates and sends a message asking for additional information. In Objective-C’s implementation of the delegate pattern, a class can declare optional and required methods through a protocol. You’ll cover protocols a bit later in this tutorial.

It might seem easier to just subclass an object and override the necessary methods, but consider that you can only subclass based on a single class. If you want an object to be the delegate of two or more other objects, you won’t be able to achieve this by subclassing.

Note: This is an important pattern. Apple uses this approach in most of the UIKit classes: UITableView, UITextView, UITextField, UIWebView, UIAlert, UIActionSheet, UICollectionView, UIPickerView, UIGestureRecognizer, UIScrollView. The list goes on and on.

How to Use the Delegate Pattern

Open up ViewController.swift and add these private properties to the class:

private var allAlbums = [Album]()
private var currentAlbumData : (titles:[String], values:[String])?
private var currentAlbumIndex = 0

Next, replace viewDidLoad with this code:

override func viewDidLoad() {
  super.viewDidLoad()
  //1
  self.navigationController?.navigationBar.translucent = false
  currentAlbumIndex = 0

  //2
  allAlbums = LibraryAPI.sharedInstance.getAlbums()

  // 3
  // the uitableview that presents the album data
  dataTable.delegate = self
  dataTable.dataSource = self
  dataTable.backgroundView = nil
  view.addSubview(dataTable!)		
}

Here’s a breakdown of the above code:

  1. Turn off translucency on the navigation bar.
  2. Get a list of all the albums via the API. Remember, the plan is to use the facade of LibraryAPI rather than PersistencyManager directly!
  3. This is where you setup the UITableView. You declare that the view controller is the UITableView delegate/data source; therefore, all the information required by UITableView will be provided by the view controller. Note that you can actually set the delegate and datasource in a storyboard, if your table view is created there.

Now, add the following method to ViewController.swift:

func showDataForAlbum(albumIndex: Int) {
  // defensive code: make sure the requested index is lower than the amount of albums
  if (albumIndex < allAlbums.count && albumIndex > -1) {
    //fetch the album
    let album = allAlbums[albumIndex]
    // save the albums data to present it later in the tableview
    currentAlbumData = album.ae_tableRepresentation()
  } else {
    currentAlbumData = nil
  }
  // we have the data we need, let's refresh our tableview
  dataTable!.reloadData()
}

showDataForAlbum() fetches the required album data from the array of albums. When you want to present the new data, you just need to call reloadData. This causes UITableView to ask its delegate such things as how many sections should appear in the table view, how many rows in each section, and how each cell should look.

Add the following line to the end of viewDidLoad

self.showDataForAlbum(currentAlbumIndex)

This loads the current album at app launch. And since currentAlbumIndex was previously set to 0, this shows the first album in the collection.

Now it’s time to implement the data source protocol! You can add the list of protocols implemented by the class right on the class declaration line. Or, to keep things tidy you can add them as extensions, which you’re already familiar with.

Add the following extensions to the bottom of the file. Make sure you add these lines after the closing brace of the class definition!

extension ViewController: UITableViewDataSource {
}

extension ViewController: UITableViewDelegate {
}

This is how you make your delegate conform to a protocol — think of it as a promise made by the delegate to fulfill the method’s contract. Here, you indicate that ViewController will conform to the UITableViewDataSource and UITableViewDelegate protocols. This way UITableView can be absolutely certain that the required methods are implemented by its delegate.

Add the following code to the UITableViewDataSource extension:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  if let albumData = currentAlbumData {
    return albumData.titles.count
  } else {
    return 0
  }
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  var cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
  if let albumData = currentAlbumData {
    cell.textLabel!.text = albumData.titles[indexPath.row]
    cell.detailTextLabel!.text = albumData.values[indexPath.row]
  }
  return cell
}

tableView(_:numberOfRowsInSection:) returns the number of rows to display in the table view, which matches the number of titles in the data structure.

tableView(_:cellForRowAtIndexPath:) creates and returns a cell with the title and its value.

Note: You can actually add the methods to the main class declaration or to the extension; the compiler doesn’t care that the data source methods are actually inside the UITableViewDataSource extension. For humans reading the code though, this kind of organization really helps with readability.

The Adapter Pattern

An Adapter allows classes with incompatible interfaces to work together. It wraps itself around an object and exposes a standard interface to interact with that object.

If you’re familiar with the Adapter pattern then you’ll notice that Apple implements it in a slightly different manner – Apple uses protocols to do the job. You may be familiar with protocols like UITableViewDelegate, UIScrollViewDelegate, NSCoding and NSCopying. As an example, with the NSCopying protocol, any class can provide a standard copy method.

How to Use the Adapter Pattern

The horizontal scroller mentioned before will look like this:

To begin implementing it, right click on the View group in the Project Navigator, select New File… and select, iOS > Cocoa Touch class and then click Next. Set the class name to HorizontalScroller and make it a subclass of UIView.

Open HorizontalScroller.swift and insert the following code above the class HorizontalScroller line:

@objc protocol HorizontalScrollerDelegate {
}

This defines a protocol named HorizontalScrollerDelegate. You’re including @objc before the protocol declaration so you can make use of @optional delegate methods like in Objective-C.

You define the required and optional methods that the delegate will implement between the protocols curly braces. So add the following protocol methods:

// ask the delegate how many views he wants to present inside the horizontal scroller
func numberOfViewsForHorizontalScroller(scroller: HorizontalScroller) -> Int
// ask the delegate to return the view that should appear at <index>
func horizontalScrollerViewAtIndex(scroller: HorizontalScroller, index:Int) -> UIView
// inform the delegate what the view at <index> has been clicked
func horizontalScrollerClickedViewAtIndex(scroller: HorizontalScroller, index:Int)
// ask the delegate for the index of the initial view to display. this method is optional
// and defaults to 0 if it's not implemented by the delegate
optional func initialViewIndex(scroller: HorizontalScroller) -> Int

Here you have both required and optional methods. Required methods must be implemented by the delegate and usually contain some data that is absolutely required by the class. In this case, the required details are the number of views, the view at a specific index, and the behavior when the view is tapped. The optional method here is the initial view; if it’s not implemented then the HorizontalScroller will default to the first index.

In HorizontalScroller.swift, add the following code to the HorizontalScroller class definition:

weak var delegate: HorizontalScrollerDelegate?

The attribute of the property you created above is defined as weak. This is necessary in order to prevent a retain cycle. If a class keeps a strong reference to its delegate and the delegate keeps a strong reference back to the conforming class, your app will leak memory since neither class will release the memory allocated to the other. All properties in swift are strong by default!

The delegate is an optional, so it’s possible whoever is using this class doesn’t provide a delegate. But if they do, it will conform to HorizontalScrollerDelegate and you can be sure the protocol methods will be implemented there.

Add a few more properties to the class:

// 1
private let VIEW_PADDING = 10
private let VIEW_DIMENSIONS = 100
private let VIEWS_OFFSET = 100

// 2
private var scroller : UIScrollView!
// 3
var viewArray = [UIView]()

Taking each comment block in turn:

  1. Define constants to make it easy to modify the layout at design time. The view’s dimensions inside the scroller will be 100 x 100 with a 10 point margin from its enclosing rectangle.
  2. Create the scroll view containing the views.
  3. Create an array that holds all the album covers.

Next you need to implement the initializers. Add the following methods:

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

required init(coder aDecoder: NSCoder) {
  super.init(coder: aDecoder)
  initializeScrollView()
}

func initializeScrollView() {
  //1
  scroller = UIScrollView()
  addSubview(scroller)
  
  //2
  scroller.setTranslatesAutoresizingMaskIntoConstraints(false)
  //3
  self.addConstraint(NSLayoutConstraint(item: scroller, attribute: .Leading, relatedBy: .Equal, toItem: self, attribute: .Leading, multiplier: 1.0, constant: 0.0))
  self.addConstraint(NSLayoutConstraint(item: scroller, attribute: .Trailing, relatedBy: .Equal, toItem: self, attribute: .Trailing, multiplier: 1.0, constant: 0.0))
  self.addConstraint(NSLayoutConstraint(item: scroller, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1.0, constant: 0.0))
  self.addConstraint(NSLayoutConstraint(item: scroller, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1.0, constant: 0.0))

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

The initializers delegate most of the work to initializeScrollView(). Here’s what’s going on in that method:

  1. Create’s a new UIScrollView instance and add it to the parent view.
  2. Turn off autoresizing masks. This is so you can apply your own constraints
  3. Apply constraints to the scrollview. You want the scroll view to completely fill the HorizontalScroller
  4. Create a tap gesture recognizer. The tap gesture recognizer detects touches on the scroll view and checks if an album cover has been tapped. If so, it will notify the HorizontalScroller delegate.

Now add this method:

func scrollerTapped(gesture: UITapGestureRecognizer) {
    let location = gesture.locationInView(gesture.view)
    if let delegate = delegate {
      for index in 0..<delegate.numberOfViewsForHorizontalScroller(self) {
        let view = scroller.subviews[index] as! UIView
        if CGRectContainsPoint(view.frame, location) {
          delegate.horizontalScrollerClickedViewAtIndex(self, index: index)
          scroller.setContentOffset(CGPoint(x: view.frame.origin.x - self.frame.size.width/2 + view.frame.size.width/2, y: 0), animated:true)
          break
        }
      }
    }
  }

The gesture passed in as a parameter lets you extract the location with locationInView().

Next, you invoke numberOfViewsForHorizontalScroller() on the delegate. The HorizontalScroller instance has no information about the delegate other than knowing it can safely send this message since the delegate must conform to the HorizontalScrollerDelegate protocol.

For each view in the scroll view, perform a hit test using CGRectContainsPoint to find the view that was tapped. When the view is found, call the delegate method horizontalScrollerClickedViewAtIndex. Before you break out of the for loop, center the tapped view in the scroll view.

Next add the following to access an album cover from the scroller:

func viewAtIndex(index :Int) -> UIView {
  return viewArray[index]
}

viewAtIndex simply returns the view at a particular index. You will be using this method later to highlight the album cover you have tapped on.

Now add the following code to reload the scroller:

func reload() {
    // 1 - Check if there is a delegate, if not there is nothing to load.
    if let delegate = delegate {
      //2 - Will keep adding new album views on reload, need to reset.
      viewArray = []
      let views: NSArray = scroller.subviews

			// 3 - remove all subviews
			for view in views {
				view.removeFromSuperview()
			}

      // 4 - xValue is the starting point of the views inside the scroller
      var xValue = VIEWS_OFFSET
      for index in 0..<delegate.numberOfViewsForHorizontalScroller(self) {
        // 5 - add a view at the right position
        xValue += VIEW_PADDING
        let view = delegate.horizontalScrollerViewAtIndex(self, index: index)
        view.frame = CGRectMake(CGFloat(xValue), CGFloat(VIEW_PADDING), CGFloat(VIEW_DIMENSIONS), CGFloat(VIEW_DIMENSIONS))
        scroller.addSubview(view)
        xValue += VIEW_DIMENSIONS + VIEW_PADDING
        // 6 - Store the view so we can reference it later
        viewArray.append(view)
      }
      // 7
      scroller.contentSize = CGSizeMake(CGFloat(xValue + VIEWS_OFFSET), frame.size.height)

      // 8 - If an initial view is defined, center the scroller on it
      if let initialView = delegate.initialViewIndex?(self) {
        scroller.setContentOffset(CGPoint(x: CGFloat(initialView)*CGFloat((VIEW_DIMENSIONS + (2 * VIEW_PADDING))), y: 0), animated: true)
      }
    }
  }

The reload method is modeled after reloadData in UITableView; it reloads all the data used to construct the horizontal scroller.

Stepping through the code comment-by-comment:

  1. Checks to see if there is a delegate before we perform any reload.
  2. Since you’re clearing the album covers, you also need to reset the viewArray. If not you will have a ton of views left over from the previous covers.
  3. Remove all the subviews previously added to the scroll view.
  4. All the views are positioned starting from the given offset. Currently it’s 100, but it can be easily tweaked by changing the constant VIEW_OFFSET at the top of the file.
  5. The HorizontalScroller asks its delegate for the views one at a time and it lays them next to each another horizontally with the previously defined padding.
  6. Store the view in viewArray to keep track of all the views in the scroll view.
  7. Once all the views are in place, set the content offset for the scroll view to allow the user to scroll through all the albums covers.
  8. The HorizontalScroller checks if its delegate implements initialViewIndex(). This check is necessary because that particular protocol method is optional. If the delegate doesn’t implement this method, 0 is used as the default value. Finally, this piece of code sets the scroll view to center the initial view defined by the delegate.

You execute reload when your data has changed. You also need to call this method when you add HorizontalScroller to another view. Add the following code to HorizontalScroller.swift to cover the latter scenario:

override func didMoveToSuperview() {
  reload()
}

didMoveToSuperview is called on a view when it’s added to another view as a subview. This is the right time to reload the contents of the scroller.

The last piece of the HorizontalScroller puzzle is to make sure the album you’re viewing is always centered inside the scroll view. To do this, you’ll need to perform some calculations when the user drags the scroll view with their finger.

Add the following method:

 func centerCurrentView() {
    var xFinal = Int(scroller.contentOffset.x) + (VIEWS_OFFSET/2) + VIEW_PADDING
    let viewIndex = xFinal / (VIEW_DIMENSIONS + (2*VIEW_PADDING))
    xFinal = viewIndex * (VIEW_DIMENSIONS + (2*VIEW_PADDING))
		scroller.setContentOffset(CGPoint(x: xFinal, y: 0), animated: true)
    if let delegate = delegate {
      delegate.horizontalScrollerClickedViewAtIndex(self, index: Int(viewIndex))
    }  
 }

The above code takes into account the current offset of the scroll view and the dimensions and the padding of the views in order to calculate the distance of the current view from the center. The last line is important: once the view is centered, you then inform the delegate that the selected view has changed.

To detect that the user finished dragging inside the scroll view, you’ll need to implement some UIScrollViewDelegate methods. Add the following class extension to the bottom of the file; remember, this must be added after the curly braces of the main class declaration!

extension HorizontalScroller: UIScrollViewDelegate {
  func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if !decelerate {
      centerCurrentView()
    }
  }
  
  func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
    centerCurrentView()
  }
}

scrollViewDidEndDragging(_:willDecelerate:) informs the delegate when the user finishes dragging. The decelerate parameter is true if the scroll view hasn’t come to a complete stop yet. When the scroll action ends, the the system calls scrollViewDidEndDecelerating. In both cases you should call the new method to center the current view since the current view probably has changed after the user dragged the scroll view.

Lastly don’t forget to set the delegate. Within initializeScrollView() add the following code after scroller = UIScrollView():

scroller.delegate = self;

Your HorizontalScroller is ready for use! Browse through the code you’ve just written; you’ll see there’s not one single mention of the Album or AlbumView classes. That’s excellent, because this means that the new scroller is truly independent and reusable.

Build your project to make sure everything compiles properly.

Now that HorizontalScroller is complete, it’s time to use it in your app. First, open Main.storyboard. Click on the top gray rectangular view and click on the identity inspector. Change the class name to HorizontalScroller.

Next, open the assistant editor and control drag from the gray rectangular view to ViewController.swift to create an outlet. Name the name the outlet scroller,

Next, open ViewController.swift. It’s time to start implementing some of the HorizontalScrollerDelegate methods!

Add the following extension to the bottom of the file:

extension ViewController: HorizontalScrollerDelegate {
  func horizontalScrollerClickedViewAtIndex(scroller: HorizontalScroller, index: Int) {
    //1
    let previousAlbumView = scroller.viewAtIndex(currentAlbumIndex) as! AlbumView
    previousAlbumView.highlightAlbum(didHighlightView: false)
    //2
    currentAlbumIndex = index
    //3
    let albumView = scroller.viewAtIndex(index) as! AlbumView
    albumView.highlightAlbum(didHighlightView: true)
    //4
    showDataForAlbum(index)
  }
}

Let’s go over the delegate method you just implemented line by line:

  1. First you grab the previously selected album, and deselect the album cover.
  2. Store the current album cover index you just clicked
  3. Grab the album cover that is currently selected and highlight the selection.
  4. Display the data for the new album within the table view.

Next, add the following method to the extension:

func numberOfViewsForHorizontalScroller(scroller: HorizontalScroller) -> (Int) {
  return allAlbums.count
}

This, as you’ll recognize, is the protocol method returning the number of views for the scroll view. Since the scroll view will display covers for all the album data, the count is the number of album records.

Now, add this code:

func horizontalScrollerViewAtIndex(scroller: HorizontalScroller, index: Int) -> (UIView) {
    let album = allAlbums[index]
    let albumView = AlbumView(frame: CGRect(x: 0, y: 0, width: 100, height: 100), albumCover: album.coverUrl)
    if currentAlbumIndex == index {
      albumView.highlightAlbum(didHighlightView: true)
    } else {
      albumView.highlightAlbum(didHighlightView: false)
    }
    return albumView
  }

Here you create a new AlbumView, next check to see whether or not the user has selected this album. Then you can set it as highlighted or not depending on whether the album is selected. Lastly, you pass it to the HorizontalScroller.

That’s it! Only three short methods to display a nice looking horizontal scroller.

Yes, you still need to actually create the scroller and add it to your main view but before doing that, add the following method to the main class definition:

  func reloadScroller() {
    allAlbums = LibraryAPI.sharedInstance.getAlbums()
    if currentAlbumIndex < 0 {
      currentAlbumIndex = 0
    } else if currentAlbumIndex >= allAlbums.count {
      currentAlbumIndex = allAlbums.count - 1
    }
    scroller.reload()
    showDataForAlbum(currentAlbumIndex)
  }

This method loads album data via LibraryAPI and then sets the currently displayed view based on the current value of the current view index. If the current view index is less than 0, meaning that no view was currently selected, then the first album in the list is displayed. Otherwise, the last album is displayed.

Now, initialize the scroller by adding the following code to the end of viewDidLoad:

scroller.delegate = self
reloadScroller()

Since the HorizontalScroller was created in the storyboard, all you need to do is set the delegate, and call reloadScroller(), which will load the subviews for the scroller to display album data.

Note: If a protocol becomes too big and is packed with a lot of methods, you should consider breaking it into several smaller protocols. UITableViewDelegate and UITableViewDataSource are a good example, since they are both protocols of UITableView. Try to design your protocols so that each one handles one specific area of functionality.

Build and run your project and take a look at your awesome new horizontal scroller:

Uh, wait. The horizontal scroller is in place, but where are the covers?

Ah, that’s right — you didn’t implement the code to download the covers yet. To do that, you’ll need to add a way to download images. Since all your access to services goes through LibraryAPI, that’s where this new method would have to go. However, there are a few things to consider first:

  1. AlbumView shouldn’t work directly with LibraryAPI. You don’t want to mix view logic with communication logic.
  2. For the same reason, LibraryAPI shouldn’t know about AlbumView.
  3. LibraryAPI needs to inform AlbumView once the covers are downloaded since the AlbumView has to display the covers.

Sounds like a conundrum? Don’t despair, you’ll learn how to do this using the Observer pattern! :]

The Observer Pattern

In the Observer pattern, one object notifies other objects of any state changes. The objects involved don’t need to know about one another – thus encouraging a decoupled design. This pattern’s most often used to notify interested objects when a property has changed.

The usual implementation requires that an observer registers interest in the state of another object. When the state changes, all the observing objects are notified of the change.

If you want to stick to the MVC concept (hint: you do), you need to allow Model objects to communicate with View objects, but without direct references between them. And that’s where the Observer pattern comes in.

Cocoa implements the observer pattern in two familiar ways: Notifications and Key-Value Observing (KVO).

Notifications

Not be be confused with Push or Local notifications, Notifications are based on a subscribe-and-publish model that allows an object (the publisher) to send messages to other objects (subscribers/listeners). The publisher never needs to know anything about the subscribers.

Notifications are heavily used by Apple. For example, when the keyboard is shown/hidden the system sends a UIKeyboardWillShowNotification/UIKeyboardWillHideNotification, respectively. When your app goes to the background, the system sends a UIApplicationDidEnterBackgroundNotification notification.

Note: Open up UIApplication.swift, at the end of the file you’ll see a list of over 20 notifications sent by the system.

How to Use Notifications

Go to AlbumView.swift and insert the following code to the end of the init(frame: CGRect, albumCover: String) initializer:

NSNotificationCenter.defaultCenter().postNotificationName("BLDownloadImageNotification", object: self, userInfo: ["imageView":coverImage, "coverUrl" : albumCover])

This line sends a notification through the NSNotificationCenter singleton. The notification info contains the UIImageView to populate and the URL of the cover image to be downloaded. That’s all the information you need to perform the cover download task.

Add the following line to init in LibraryAPI.swift, directly after super.init():

NSNotificationCenter.defaultCenter().addObserver(self, selector:"downloadImage:", name: "BLDownloadImageNotification", object: nil)

This is the other side of the equation: the observer. Every time an AlbumView class posts a BLDownloadImageNotification notification, since LibraryAPI has registered as an observer for the same notification, the system notifies LibraryAPI. Then LibraryAPI calls downloadImage() in response.

However, before you implement downloadImage() you must remember to unsubscribe from this notification when your class is deallocated. If you do not properly unsubscribe from a notification your class registered for, a notification might be sent to a deallocated instance. This can result in application crashes.

Add the following method to LibraryAPI.swift:

deinit {
  NSNotificationCenter.defaultCenter().removeObserver(self)
}

When this object is deallocated, it removes itself as an observer from all notifications it had registered for.

There’s one more thing to do. It would probably be a good idea to save the downloaded covers locally so the app won’t need to download the same covers over and over again.

Open PersistencyManager.swift and add the methods below:

func saveImage(image: UIImage, filename: String) {
  let path = NSHomeDirectory().stringByAppendingString("/Documents/\(filename)")
  let data = UIImagePNGRepresentation(image)
  data.writeToFile(path, atomically: true)
}

func getImage(filename: String) -> UIImage? {
  var error: NSError?
  let path = NSHomeDirectory().stringByAppendingString("/Documents/\(filename)")
  let data = NSData(contentsOfFile: path, options: .UncachedRead, error: &error)
  if let unwrappedError = error {
    return nil
  } else {
    return UIImage(data: data!)
  }
}

This code is pretty straightforward. The downloaded images will be saved in the Documents directory, and getImage() will return nil if a matching file is not found in the Documents directory.

Now add the following method to LibraryAPI.swift:

  func downloadImage(notification: NSNotification) {
    //1
    let userInfo = notification.userInfo as! [String: AnyObject]
    var imageView = userInfo["imageView"] as! UIImageView?
    let coverUrl = userInfo["coverUrl"] as! String

    //2
    if let imageViewUnWrapped = imageView {
      imageViewUnWrapped.image = persistencyManager.getImage(coverUrl.lastPathComponent)
      if imageViewUnWrapped.image == nil {
        //3
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { () -> Void in
          let downloadedImage = self.httpClient.downloadImage(coverUrl as String)
          //4
          dispatch_sync(dispatch_get_main_queue(), { () -> Void in
            imageViewUnWrapped.image = downloadedImage
            self.persistencyManager.saveImage(downloadedImage, filename: coverUrl.lastPathComponent)
          })
        })
      }
    }
  }

Here’s a breakdown of the above code:

  1. downloadImage is executed via notifications and so the method receives the notification object as a parameter. The UIImageView and image URL are retrieved from the notification.
  2. Retrieve the image from the PersistencyManager if it’s been downloaded previously.
  3. If the image hasn’t already been downloaded, then retrieve it using HTTPClient.
  4. When the download is complete, display the image in the image view and use the PersistencyManagerto save it locally.

Again, you’re using the Facade pattern to hide the complexity of downloading an image from the other classes. The notification sender doesn’t care if the image came from the web or from the file system.

Build and run your app and check out the beautiful covers inside your HorizontalScroller.

Stop your app and run it again. Notice that there’s no delay in loading the covers because they’ve been saved locally. You can even disconnect from the Internet and your app will work flawlessly. However, there’s one odd bit here: the spinner never stops spinning! What’s going on?

You started the spinner when downloading the image, but you haven’t implemented the logic to stop the spinner once the image is downloaded. You could send out a notification every time an image has been downloaded, but instead, you’ll do that using the other Observer pattern, KVO.

Key-Value Observing (KVO)

In KVO, an object can ask to be notified of any changes to a specific property; either its own or that of another object. If you’re interested, you can read more about this on Apple’s KVO Programming Guide.

How to Use the KVO Pattern

As mentioned above, the KVO mechanism allows an object to observe changes to a property. In your case, you can use KVO to observe changes to the image property of the UIImageView that holds the image.

Open AlbumView.swift and add the following code to init(frame:albumCover:), just after you add coverImage as a subView:

coverImage.addObserver(self, forKeyPath: "image", options: nil, context: nil)

This adds self, which is the current class, as an observer for the image property of coverImage.

You also need to unregister as an observer when you’re done. Still in AlbumView.swift, add the following code:

deinit {
  coverImage.removeObserver(self, forKeyPath: "image")
}

Finally, add this method:

override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
  if keyPath == "image" {
    indicator.stopAnimating()
  }
}

You must implement this method in every class acting as an observer. The system executes this method every time an observed property changes. In the above code, you stop the spinner when the “image” property changes. This way, when an image is loaded, the spinner will stop spinning.

Build and run your project. The spinner should disappear.

Note: Always remember to remove your observers when they’re deallocated, or else your app will crash when the subject tries to send messages to these non-existent observers!

If you play around with your app a bit and terminate it, you’ll notice that the state of your app isn’t saved. The last album you viewed won’t be the default album when the app launches.

To correct this, you can make use of the next pattern on the list: Memento.

The Memento Pattern

The memento pattern captures and externalizes an object’s internal state. In other words, it saves your stuff somewhere. Later on, this externalized state can be restored without violating encapsulation; that is, private data remains private.

How to Use the Memento Pattern

Add the following two methods to ViewController.swift:

//MARK: Memento Pattern
func saveCurrentState() {
  // When the user leaves the app and then comes back again, he wants it to be in the exact same state
  // he left it. In order to do this we need to save the currently displayed album.
  // Since it's only one piece of information we can use NSUserDefaults.
  NSUserDefaults.standardUserDefaults().setInteger(currentAlbumIndex, forKey: "currentAlbumIndex")
}
	
func loadPreviousState() {
  currentAlbumIndex = NSUserDefaults.standardUserDefaults().integerForKey("currentAlbumIndex")
  showDataForAlbum(currentAlbumIndex)
}

saveCurrentState saves the current album index to NSUserDefaultsNSUserDefaults is a standard data store provided by iOS for saving application specific settings and data.

loadPreviousState loads the previously saved index. This isn’t quite the full implementation of the Memento pattern, but you’re getting there.

Now, Add the following line to viewDidLoad in ViewController.swift before the scroller.delegate = self:

loadPreviousState()

That loads the previously saved state when the app starts. But where do you save the current state of the app for loading from? You’ll use Notifications to do this. iOS sends a UIApplicationDidEnterBackgroundNotification notification when the app enters the background. You can use this notification to call saveCurrentState. Isn’t that convenient?

Add the following line to the end of viewDidLoad:

NSNotificationCenter.defaultCenter().addObserver(self, selector:"saveCurrentState", name: UIApplicationDidEnterBackgroundNotification, object: nil)

Now, when the app is about to enter the background, the ViewController will automatically save the current state by calling saveCurrentState.

As always, you’ll need to un-register for notifications. Add the following code to the class:

deinit {
  NSNotificationCenter.defaultCenter().removeObserver(self)
}

This ensures you remove the class as an observer when the ViewController is deallocated.

Build and run your app. Navigate to one of the albums, send the app to the background with the Home button (Command+Shift+H if you are on the simulator) and then shut down your app from Xcode. Relaunch, and check that the previously selected album is centered:

It looks like the album data is correct, but the scroller isn’t centered on the correct album. What gives?

This is what the optional method initialViewIndexForHorizontalScroller was meant for! Since that method’s not implemented in the delegate, ViewController in this case, the initial view is always set to the first view.

To fix that, add the following code to ViewController.swift:

func initialViewIndex(scroller: HorizontalScroller) -> Int {
  return currentAlbumIndex
}

Now the HorizontalScroller first view is set to whatever album is indicated by currentAlbumIndex. This is a great way to make sure the app experience remains personal and resumable.

Run your app again. Scroll to an album as before, put the app in the background, stop the app, then relaunch to make sure the problem is fixed:

If you look at PersistencyManager‘s init, you’ll notice the album data is hardcoded and recreated every time PersistencyManager is created. But it’s better to create the list of albums once and store them in a file. How would you save the Album data to a file?

One option is to iterate through Album‘s properties, save them to a plist file and then recreate the Album instances when they’re needed. This isn’t the best option, as it requires you to write specific code depending on what data/properties are there in each class. For example, if you later created a Movie class with different properties, the saving and loading of that data would require new code.

Additionally, you won’t be able to save the private variables for each class instance since they are not accessible to an external class. That’s exactly why Apple created the archiving mechanism.

Archiving

One of Apple’s specialized implementations of the Memento pattern is Archiving. This converts an object into a stream that can be saved and later restored without exposing private properties to external classes. You can read more about this functionality in Chapter 16 of the iOS 6 by Tutorials book. Or in Apple’s Archives and Serializations Programming Guide.

How to Use Archiving

First, you need to declare that Album can be archived by conforming to the NSCoding protocol. Open Album.swift and change the class line as follows:

class Album: NSObject, NSCoding {

Add the following two methods to Album.swift:

required init(coder decoder: NSCoder) {
  super.init()
  self.title = decoder.decodeObjectForKey("title") as! String
  self.artist = decoder.decodeObjectForKey("artist") as! String
  self.genre = decoder.decodeObjectForKey("genre") as! String
  self.coverUrl = decoder.decodeObjectForKey("cover_url") as! String
  self.year = decoder.decodeObjectForKey("year") as! String
}

	
func encodeWithCoder(aCoder: NSCoder) {
  aCoder.encodeObject(title, forKey: "title")
  aCoder.encodeObject(artist, forKey: "artist")
  aCoder.encodeObject(genre, forKey: "genre")
  aCoder.encodeObject(coverUrl, forKey: "cover_url")
  aCoder.encodeObject(year, forKey: "year")
}

As part of the NSCoding protocol, encodeWithCoder will be called when you ask for an Album instance to be archived. Conversely, the init(coder:) initializer will be used to reconstruct or unarchive from a saved instance. It’s simple, yet powerful.

Now that the Album class can be archived, add the code that actually saves and loads the list of albums.

Add the following method to PersistencyManager.swift:

func saveAlbums() {
  var filename = NSHomeDirectory().stringByAppendingString("/Documents/albums.bin")
  let data = NSKeyedArchiver.archivedDataWithRootObject(albums)
  data.writeToFile(filename, atomically: true)
}

This will be the method that’s called to save the albums. NSKeyedArchiver archives the album array into a file called albums.bin.

When you archive an object which contains other objects, the archiver automatically tries to recursively archive the child objects and any child objects of the children and so on. In this instance, the archival starts with albums, which is an array of Album instances. Since Array and Album both support the NSCopying interface, everything in the array is automatically archived.

Now replace init in PersistencyManager.swift with the following code:

override init() {
  super.init()
  if let data = NSData(contentsOfFile: NSHomeDirectory().stringByAppendingString("/Documents/albums.bin")) {
    let unarchiveAlbums = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! [Album]?
      if let unwrappedAlbum = unarchiveAlbums {
        albums = unwrappedAlbum
      }
  } else {
    createPlaceholderAlbum()
  }
}
	
func createPlaceholderAlbum() {
  //Dummy list of albums
  let album1 = Album(title: "Best of Bowie",
		     artist: "David Bowie",
		     genre: "Pop",
		     coverUrl: "http://www.coversproject.com/static/thumbs/album/album_david%20bowie_best%20of%20bowie.png",
		     year: "1992")
		
let album2 = Album(title: "It's My Life",
		   artist: "No Doubt",
		   genre: "Pop",
		   coverUrl: "http://www.coversproject.com/static/thumbs/album/album_no%20doubt_its%20my%20life%20%20bathwater.png",
		   year: "2003")
		
let album3 = Album(title: "Nothing Like The Sun",
	           artist: "Sting",
		   genre: "Pop",
		   coverUrl: "http://www.coversproject.com/static/thumbs/album/album_sting_nothing%20like%20the%20sun.png",
		   year: "1999")
		
let album4 = Album(title: "Staring at the Sun",
		   artist: "U2",
		   genre: "Pop",
		   coverUrl: "http://www.coversproject.com/static/thumbs/album/album_u2_staring%20at%20the%20sun.png",
		   year: "2000")
		
let album5 = Album(title: "American Pie",
		   artist: "Madonna",
		   genre: "Pop",
		   coverUrl: "http://www.coversproject.com/static/thumbs/album/album_madonna_american%20pie.png",
		   year: "2000")
  albums = [album1, album2, album3, album4, album5]
  saveAlbums()
}

You have moved the placeholder album creation code into a separate method createPlaceholderAlbum()for readability. In the new code, NSKeyedUnarchiver loads the album data from the file, if it exists. If it doesn’t exist, it creates the album data and immediately saves it for the next launch of the app.

You’ll also want to save the album data every time the app goes into the background. This might not seem necessary now but what if you later add the option to change album data? Then you’d want this to ensure that all your changes are saved.

Since the main application accesses all services via LibraryAPI, this is how the application will let PersistencyManager know that it needs to save album data.

Now add the method implementation to LibraryAPI.swift:

func saveAlbums() {
  persistencyManager.saveAlbums()
}

This code simply passes on a call to LibraryAPI to save the albums on to PersistencyMangaer.

Add the following code to the end of saveCurrentState in ViewController.swift:

LibraryAPI.sharedInstance.saveAlbums()

And the above code uses LibraryAPI to trigger the saving of album data whenever the ViewController saves its state.

Build your app to check that everything compiles.

Unfortunately, there’s no easy way to check if the data persistency is correct though. You can check the simulator Documents folder for your app in Finder to see that the album data file is created but in order to see any other changes you’d have to add in the ability to change album data.

But instead of changing data, what if you added an option to delete albums you no longer want in your library? Additionally, wouldn’t it be nice to have an undo option if you delete an album by mistake?

Final Touches (Alternative of command Pattern in Objective-C)

You are going to add the final touches to your music application by allowing the user to perform delete actions to remove an album, or undo actions in case they change their mind!

Add the following property to ViewController:

// We will use this array as a stack to push and pop operation for the undo option
var undoStack: [(Album, Int)] = []

This creates an empty undo stack. The undoStack will hold a tuple of two arguments. The first is an Albumand the second is the index of the album.

Add the following code after reloadScroller() in viewDidLoad::

let undoButton = UIBarButtonItem(barButtonSystemItem: .Undo, target: self, action:"undoAction")
undoButton.enabled = false;
let space = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace, target:nil, action:nil)
let trashButton = UIBarButtonItem(barButtonSystemItem: .Trash, target:self, action:"deleteAlbum")
let toolbarButtonItems = [undoButton, space, trashButton]
toolbar.setItems(toolbarButtonItems, animated: true)

The above code creates a toolbar with two buttons and a flexible space between them. The undo button is disabled here because the undo stack starts off empty. Note that the toolbar is already in the storyboard, so all you need to do is set the toolbar items.

You’ll add three method to ViewController.swift for handling album management actions: add, delete, and undo.

The first is the method for adding a new album:

func addAlbumAtIndex(album: Album,index: Int) {
  LibraryAPI.sharedInstance.addAlbum(album, index: index)
  currentAlbumIndex = index
  reloadScroller()
}

Here you add the album, set it as the current album index, and reload the scroller.

Next comes the delete method:

func deleteAlbum() {
  //1
  var deletedAlbum : Album = allAlbums[currentAlbumIndex]
  //2
  var undoAction = (deletedAlbum, currentAlbumIndex)
  undoStack.insert(undoAction, atIndex: 0)
  //3
  LibraryAPI.sharedInstance.deleteAlbum(currentAlbumIndex)
  reloadScroller()
  //4
  let barButtonItems = toolbar.items as! [UIBarButtonItem]
  var undoButton : UIBarButtonItem = barButtonItems[0]
  undoButton.enabled = true
  //5
  if (allAlbums.count == 0) {
    var trashButton : UIBarButtonItem = barButtonItems[2]
    trashButton.enabled = false
  }
}

Consider each commented section below:

  1. Get the album to delete.
  2. Create a variable called undoAction which stores a tuple of Album and the index of the album. You then add the tuple into the stack
  3. Use LibraryAPI to delete the album from the data structure and reload the scroller.
  4. Since there’s an action in the undo stack, you need to enable the undo button.
  5. Lastly check to see if there are any albums left; if there aren’t any you can disable the trash button.

Finally, add the method for the undo action:

func undoAction() {
  let barButtonItems = toolbar.items as! [UIBarButtonItem]
  //1		
  if undoStack.count > 0 {
    let (deletedAlbum, index) = undoStack.removeAtIndex(0)
    addAlbumAtIndex(deletedAlbum, index: index)
  }
  //2		
  if undoStack.count == 0 {
    var undoButton : UIBarButtonItem = barButtonItems[0]
    undoButton.enabled = false
  }
  //3		
  let trashButton : UIBarButtonItem = barButtonItems[2]
  trashButton.enabled = true
}

Finally consider the comments for the method above:

  1. The method “pops” the object out of the stack, giving you a tuple containing the deleted Album and its index. You then proceed to add the album back.
  2. Since you also deleted the last object in the stack when you “popped” it, you now need to check if the stack is empty. If it is, that means that there are no more actions to undo. So you disable the Undo button.
  3. You also know that since you undid an action, there should be at least one album cover. Hence you enable the trash button.

Build and run your app to test out your undo mechanism, delete an album (or two) and hit the Undo button to see it in action

This is also a good place to test out whether changes to your album data is retained between sessions. Now, if you delete an album, send the app to the background, and then terminate the app, the next time you start the app the displayed album list should reflect the deletion.

If you want to get all the albums back, just delete the app and run it again from Xcode to install a fresh copy with the starter data.

Design Patterns in Swift