Interactive Gestures can really bring your UI to life! In this guide we will explore some common use-cases for moving and transforming views based on gesture input.
Slide out trays, Hamburger menus, and any other view you want to move by sliding your finger can be configured using a UIPanGesture Recognizer.
trayView
.didPanTray
. You can add a Gesture Recognizer in Storyboard or add a Gesture Recognizer programmatically
Define an Instance Variable that can be accessed throughout the View Controller. We define these variables at the top of the ViewController Swift file, right above the viewDidLoad
method.
var trayOriginalCenter: CGPoint!
The code to make the Tray draggable will go inside our didPanTray
method.
NOTE: If you created your Gesture Recognizer and added an Action in Storyboard, the method will be proceeded by @IBAction
.
@IBAction func didPanTray(sender: UIPanGestureRecognizer) {
}
didPanTray
method, we want to access the translation property of the UIPanGestureRecognizer and store it in a variable. This will tell us how far our finger has moved from the original "touch-down" point as we drag. We will also print the translation value to the console to get a feel for what it means. You should see that the translation is a CGPoint with values for the x and y components. var translation = sender.translationInView(view)
print("translation \(translation)")
.Began
is called once at the very beginning of each gesture recognition..Changed
is called continuously as the user is in the process of "gesturing"..Ended
is called once at the end of the gesture.if sender.state == UIGestureRecognizerState.Began {
} else if sender.state == UIGestureRecognizerState.Changed {
} else if sender.state == UIGestureRecognizerState.Ended {
}
When the gesture begans (.Began
), store the tray's center into the trayOriginalCenter variable:
trayOriginalCenter = trayView.center
As the user pans (.Changed
), change the trayView.center
by the translation. Note: we ignore the x translation because we only want the tray to move up and down:
trayView.center = CGPoint(x: trayOriginalCenter.x, y: trayOriginalCenter.y + translation.y)
When a user stops panning the Tray, we want the tray to animate to an up or down position. We will infer that if the users last gesture movement was downward, they intend to close the tray to it's down position. Conversely, if they are NOT panning down, they must be panning up, and intend to open the tray to it's up position.
We can tell which way a user is panning by looking at the gesture property, velocity. Like translation, velocity has a value for both x and y components. If the y component of the velocity is a positive value, the user is panning down. If the y component is negative, the user is panning up.
Since we are focusing on the user's last gesture movement, we will check for the velocity in the .Ended
condition of our gesture state conditional statement.
var velocity = sender.velocityInView(view)
. Pan Gesture Recognizer
var trayDownOffset: CGFloat!
var trayUp: CGPoint!
var trayDown: CGPoint!
viewDidLoad
method, assign values to the trayDownOffset
, trayUp
and trayDown
variables . The trayDownOffset
will dictate how much the tray moves down. 160 worked for my tray, but you will have to adjust this value to accommodate the specific size of your tray. trayDownOffset = 160
trayUp = trayView.center
trayDown = CGPoint(x: trayView.center.x ,y: trayView.center.y + trayDownOffset)
didPanTray
method, within the gesture state, .Ended
, create a conditional statement to check the y component of the velocity. In the case that the tray is moving down, animate the tray position to the trayDown
point, otherwise, animate it towards the trayUp
point. Animating View Properties
if velocity.y > 0 {
UIView.animateWithDuration(0.3, animations: { () -> Void in
trayView.center = trayDown
})
} else {
UIView.animateWithDuration(0.3, animations: { () -> Void in
trayView.center = trayUp
})
}
You can also try animating the ending tray motion with a bounce using the damping ratio and initial spring velocity. Spring Animation
This Use-Case will explore using multiple gesture recognizers simultaneously to scale and rotate an ImageView.
didPinch
and didRotate
. You can add a Gesture Recognizer in Storyboard or add a Gesture Recognizer programmatically
Since we will be using multiple gesture recognizers at the same time, we will need to configure our ViewController to support Simultaneous Gesture Recognizers.
Within the didPinch
method...
sender.view
returns a generic UIView so we need to specify that we are working with a UIImageView using as! UIImageView
.CGAffineTransformScale
instead of CGAffineTransformMakeScale
. This is because CGAffineTransformScale
allows us to add to the previous transform state as an argument, previousTransfrom
whereas CGAffineTransformMakeScale
will overwrite it completely. Combining Transforms
previousTransform
each time the method is called. If we didn't reset the scale, each time around the scale that was added to the previousTransform
would be doubled and our Image View would scale out of control! But don't take my word for it, run the app without resetting the scale back to 1 and see what happens! @IBAction func didPinch(sender: UIPinchGestureRecognizer) {
let scale = sender.scale
let imageView = sender.view as! UIImageView
let currentTransform = imageView.transform
imageView.transform = CGAffineTransformScale(imageView.transform, scale, scale)
sender.scale = 1
}
Within the didRotate
method...
sender.view
returns a generic UIView so we need to specify that we are working with a UIImageView using as! UIImageView
.CGAffineTransformRotate
instead of CGAffineTransformMakeRotate
for the same reasons we chose our scale transform method. Combining Transforms
@IBAction func didRotate(sender: UIRotationGestureRecognizer) {
let rotation = sender.rotation
let imageView = sender.view as! UIImageView
let previousTransform = imageView.transform
imageView.transform = CGAffineTransformRotate(previousTransform, rotation)
sender.rotation = 0
}