UIGestureRecognizer Tutorial: Creating Custom Recognizers

Learn how to detect circles in this custom UIGestureRecognizer tutorial. You’ll even learn how the math works! By Michael Katz.

Leave a rating/review
Save for later
Share

Custom gesture recognizers delight users by making apps feel unique and alive. If basic taps, pans, and rotations are the utility and pickup trucks of the iOS world, custom gesture recognizers are the flashy hot rods with custom paint jobs and hydraulics. Read this custom UIGestureRecognizer tutorial and learn all about gesture recognizers!

In this tutorial you’ll take a fun little “find the differences” game and make it interactive by adding a custom circle gesture recognizer to select the non-matching image. Along the way you’ll learn:

  • How to use UIGestureRecognizer subclasses to leverage the provided state machine and callback mechanism to simplify gesture detection.
  • How to fit a circle to a collection of touched points.
  • How to be “fuzzy” in recognizing specific shapes, as drawing with one’s finger is often imprecise.

Note: This gesture recognizer tutorial assumes you already have general knowledge of how gesture recognizers work and how to use a pre-defined gesture recognizer in your app. To get up to speed, read through this UIGestureRecognizer on this site.

Getting Started

MatchItUp is a simple game that shows four images to the user, three alike and one image that is slightly different than the others. The user’s job is to identify the odd one out by drawing a circle over it with a finger:

The MatchItUp! game

The MatchItUp! game

Download and open the starter project for this tutorial here.

Build and run your app; you’ll see four images, but you can’t select the odd one out yet. Your task is to add a custom gesture recognizer to this game. The custom gesture recognizer will detect when the user draws a circle around an image. If they draw around the odd one out, they win!

Adding a Custom Gesture Recognizer

Go to File\New\File… and select the iOS\Source\Cocoa Touch Class template to create a class called CircleGestureRecognizer as a subclass of UIGestureRecognizer. Make sure Swift is selected. Then click Next and then Create.

In order for a gesture recognizer to work, it has to be attached to a view in the responder chain. When a user taps on the screen, the touch event is forwarded along the stack of views with each view’s gesture recognizers receiving a chance to handle those touches.

Open GameViewController.swift and add an instance variable for your gesture recognizer:

var circleRecognizer: CircleGestureRecognizer!

Next, add the following code to the bottom of viewDidLoad():

circleRecognizer = CircleGestureRecognizer(target: self, action: "circled:")
view.addGestureRecognizer(circleRecognizer)

This creates the gesture recognizer and adds it to the main view.

But wait… if the goal is to have the user circle the differing image, why not add the recognizer to each image view instead of the main view?

That’s a great question — glad you asked! :]

When building gesture recognizers, you must compensate for the imprecise nature of the user interface. If you’ve ever tried to sign your name inside a little box on a touchscreen, you’ll know what I mean! :]

When you put the recognizer on the whole view, it’s more forgiving to users who start or continue a gesture slightly outside the bounds of the image’s box. Eventually, your recognizer will have a tolerance setting to help those who can’t draw a perfect circle.

Build and run your app; even though you’ve created a subclass of UIGestureRecognizer, you haven’t added any code yet so it will recognize… exactly zero gestures! To make it useful, your gesture recognizer needs to implement a gesture recognizer state machine.

The Gesture Recognizer State Machine

The simplest gesture in a user’s repertoire is a tap; the user puts a finger down and then lifts it up. There are two methods called on the gesture recognizer for this event: touchesBegan(_:withEvent:) and touchesEnded(_:withEvent:).

In the case of a simple tap gesture, these methods correspond to the gesture recognizer states .Began and .Ended:

Basic Two-State Machine

A Basic Tap Recognizer

To see this in action, you’ll implement this state machine in the CircleGestureRecognizer class.

First things first! Add the following import at the top of CircleGestureRecognizer.swift:

import UIKit.UIGestureRecognizerSubclass

UIGestureRecognizerSubclass is a public header in UIKit, but isn’t included in the umbrella UIKit header. Importing this is necessary since you will need it to update the state property which, otherwise, would be a read-only property in UIGestureRecognizer.

Now add the following code to the same class:

override func touchesBegan(touches: Set<NSObject>!, withEvent event: UIEvent!) {
  super.touchesBegan(touches, withEvent: event)
  state = .Began
}

override func touchesEnded(touches: Set<NSObject>!, withEvent event: UIEvent!) {
  super.touchesEnded(touches, withEvent: event)
  state = .Ended
}

If you ran the app now and tapped the screen, the app would crash since you are not handling the gesture yet.

Add the following to the class in GameViewController.swift:

func circled(c: CircleGestureRecognizer) {
  if c.state == .Ended {
    let center = c.locationInView(view)
    findCircledView(center)
  }
}

The gesture recognizer’s target-action is fired when there’s a state change in the gesture recognizer. When the fingers touch the screen, touchesBegan(_:withEvent) fires. The gesture recognizer sets its state to .Began, resulting in an automatic call to the target-action. When the fingers are removed, touchesEnded(_:withEvent) sets the state to .Ended, calling the target-action again.

Earlier, when you set up the gesture recognizer, you made the target-action the circled(_:) method. The implementation of this method uses the provided findCircledView(_:) to check which image was tapped.

Build and run your app; tap one of the images to select it. The game checks your response and moves you to the next round:

Tap an image to choose it

Tap an image to choose it

Handling Multiple Touches

So you have a working tap gesture recognizer, right? Not so fast, fancy fingers! :] Note that the methods are named with “touches” — plural. Gesture recognizers can detect multi-finger gestures, but the game’s circle recognizer is meant to just recognize a single-figure gesture.

Almost got away with it...

You’ll need a check that there’s only a single finger involved in the touch.

Open CircleGestureRecognizer.swift and modify touchesBegan(_:) so that the touches Set parameter will allow one UITouch per finger:

override func touchesBegan(touches: Set<NSObject>!, withEvent event: UIEvent!) {
  super.touchesBegan(touches, withEvent: event)
  if touches.count != 1 {
    state = .Failed
  }
  state = .Began
}

Here you’ve introduced a third state: .Failed. .Ended indicates that the gesture completed successfully , while the .Failed state indicates that the user’s gesture wasn’t what you expected.

It’s important that you quickly move the state machine to a terminal state, such as .Failed, so that other gesture recognizers waiting in the wings get a chance to interpret the touches instead.

Gesture State Machine 2

Build and run your app again; try some multi-finger taps and some single-finger taps. This time, only a single-finger tap should work to select the image.