iOS

March 22, 2019

The delegation pattern in Swift

The delegation pattern is commonly used pattern in Swift. Many of the native components like UISearchBar, UICollectionView, UITableView use this pattern.

The purpose of the delegation pattern is to be able to changing the behavior of an object by modifying another object. For example, the UISearchBar’s behavior can be changed by modifying its delegate. The delegate is in control over what happens when text inside the search bar changes or when the search button is tapped, not the search bar itself. If the search bar was responsible for what happened in those instances you would need a lot of search bar components.

Let’s create our own component that will have a delegate. In this example we will create an object detector that can detect certain objects from a video player. (Usually the delegation pattern is used on UI component, but it can be just as effective elsewhere.)

The detector will be able to detect a number of different types. For these types we create an enum like so.

1
2
3
4
5
6
enum ObjectType {
  case Car
  case Person
  case Bird
  // ... etc
}

Then, we start of by making a protocol for the delegate. These are the methods the delegate will need to implement and that the Detector will call. Let’s say that the detector lets the delegate know every time it finds an object. It also lets the delegate be able to set the frame rate for how many times a second it should look for objects.

1
2
3
4
protocol DetectorDelegate {
  func detector(detectedObject: ObjectType, atPosition: CGPoint)
  func detectorSetFrameRate() -> Float
}

This protocol will then be used in the Detector class. Every time the detector find an object or needs to know the frame rate it will call the delegate methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Detector() {
  var delegate: DetectorDelegate?
  var videoPlayer: VideoPlayer?
  var frameRate: Float = 0.0
  var position: CGFloat?

  init() {
    // Initiate the detector
  }

  func startDetecting() {
    var objectType: ObjectType?
    // The delegate sets the frame rate
    frameRate = delegate.detectorSetFrameRate()

    // ... Code for detecting the object from an image
    if(objectDetected) {
      var objectType = objectDetected.Type
      var position = objectDetected.position

      // Finally we call the delegate method
      delegate.detector(detectedObject: objectType, atPosition: position)
    }
  }
}

Now that we have these in place, all we need to do is to use them in the class that is the delegate. That class is usually the class that holds the Detector. In our example it will be a View Controller, which means it will have to conform to the protocol i.e. implement the two methods set in the protocol from before.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class ViewController: UIViewController, DetectorDelegate {
  var detector: Detector?
  var videoPlayer: VideoPlayer?

  init(videoPlayer: VideoPlayer, detector: Detector) {
    self.videoPlayer = videoPlayer
    self.detector.videoPlayer = videoPlayer
   
    self.detector = detector
    self.detector.delegate = self      // Notice that we set the ViewController as the delegate.
  }
 
  // DetectorDelegate function. Called when detecting an object
  func detector(detectedObject: ObjectType, atPosition: CGPoint) {
    if(detectedObject == ObjectType.Car) {
      handleCarDetected(atPosition)
    }
  }

  // DetectorDelegate function for setting the frame rate
  func detectorSetFrameRate() -> Float {
    return calculateFrameRate()
  }

  private func calculateFrameRate() {
    var rate = 0
    // do some calculations to calculate the frame rate
    return rate
  }

  private func handleCarDetected(position: CGPoint) {
    // Do some stuff when a car is detected
  }
}

Conclusion

So the benefit from all of this is that we have an object detector component that can detect a few different objects. But all it does is detect them. What happens when it does is up to the delegate. Thus, changing the delegate will alter the behavior of the detector.

As stated before, many of the native components use this pattern.

The most common examples of these are UITableView, UICollectionView, . There is even the AppDelegate that delegates what happens to the app at a higher level when, for instance, the app becomes active or goes into the background.

1
2
3
4
5
6
7
8
9
10
11
class AppDelegate: UIResponder, UIApplicationDelegate
{
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { return true }
    func applicationWillResignActive(_ application: UIApplication) {}
    func applicationDidEnterBackground(_ application: UIApplication) {}
    func applicationWillEnterForeground(_ application: UIApplication) {}
    func applicationDidBecomeActive(_ application: UIApplication) {}
    func applicationWillTerminate(_ application: UIApplication) {}
}

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *