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.

Leave a rating/review
Save for later
Share

Update note: This tutorial was updated to Swift and iOS 8 by Bjørn Ruud. Original post by Tutorial Team member Tony Dahbura.

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, check out the details and get in touch!

Note from Ray: Full credit for this technique goes to Orta Therox, the newest member of our new “Code Team” at raywenderlich.com. Orta created the original sample project that Tony polished and converted into this tutorial.

Getting Started

Note: This section is optional for those who want to make the project from scratch. Experienced iOS developers might want to skip this section and proceed to the next section, “UIDynamicAnimator and UIAttachmentBehavior” where we have a starter project waiting for you.

Start up Xcode, select File\New\Project…, choose iOS\Application\Single View Application template, and click Next. Name the project DynamicToss, make sure the selected language is Swift, and set the device family to iPhone.

Now select the project name on the left and select the 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.

dynamictoss-orientation

Next, download an image that you’ll need for this project, courtesy of gameartguppy.com.

goldfish_feature

Unzip the file and add it to the Images asset catalog in your project.

Next, select Main.storyboard and go to the File Inspector in the utilities area on the right. To keep the scope of this tutorial small you will not use auto layout and size classes, so untick the Use Auto Layout checkbox. This will also disable size classes. In the dialog box that appears, choose to keep size class data for iPhone and select Disable Size Classes.

In the storyboard, add an image view to the default view controller and set the image to goldfish_feature. In the size inspector for the image view, set the following values: X=33, Y=137, Width=254, Height=172. Also, change the autoresizing mask to disable flexible height, width, right, and bottom margins. That means only the top and left margins stay “selected”.

Your screen should now look like this:

dynamictoss-imageview

Next, drag two plain Views 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:

dynamictoss-views

That covers the views you’ll need to set up in the storyboard. Next: on to some code!

Touch Handling

Open ViewController.swift and add the following properties to the class:

@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var redSquare: UIView!
@IBOutlet weak var blueSquare: UIView!

private var originalBounds = CGRect.zeroRect
private var originalCenter = CGPoint.zeroPoint

private var animator: UIDynamicAnimator!
private var attachmentBehavior: UIAttachmentBehavior!
private var pushBehavior: UIPushBehavior!
private var itemBehavior: UIDynamicItemBehavior!

You’ll connect the outlets in a moment. The other properties will control the image view’s movement and keep track of its state as the touch events go on.

Open Main.storyboard and right click (or control-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 imageView, drag onto your jetpack-equipped goldfish. Your three view properties should now be linked, like this:

finallinking

The red and blue squares will represent points that the UIKit dynamics 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 dynamics so that when you move that point around, the image view physically animates to move in tandem.

There’s one final thing you need to do – set up a gesture recognizer for the view. Open ViewController.swift and add this new method to the file:

@IBAction func handleAttachmentGesture(sender: UIPanGestureRecognizer) {
  let location = sender.locationInView(self.view)
  let boxLocation = sender.locationInView(self.imageView)

  switch sender.state {
  case .Began:
    println("Your touch start position is \(location)")
    println("Start location in image is \(boxLocation)")

  case .Ended:
    println("Your touch end position is \(location)")
    println("End location in image is \(boxLocation)")

  default:
    break
  }
}

You’re going add a gesture recognizer to detect dragging, aka panning, and call this method when panning 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 onto 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:

Your touch start position is (125.0,227.0)
Start location in image is (92.0,90.0)
Your touch end position is (195.5,374.0)
End location in image is (162.5,237.0)

Great! You’ve got the basic UI set up – now it’s time to make it dynamic.

UIDynamicAnimator and UIAttachmentBehavior

Note: If you skipped ahead from the previous section, download the starter project to continue from this point.

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.

  1. First you remove any existing animation behaviors that might be hanging around.
  2. 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.
  3. 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.
  4. 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:

Dragged View

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.

Bjørn Olav Ruud

Contributors

Bjørn Olav Ruud

Author

Over 300 content creators. Join our team.