Tracer, a Swift Drawing View, from Concept to CI
Tags: swift, ci, automatedtesting, cocoapods
Published: 2019-04-02 14:05:23

---
title: Tracer, a Swift Drawing View, from Concept to CI
published: true
description: Demonstrates the creation of a custom UIView for drawing, and goes through the process of adding tests, CI, and packaging via Cocoapods.
tags: 
  - swift
  - ci
  - automated-testing
  - cocoapods
---

Introduction
============

A common way for children's apps to teach letters is to have someone draw the letter, the app tracking the user's drawing. For this article, we'll implement this functionality by subclassing `UIView`, adding features incrementally, adding tests, managing Cocoapod dependencies, and using fastlane. You can follow along using the source code on [GitHub](https://github.com/jamiely/Tracer). Along the way, I've added links to git tags, pointers to the code at specific points in time.

Touches
=======

There are several `UIView` callbacks which deal with users touching the view.

```swift
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
```

When we detect that someone has moved their touch, we can grab the list of touches, which includes the current and previous position of the touch.  We'll store those in a `struct Line`.

```swift
struct Line {
    let start: CGPoint
    let end: CGPoint
}
```

Then, we'll draw these lines in the the `UIView`'s `draw` call. The drawing functionality is similar to other drawing APIs like HTML canvas.

```swift
override func draw(_ rect: CGRect) {
    lines.forEach(drawLine)
}

private func drawLine(line: Line) {
    guard let context = UIGraphicsGetCurrentContext() else {
        print("ERROR: no context available")
        return
    }
    context.move(to: line.start)
    context.addLine(to: line.end)
    context.setStrokeColor(UIColor.black.cgColor)
    context.strokePath()
    UIGraphicsEndImageContext()
}
```

[refs/tags/line-drawing](https://github.com/jamiely/tracer/releases/tag/line-drawing)

{% youtube g7SyzN9jgsI %}

Out of Bounds
=============

We want to implement a feature where a user has to follow a particular path.  If the user goes beyond some threshold of this path, then we want to detect that in order to give the user some sort of indication.

There are at least two ways we can implement this:

1. We can allow the user to draw as long as any point drawn is within a threshold from any point in the expected path.
2. We can track the progress of the user as they draw, and note which points on the expected path have been drawn, and require that the user continue on the path. In this way, we only need to check parts of the expected path that we have not drawn yet.

We'll implement #1 first since it is simpler.

To implement this, we'll first need to accept some expected path. Although we may want smooth paths at some point using bezier paths, we'll keep things simple and only support straight lines between segments. We'll display the path under our drawing to guide our touch. Since this expected path won't change, we'll draw this on an image. We'll display the image on an image view behind our drawing.

![Shows the expected path, traced path, and out of bounds concept](https://thepracticaldev.s3.amazonaws.com/i/e74x9e4vvfbg0dihj05x.png)

Next, when we draw, we will compare the points we draw, and calculate the distance from each of the points in the expected path. This is fairly inefficient but there are ways of making it quicker. One way is to partition the space into a set of areas and note which points in the expected path fall into these areas. (Related to [BSP](https://en.wikipedia.org/wiki/Binary_space_partitioning).) For now, we will just use the trivial method of doing this. If the line that we draw is beyond some threshold distance from the closest point on the expected path, then we will give some indication (by drawing the line red).

We create a property that will draw the expected path on the image associated with a `UIImageView` when it is set.

```swift
var expectedPath: Array<CGPoint> {
    get { return _expectedPath }
    set {
        _expectedPath = newValue
        drawExpectedPath(points: newValue)
    }
}

private func drawExpectedPath(points: Array<CGPoint>) {
    UIGraphicsBeginImageContext(expectedPathView.bounds.size)
    
    guard var last = points.first,
        let context = UIGraphicsGetCurrentContext() else {
        print("There should be at least one point")
        return
    }
    
    context.setFillColor(UIColor.white.cgColor)
    context.fill(expectedPathView.bounds)
    
    context.setStrokeColor(UIColor.blue.cgColor)
    points[1..<points.count].forEach { pt in
        context.move(to: last)
        context.addLine(to: pt)
        context.strokePath()
        last = pt
    }
    
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    expectedPathView.image = image
}

```

We change the `touchesMoved` call to note the color that should be used for the line. Black if the line is valid and red if invalid. We'll add a property in the `Line` struct to hold the color as well.

```swift
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    touches.forEach {
        let start = $0.previousLocation(in: self)
        let end = $0.location(in: self)
        let color = colorForPoints(start, end)
        self.lines.append(Line(start: start, end: end, color: color))
    }
    setNeedsDisplay()
}
```

`colorForPoints` just checks if both of the line's points are within some threshold of any of the points in the expected path, returning black if so, and otherwise red.

```swift
private func colorForPoints(_ pts: CGPoint...) -> CGColor {
    if pts.allSatisfy(isPointWithinBounds) {
        return UIColor.black.cgColor
    }
    
    return UIColor.red.cgColor
}

private func isPointWithinBounds(_ pt: CGPoint) -> Bool {
    let threshold: CGFloat = 75
    return expectedPath.contains { ept in
        let dx = pt.x - ept.x
        let dy = pt.y - ept.y
        let distance = sqrt(dx * dx + dy * dy)
        return distance < threshold
    }
}
```

![A red line denotes out of bounds](https://thepracticaldev.s3.amazonaws.com/i/ovw08t7x71x2bkdhexmn.png)

[refs/tags/red-lines](https://github.com/jamiely/tracer/tree/red-lines)

{% youtube ZzuO4-Euju8 %}

Refining Features
=================

Although we've made good progress, this is somewhat unimpressive. There are two problems. 

* Firstly, things would look a lot better with some nice image outlining the path we have to trace, like a big letter T (we pick T because it is easy to path). 
* Secondly, when we specify a path and threshold, we want the view to figure out waypoints between the given path so that the distance between any two points is less than the threshold. This will ensure that the red lines we generate are what we'd expect.

If you are interested in reading more, this post is continued at https://jamie.ly/blog/programming/2019/04/01/tracer-a-swift-drawing-view.html, where we'll add new features, import cocoapods, add unit tests and property-based tests, add CI, and distribute our code via Cocoapods.