Advanced Gesture Recognisers

Through this article I’m going to show you how to use a UIPanGestureRecognizer to create a marquee selection tool that you can use in any of your UIView’s.

Gestures

UIGestureRecogniser’s are a really powerful way of working with touch events in iOS. They allow us to handle taps, pans, pinches and more. Through a simple state-driven API we can easily use them to detect lots of types of interactions in our apps.

Marquee Selection



Finder

Similar to what you would find in Finder or another file browser.

Recently I came across a feature I needed to build. Basically I needed to provide a marquee selection tool like you might see in Finder for selecting files and folders.

UIPanGestueRecognizer seemed like a perfect fit for the job since it provides location data while the you move your finger across the view. So I added one to my view and proceeded to write some code for handling the marquee itself.

Typical Solution

So I started off by checking the recognizer state and recording the initial location when state == .began

I then used location(in view:) while the gesture’s state == .changed — finally creating a rectangle between the two points.

At this point I had a working marquee tool but I was drawing the rectangle using draw(rect:) of the gesture.view, which wasn’t ideal, especially because it meant I couldn’t draw on ‘top’ of the subviews.

So I decided to add a view instead based on the gesture’s state.

.began – add the marqueeView to the source view
.changed – set the frame of the selection view
.ended – remove the selection view from the source view

At this point however I realised that this solution wasn’t ideal since I was now tied to this specific implementation of UIView. Furthermore, if I wanted to provide selection to something like a UICollectionView then I would have to copy/paste a lof of code.

Alternative Solution

The solution I came up with was to move the marqueeViewinto the gesture itself. In hindsight this seemed obvious. The gesture has everything I need to provide a selection rectangle. Its state-driven, provides the location of the touch events during a pan, and even provides me with the source view.

All I had to do was listen for the various state changes and insert/remove my marqueeView appropriately. Lets checkout an example.

import UIKit.UIGestureRecognizerSubclass

public final class MarqueeGestureRecognizer: UIPanGestureRecognizer {
    public private(set) var initialLocation: CGPoint = .zero
    public private(set) var selectionRect: CGRect = .zero
    public var tintColor: UIColor = .yellow
    public var zPosition: Int = 0

    public override init(target: Any?, action: Selector?) {
        super.init(target: target, action: action)
        addTarget(self, action: #selector(handleGesture(gesture:)))
    }

    private var marqueeView: UIView? {
        didSet {
            marqueeView?.backgroundColor = tintColor.withAlphaComponent(0.1)
            marqueeView?.layer.borderColor = tintColor.cgColor
            marqueeView?.layer.borderWidth = 1
            marqueeView?.layer.zPosition = CGFloat(zPosition)
        }
    }

    @objc private func handleGesture(gesture: MarqueeGestureRecognizer) {
        guard let view = gesture.view else { return }

        let currentLocation = gesture.location(in: view)
        selectionRect = CGRectReversible.rect(from: initialLocation, to: currentLocation)

        switch gesture.state {
        case .began:
            let marqueeView = UIView()
            view.insertSubview(marqueeView, at: zPosition)
            self.marqueeView = marqueeView
            initialLocation = currentLocation
        case .changed:
            marqueeView?.frame = selectionRect
        default:
            initialLocation = .zero
            selectionRect = .zero

            marqueeView?.removeFromSuperview()
            marqueeView = nil
        }
    } 
}

For the full code, you can download the Playground on GitHub:

Summary

Subclassing a UIGestureRecognizer and adding behaviour to it has a lot of advantages:

  • You can easily composite this into any view
  • You can add advanced behaviour to your gesture; e.g.
  • Hold down a second finger to constrain aspect ratio
  • The marqueeView’s lifecycle is bound to the state of the gesture; i.e.
  • no need to manage it from your view controller, etc…
  • Gesture’s are state-driven by default, which makes them great to work with.

Have a cool idea for another gesture recognizer? Maybe you’ve done something similar before? I’d love to hear about it.

Generic Delegate’s in Swift

A common pattern in cocoa development is the use of the delegate pattern. Most commonly used as a callback mechanism.

In Swift however sometimes we would like to use this pattern along with a generic implementation of our type. First, lets look at the problem.

The Problem

A typical approach would look like this:

struct Item { }

protocol ItemDelegate: class {
    func didSelect(item: Item)
}

final class ItemController {
    weak var delegate: ItemDelegate?
}

This works great while Item is a concrete type, but what if we wanted to make Item generic?

protocol Item { }
struct MyItem: Item { }

final class ItemController<ItemType: Item> {
    weak var delegate: ItemDelegate?
}

Well we can simply make our delegate generic as well right?

protocol ItemDelegate: class {
    func didSelect<ItemType: Item>(item: ItemType)
}

Well actually this isn’t right. To see the problem lets check out the callsite:

let controller = ItemController<MyItem>()
func didSelect<ItemType>(item: ItemType) where ItemType : Item {
    print("\(item)")
}

Specifically where ItemType : Item. Item is our protocol and not our concrete type MyItem. So even though we’ve successfully constrained our controller to a specific type, our delegate is much less specific.

The Solution

What we really want to do is define an associatedtype on our delegate.

protocol ItemDelegate {
    associatedtype ItemType: Item
    func didSelect(item: ItemType)
}

The problem is that now we have a generic constraint on our ItemDelegate. The solution is actually quite simple. Instead of constraining our ItemController to a specific Item, instead constrain it to a specific ItemDelegate.

Our final solution would look like the following:

protocol ItemDelegate: class {
    associatedtype ItemType: Item
    func didSelect(item: ItemType)
}

final class ItemController<Delegate: ItemDelegate> {
    weak var delegate: Delegate?
    func addItem(item: Delegate.ItemType)
}

Now if we look at our callsite:

let controller = ItemController<MyItem>()
func didSelect(item: MyItem) {
    print("\(item.name)")
}

Recap

By moving the generic constraint to the delegate we can now safely define an instance of ItemController and ensure our delegate callbacks are type-safe to the same concrete instance.

This is a fairly simple approach but makes working with generic delegate’s much simpler and much safer.