Lecture 6: Gesture Recognizers

Gesture Recognizers

Download the complete demo app Gestures we made in class
here. (Tab bar button item images used in app are from:
IconBeast). Many
references are made to the app in these notes.

Introduction

From experience, you probably know that there are more than
just one way to interact with the touch screen on the iPhone.
If you want to zoom into a picture, you "pinch" the screen.
When unlocking your phone you "swipe." Maybe in a game you had
to "rotate" something. All of these things are called gestures
and we will discuss how to incorporate them into your app.

UIGestureRecognizer

All gesture recognizers subclass from the
UIGestureRecognizer abstract class. Each subclass is
designed to detect and handle a specific gesture. The
documentation for UIGestureRecognizer functions and fields is
pretty straight forward, so you shouldn't have too much trouble
deciphering it. An important function for setting up a gesture
recognizer is:

func addTarget(_ target: AnyObject, action action: Selector)

The addTarget function allows you to connect a
class to a gesture reconginzer and specify a function that will
handle what happens when the gesture is detected. We saw an
example of this in the Gesture demo app:

This code is telling the UITapGestureRecognizer to send a
message to the GamePlayViewController class when it
detects a tap. Then, when the message is received the function
tappedView will be called with the sender of the
message (the tap gesture recognizer) passed in as an argument
of the function (that's what the : after "tappedView" is
for).

Another important function for setting up gesture
recognizers is:

func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer)

First note that this is not part of the
UIGestureRecognizer class, but rather a function of
the UIView class. Gesture recognizers help
dectect gestures on views and then send messages to
other objects to handle the event. They act as some type of
middle man/manager in the whole process of detecting a gesture
and doing something to respond to it. So, the function
addGestureRecognizer allows you to add a
UIGestureRecognizer to help detect and handle what
happens when a gesture is performed on a particular view. In
the Gesture demo app we saw:

Recall that self.gameBox is the UIView
that we performed gestures on in our game. In the code we are
adding the UITapGestureRecognizer to the
gameBox view. What this does is add a detector/handler
for when the gameBox view specifically is
tapped. Note that a single gesture recognizer can be added to
multiple views if you would like the same thing to happen when
gestures are performed on different views. However, most of the
time this isn't the case, so you can create new gesture
recognizers and customize them specifically for the view to
which you plan on adding them.

One more thing to note about gesture recognizers is that
they have states. The UIGestureRecognizer class has an
enum with 6 different possible states for a gesture
recognizer:

The three most commonly used states are probably
UIGestureRecognizerState.Began,
UIGestureRecognizerState.Changed, and
UIGestureRecognizerState.Ended. Knowing the state of
the gesture recognizer can be helpful if you want to only do
something when the gesture begins, changes, ends, etc. For
example, in the Gesture demo app we only verified moves after
checking that the state of a gesture recognizer was
UIGestureRecognizerState.Ended.

So, that's the basic setup for a
UIGestureRecognizer. Take a look at the
documentation for UIGestureRecognizer to check out
other things you can do with any given gesture recognizer (like
find the location of where the user performed a gesture on the
touch screen, get the number of touches on the screen, etc).
Now we will cover the details of each specific gesture
recognizer subclass to achieve a better understand of how they
work.

UITapGestureRecognizer

The UITapGestureRecognizer detects when the touch
screen is tapped. It is probably the most basic and familar
gesture.

The fields in the UITapGestureRecognizer are:

var numberOfTapsRequired: Int
var numberOfTouchesRequired: Int

These fields are pretty self explanatory. We used the tap
gesture recognizer in the Gesture demo app just by adding it to
the gameBox view.

UILongPressGestureRecognizer

The UILongPressGestureRecognizer detects when a
user presses down on a view for an extended amount of time (so
like holding a press down for one second versus just a quick
tap). For example, the app SnapChat uses this gesture. You have
to hold down the picture button to record a video and then
release when you are finished.

The fields in the UILongPressGestureRecognizer that
you can customize are:

The minimumPressDuration is the minimum amount of
time in seconds that a touch must press on a view before the
gesture is recognzied. By default, it is set to 0.5
seconds.

The numberOfTouchesRequired is the number of
fingers that must perform a long press in order for the gesture
to be recognized. By default, this is set to one.

The numberOfTapsRequired is the number of taps
required for the gesture to be recognized. By default this is
zero, which makes sense because long press isn't really a tap.
But who knows, there could be some good application of using
this field (and maybe you could test it out).

The allowableMovement is the maximum a finger can
during the gesture before the gesture fails. By default this is
set to 10 points.

UIPanGestureRecognizer

The UIPanGestureRecognizer detects when a user
drags their finger around on the touch screen. For example, you
do this in an app to scroll down a page.

The fields in the UIPanGestureRecognizer that you
can customize are:

var maximumNumberOfTouches: Int
var minimumNumberOfTouches: Int

These are pretty self explanatory.

The functions in the UIPanGestureRecognizer class
that you can call are:

Here's where things start to get more interesting. The long
press and tap gestures are both more or less just a tap, which
isn't all that exciting. A user doesn't expect too much when
they tap or long press. The rest of the gestures have more
expectation. When you perform the pan gesture, you expect
something to move and drag, right? That is where the above
functions come in.

The translationInView takes in a UIView
and returns a CGPoint that represents how much the
sender's view's center has moved with respect to the
UIView passed in. Since the pan gesture comes with the
expectation that a view will drag, you can use this function to
find where the view moved when the pan gesture was performed.
Then, you can apply it by resetting the center of the view. We
saw this in the Gesture demo app:

Since we added the UIPanGestureRecognizer to the
gameBox view, we know that sender.view will
refer to the gameBox. Therefore, we get the current
translation with respect to the
GamePlayViewController's view (the gameBox's
"superview") from the sender and make the gameBox move
by resetting its center.

Next the setTranslation function comes in. Since we
already moved the gameBox by resetting its center, we
need to reset the translation of the gesture recognizer. This
is because the translation of the view from
translationInView is not a delta between moves, but
rather how much a view has panned over time. We only want to
calculate a delta, so if we reset the translation to zero, we
basically are doing that. In the Gesture demo app we did it
like this:

sender.setTranslation(CGPointZero, inView: self.view)

It's not required to set the translation back to zero
though. For example, if you wanted something to pan faster over
time, you could set translation to some non-zero
CGPoint. You can play around with this and see what
happens.

The velocityInView function takes in
UIView and returns the velocity in points per second
of the sender's view with respect to the view passed in. This
might be helpful to the point just mentioned: changing how fast
a view pans.

UIPinchGestureRecognizer

The UIPinchGestureRecognizer detects when a user
"pinches" the touch screen with two fingers. For the pinch
gesture, there is some sort of expectation to zoom in when
pinching fingers closer together and to zoom out when fingers
are farther apart. For example, you have probably used this
gesture to zoom into pictures on an iPhone.

The fields in the UIPinchGestureRecognizer are:

var scale: CGFloat
var velocity: CGFloat { get }

The field scale is a scale factor for the sender's
view that is relative to the points of the two pinch touches on
the touch screen. It is the scale for the size of the view
going from before a pinch to after a pinch. In the Gesture demo
app, we used it like this:

Every UIView has field called transform
that is a CGAffineTransform. Whenever this field is
reset, the transform will be applied to the view. Therefore,
since we want to scale the view that is being pinched, we reset
the sender's view's transform to be scaled. Then, we reset the
scale to be 1 since, just like with the pan gesture, the scale
isn't a delta, so we need to reset it to in a way make it "act"
as a delta between different pinches. Again, this doesn't need
to be reset to zero if you want to have something zoom in or
zoom out with some sort of acceleration.

The field velocity is the velocity of the pinch in
scale factor per second. The field cannot be set because it's
read-only, but it could be used to do something interesting
with the scale perhaps.

UIRotationGestureRecognizer

The UIRotationGestureRecognizer detects when there
are two touches on the touch screen and they are moving in a
circular motion. You have probably done this gesture before in
a game or maybe when editing pictures on the iPhone.

The fields in the UIRotationGestureRecognizer
are:

var rotation: CGFloat
var velocity: CGFloat { get }

The rotation field is the amount the sender's view
has rotated in radians. Like other gesture recognizers' fields,
it does not measure a delta between rotations, but rather the
amount the view has rotated over time. In the Gesture demo app,
we used it like this:

This gesture was not included in the Gesture demo because it
was too similar to pan and made the game too difficult.
However, it is quite simple to implement since a transformation
does not need to by applied to a view. You would set it up in
the same way as the other gesture recognizers.