In this UIKit Dynamics tutorial, you’ll learn how to toss views off-screen with gestures in a natural feeling way.
You may have seen this technique popularized in the popular app Tweetbot.
This tutorial is a good fit for intermediate developers because it covers some neat effects, such as rotation and fly-away animations using the native UIKit framework.
But if you’re completely new to UIKit dynamics, don’t fear – this tutorial will guide you through step by step.
Without further ado, let’s get to tossing!
The Code Team is a group of expert-level coders that spend their waking hours coming up particularly cool demos to demonstrate advanced techniques. The Tutorial Team then converts the best demos into high quality tutorials. If you’re an expert-level iOS developer and joining the Code Team piques your interest, contact me!
Getting Started
Start up Xcode, select File\New\Project…, choose iOS\Application\Single View Application template, and click Next. Name the project DynamicToss and set the device family to iPhone.
Now select the project name on the left and make sure to select General at the top of the Xcode window. Under Deployment Info/Device Orientation uncheck the ‘Landscape Left’ and ‘Landscape Right’ checkboxes, because your app will only run in portrait mode.
Next, download an image that you’ll need for this project, courtesy of gameartguppy.com.
Unzip the file and add it to your project.
Next, open Main.storyboard and add an image view with the following values: (X=33, Y=137, Width=254, Height=172, Image=goldfish_feature.jpg). Your screen should now look like this:
Next, drag two UIViews into your view controller that you will use to help track your gestures. Set them to the following settings:
- View 1: (X=156, Y=219, Width=8, Height=8, Background=red)
- View 2: (X=80, Y=420, Width=8, Height=8. Background=blue)
When done your view should look like this:
Add the following private properties to RWTViewController.m:
@property (nonatomic, weak) IBOutlet UIView *image; @property (nonatomic, weak) IBOutlet UIView *redSquare; @property (nonatomic, weak) IBOutlet UIView *blueSquare; @property (nonatomic, assign) CGRect originalBounds; @property (nonatomic, assign) CGPoint originalCenter; @property (nonatomic) UIDynamicAnimator *animator; @property (nonatomic) UIAttachmentBehavior *attachmentBehavior; @property (nonatomic) UIPushBehavior *pushBehavior; @property (nonatomic) UIDynamicItemBehavior *itemBehavior; |
Select the Main.storyboard file and right click on the View Controller. Drag from the circle to the right of the outlet named blueSquare
onto the small blue square view and release the mouse. This links the property to the view object.
Do the same for the red square, and finally for the property named image
, drag onto your jetpack-equipped goldfish. Your three view properties should now be linked, like this:
The red and blue squares will represent points that the UIDynamics physics engine uses to animate the image.
The blue square will simply represent where your touch began, i.e., where your finger first made contact with the screen. The red square will track your finger as it moves.
You’ll configure the UIDynamics framework so that when you move that point around, the image view physically animates them to move in tandem.
There’s one final thing you need to do – set up a gesture recognizer for the view. Open RWTViewController.m and add this new method to the file:
- (IBAction) handleAttachmentGesture:(UIPanGestureRecognizer*)gesture { CGPoint location = [gesture locationInView:self.view]; CGPoint boxLocation = [gesture locationInView:self.image]; switch (gesture.state) { case UIGestureRecognizerStateBegan:{ NSLog(@"you touch started position %@",NSStringFromCGPoint(location)); NSLog(@"location in image started is %@",NSStringFromCGPoint(boxLocation)); break; } case UIGestureRecognizerStateEnded: { NSLog(@"you touch ended position %@",NSStringFromCGPoint(location)); NSLog(@"location in image ended is %@",NSStringFromCGPoint(boxLocation)); break; } default: break; } } |
You’re going add a gesture recognizer to detect dragging, aka panning, and call this method that occurs. For now, this method simply displays the position of your finger in two coordinate systems (the view, and the image view).
To add the gesture recognizer, open Main.storyboard and drag a Pan Gesture Recognizer into the View. Then control-drag from the Pan Gesture Recognizer up to your View Controller, and connect it to the handleAttachmentGesture:
action.
Now build and run. Swipe or drag across the screen and you should see messages coming out on the debug window:
2014-07-13 14:01:49.666 DynamicToss[3999:60b] you touch started position {127, 365} 2014-07-13 14:01:49.667 DynamicToss[3999:60b] location in image started is {94, 228} 2014-07-13 14:01:50.097 DynamicToss[3999:60b] you touch ended position {113, 464} 2014-07-13 14:01:50.097 DynamicToss[3999:60b] location in image ended is {80, 327} |
Great! You’ve got the basic UI set up – now it’s time to make it dynamic.
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 RWTViewController.m and place the following code in the viewDidLoad
method underneath [super viewDidLoad]
.
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view]; self.originalBounds = self.image.bounds; self.originalCenter = self.image.center; |
The above code sets up a UIDynamicAnimator
, which is UIKit’s engine for physics-based animation. The reference view self.view
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.
Let’s add your first behavior to this animator. 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:
, underneath the two NSLog statements in the case UIGestureRecognizerStateBegan
section:
// 1 [self.animator removeAllBehaviors]; // 2 UIOffset centerOffset = UIOffsetMake(boxLocation.x - CGRectGetMidX(self.image.bounds), boxLocation.y - CGRectGetMidY(self.image.bounds)); self.attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:self.image offsetFromCenter:centerOffset attachedToAnchor:location]; // 3 self.redSquare.center = self.attachmentBehavior.anchorPoint; self.blueSquare.center = location; // 4 [self.animator addBehavior:self.attachmentBehavior]; |
Let’s go over this section by section:
- This first removes any existing animation behaviors that might be hanging around.
- Second this creates 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. - Updates 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 (right now these should be the same).
- Adds this behavior to the animator to make it take effect.
Next you need to tell the anchor point itself to follow your finger. To do this, add the following code to handleAttachmentGesture:
in the default
section (which occurs as the gesture changes):
[self.attachmentBehavior setAnchorPoint:[gesture locationInView:self.view]]; self.redSquare.center = self.attachmentBehavior.anchorPoint; |
This simply aligns the anchor point and red square to the finger’s current position. When your finger moves, the gesture recognizer calls this method to update the anchor point to follow your 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:
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 file:
- (void)resetDemo { [self.animator removeAllBehaviors]; [UIView animateWithDuration:0.45 animations:^{ self.image.bounds = self.originalBounds; self.image.center = self.originalCenter; self.image.transform = CGAffineTransformIdentity; }]; } |
Then call this in handleAttachmentGesture:
, underneath UIGestureRecognizerStateEnded
section:
[self 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:
static const CGFloat ThrowingThreshold = 1000; static const CGFloat ThrowingVelocityPadding = 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 UIGestureRecognizerStateEnded
case with the following:
case UIGestureRecognizerStateEnded: { [self.animator removeBehavior:self.attachmentBehavior]; //1 CGPoint velocity = [gesture velocityInView:self.view]; CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y)); if (magnitude > ThrowingThreshold) { //2 UIPushBehavior *pushBehavior = [[UIPushBehavior alloc] initWithItems:@[self.image] mode:UIPushBehaviorModeInstantaneous]; pushBehavior.pushDirection = CGVectorMake((velocity.x / 10) , (velocity.y / 10)); pushBehavior.magnitude = magnitude / ThrowingVelocityPadding; self.pushBehavior = pushBehavior; [self.animator addBehavior:self.pushBehavior]; //3 NSInteger angle = arc4random_uniform(20) - 10; self.itemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.image]]; self.itemBehavior.friction = 0.2; self.itemBehavior.allowsRotation = YES; [self.itemBehavior addAngularVelocity:angle forItem:self.image]; [self.animator addBehavior:self.itemBehavior]; //4 [self performSelector:@selector(resetDemo) withObject:nil afterDelay:0.4]; } else { [self resetDemo]; } break; } |
Let’s go over this section by section:
- 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 continuous — or instantaneous — 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. Finally, the push behavior is added 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!
Where To Go from Here?
Here is the final example project from this UIKit Dynamics tutorial.
Congratulations, you’ve now learned how to do some fancy UIKit dynamic animations that can give an app’s UI some pretty sweet effects.
If you want learn more about UIKit Dynamics, check out the two UIKit Dynamics chapters in iOS 7 for Tutorials.
Drop by the forums or leave a comment below to share your successes or ask questions about making cool animations in iOS. And use your new powers wisely!
UIKit Dynamics Tutorial: Tossing Views is a post from: Ray Wenderlich
The post UIKit Dynamics Tutorial: Tossing Views appeared first on Ray Wenderlich.