UIKit Dynamics and Swift Tutorial: Tossing Views
Learn how to toss your views around with gestures and realistic physics behavior in this UIKit Dynamics tutorial! By Bjørn Olav Ruud.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
UIKit Dynamics and Swift Tutorial: Tossing Views
15 mins
UIDynamicAnimator and UIAttachmentBehavior
The first thing you want to do is make the image view move as you drag it. You will do this with a type of UIKit Dynamics class called a UIAttachmentBehavior
.
Open ViewController.swift and place the following code in viewDidLoad()
, below super.viewDidLoad()
.
animator = UIDynamicAnimator(referenceView: view)
originalBounds = imageView.bounds
originalCenter = imageView.center
The above code sets up a UIDynamicAnimator
, which is UIKit’s engine for physics-based animation. You provide the view controller’s view as a reference view which defines the coordinate system for the animator.
You add behaviors to an animator, which allow you to do things like attaching views, pushing views, making them be affected by gravity, and more.
You’ll start with a UIAttachmentBehavior
, to make the image view track your finger when you make a pan gesture.
To do this, add the following code to handleAttachmentGesture(sender:)
, underneath the two println
statements in the case .Began:
section:
// 1
animator.removeAllBehaviors()
// 2
let centerOffset = UIOffset(horizontal: boxLocation.x - imageView.bounds.midX,
vertical: boxLocation.y - imageView.bounds.midY)
attachmentBehavior = UIAttachmentBehavior(item: imageView,
offsetFromCenter: centerOffset, attachedToAnchor: location)
// 3
redSquare.center = attachmentBehavior.anchorPoint
blueSquare.center = location
// 4
animator.addBehavior(attachmentBehavior)
Let’s go over this section by section:
Attaching an anchor point to a view is like installing an invisible rod that connects the anchor point to a fixed attachment position on the view.
- First you remove any existing animation behaviors that might be hanging around.
- Next, you create a
UIAttachmentBehavior
that attaches the the point inside the image view where the user taps to an anchor point (which happens to be the exact same point). Later on, you will change the anchor point, which will cause the image view to move.Attaching an anchor point to a view is like installing an invisible rod that connects the anchor point to a fixed attachment position on the view.
- Update the red square to indicate the anchor point, and the blue square to indicate the point inside the image view that it is attached to. When the gesture starts, these will be the same point.
- Add this behavior to the animator to make it take effect.
Next you need to tell the anchor point itself to follow your finger. In handleAttachmentGesture(_:)
, replace the break
statement in the default:
case with the following code:
attachmentBehavior.anchorPoint = sender.locationInView(view)
redSquare.center = attachmentBehavior.anchorPoint
The default case handles the gesture update calls as the user pans around. The code here simply aligns the anchor point and red square to the finger’s current position. When the user’s finger moves, the gesture recognizer calls this method to update the anchor point to follow the touch. In addition, the animator automatically updates the view to follow the anchor point.
Build and run, and you are now able to drag the view around:
Notice how the view isn’t just translated around the screen; if you start the gesture in a corner of the image, the view will rotate as you move it because of the anchor point.
However it would be nice to return the view back to its original position when you’re done dragging. To fix this, add this new method to the class:
func resetDemo() {
animator.removeAllBehaviors()
UIView.animateWithDuration(0.45) {
self.imageView.bounds = self.originalBounds
self.imageView.center = self.originalCenter
self.imageView.transform = CGAffineTransformIdentity
}
}
Then call this in handleAttachmentGesture(_:)
, below the println
calls in the .Ended:
section:
resetDemo()
Build and run, and now after you drag an image it should revert to its original position.
UIPushBehavior
Next, you need to detach the view when you stop dragging, and endow it with momentum so that it can continue its trajectory when you release it while in motion. You will do this with a UIPushBehavior
.
First, you’ll need two constants. Add these to the top of the file:
let ThrowingThreshold: CGFloat = 1000
let ThrowingVelocityPadding: CGFloat = 35
ThrowingThreshhold
indicates how fast the view must be moving in order to have the view continue moving (versus immediately returning to its original spot). ThrowingVelocityPadding
is a magic constant that affects how fast or slow the toss should be (this was chosen by trial and error).
Finally, inside handleAttachmentGesture(_:)
, replace the call to resetDemo()
inside the .Ended
case with the following:
animator.removeAllBehaviors()
// 1
let velocity = sender.velocityInView(view)
let magnitude = sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y))
if magnitude > ThrowingThreshold {
// 2
let pushBehavior = UIPushBehavior(items: [imageView], mode: .Instantaneous)
pushBehavior.pushDirection = CGVector(dx: velocity.x / 10, dy: velocity.y / 10)
pushBehavior.magnitude = magnitude / ThrowingVelocityPadding
self.pushBehavior = pushBehavior
animator.addBehavior(pushBehavior)
// 3
let angle = Int(arc4random_uniform(20)) - 10
itemBehavior = UIDynamicItemBehavior(items: [imageView])
itemBehavior.friction = 0.2
itemBehavior.allowsRotation = true
itemBehavior.addAngularVelocity(CGFloat(angle), forItem: imageView)
animator.addBehavior(itemBehavior)
// 4
let timeOffset = Int64(0.4 * Double(NSEC_PER_SEC))
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeOffset), dispatch_get_main_queue()) {
self.resetDemo()
}
} else {
resetDemo()
}
Let’s go over this section by section:
Using velocity and your old friend the Pythagorean theorem, you compute the magnitude of the velocity — which is the hypotenuse of the triangle formed from the x direction velocity and the y direction velocity.
To understand the theory behind this check out this Trigonometry for Game Programming tutorial.
A push behavior applies a force to the specified items. In this case, it’s an instantaneous force against the image.
The desired direction is composed of the x and y velocities converted to a vector that gives the directional portion. Once you have the push behavior set up, you add it to the animation sequence.
Some of this depends on how close to the edge your finger is when it initiates the gesture.
Play around with the values here and watch how the movements change the effects. The values used give a nice, flowing rotation with a cool spinning effect!
- Ask the gesture for the velocity of the drag.
Using velocity and your old friend the Pythagorean theorem, you compute the magnitude of the velocity — which is the hypotenuse of the triangle formed from the x direction velocity and the y direction velocity.
To understand the theory behind this check out this Trigonometry for Game Programming tutorial.
- Assuming the gesture magnitude exceeds your minimum threshold set up for the action, you set up a push behavior.
A push behavior applies a force to the specified items. In this case, it’s an instantaneous force against the image.
The desired direction is composed of the x and y velocities converted to a vector that gives the directional portion. Once you have the push behavior set up, you add it to the animation sequence.
- This section sets up some rotations to make the image “fly away”. You can read up on the complicated math here.
Some of this depends on how close to the edge your finger is when it initiates the gesture.
Play around with the values here and watch how the movements change the effects. The values used give a nice, flowing rotation with a cool spinning effect!
- After a specified interval of time, the animation resets by sending the image back to its destination, so it zips off and returns to the screen — just like a ball bouncing off a wall!
Build and run, and you should now be able to toss your view offscreen in a pleasing manner!