Others Talk, We Listen.

iOS 7 Tutorial Series: Introducing UIKit Dynamics

by Adam Jones on Oct 25, 2013

At the WWDC 2013 Keynote, Apple’s Senior Vice President of Design, Jony Ive, introduced the new interface changes for iOS 7. During his recorded video presentation he referred to the "importance of motion and depth" and "new approaches to animation."

Moving and tilting your device while viewing the Home screen is perhaps the most visible example of the focus on motion and depth. The device background will appear to adjust to suit your perspective, almost as if you are looking underneath the layer of icons. Apple calls this behavior Parallax and it’s part of an idea called Motion Effects. The tabs in Mobile Safari have also been updated to look like pages in a stack, thus providing visual depth.

Apple has made it easier for developers to incorporate complex 2D animations through a new framework called UIKit Dynamics. While all of the animations discussed in this post are possible with existing view animations and with Core Animation, the new Dynamics framework adds a layer of abstraction to allow for a more declarative approach. You simply need to decide what it is you want to accomplish with your animations.

There is a great example in iOS 7 that highlight UIKit Dynamics - the new lock screen. On the "iOS 6 lock screen you can tap the camera icon and drag upwards to reveal the camera interface. Lifting your finger before reaching a certain point will cause the lock screen to softly fall back down to the bottom, returning to where it fully covers the camera screen. Performing the same action in iOS 7 will reveal a new behavior – the lock screen will slide down and collide with the bottom, bouncing until it comes to rest. In a sense, this behavior mimics dropping an object with mass and elasticity from a small height. This is a key component of UIKit Dynamics in that Apple has made it possible to incorporate real-world physics into the 2D animation.

Examples like the new lock screen are easily achieved with the new UIKit Dynamics framework, at the heart of which are three concepts – dynamic items, dynamic behaviors, and dynamic animators. Let’s start by covering dynamic items.

This post is accompanied by a sample application that explores many of the concepts that follow.

Dynamic Items

The focus of UIKit Dynamics is all about the interactions between items and their animations. The one requirement, in order to bestow dynamic behavior on an item, is that the item must conform to the UIDynamicItem protocol. This protocol defines three vital properties that allow the UIKit Dynamics engine to track animation states over time: the bounds, center, and transform properties. Only the center property is read/write since it needs to be updated as the animation progresses. The transform property returns a CGAffineTransform object but only the rotation value is used.

By default, only the UIView and UICollectionViewLayoutAttributes classes implement the UIDynamicItem protocol. This is useful since most animations will likely involve one of these classes. However, you can still create a custom class that is eligible for animation with UIKit Dynamics, provided your class conforms to the UIDynamicItem protocol and exposes a bounds, center, and transform property.

Now, suppose you have a UIView or some other custom class worthy of dynamic animation. The next step is to decide what behaviors you want your dynamic item to exhibit.

Dynamic Behaviors

In UIKit Dynamics, a dynamic behavior represents a real world physical behavior that you can apply to your animation. There are six classes that provide all the behaviors you will need. They are:

  • UIAttachmentBehavior
  • UICollisionBehavior
  • UIGravityBehavior
  • UIPushBehavior
  • UISnapBehavior
  • UIDynamicItemBehavior (not a concrete behavior; allows customization of the various properties used in the other behaviors)

Any of these behaviors can be combined if they make sense, but note that it is possible to define behaviors that conflict with each other.

The Attachment Behavior

Use the UIAttachmentBehavior class to create an invisible connection between two items. The connection can be defined as one of the following:

  1. One dynamic item attached to another dynamic item, e.g. two UIViews
  2. One dynamic item attached to an anchor point, e.g. a UIView to a CGPoint

If two items are attached, one of the items is always the leader and the other the follower. If a dynamic item with an attachment behavior moves, any attached follower will also move (multiple dynamic items can be attached to a single dynamic item). The movement is constrained by a length property that is established when the attachment behavior is initialized. The length can be adjusted but only after the attachment behavior has been created.

When a dynamic item is attached to an anchor point, the behavior is much the same as a pendulum. The dynamic item, in this example represented by the moving end of the pendulum, can have motion in a fixed plane but it will never increase its distance from the pendulum's attached end, i.e. the anchor point.

In addition to the length and anchorPoint properties, you can also configure properties for damping and frequency of oscillation. Damping is configured with a value between 0.0f and 1.0f, with 0.0f being no effect at all and 1.0f representing the maximum effect. Apple's documentation states that the setting for frequency of oscillation is in hertz. For the attachment behavior, the default values for each are not specified.

Creating an attachment behavior is simple. You just need to identify the items you wish to connect.

// Initialize a couple of UIView objects
// The UIView class already conforms to the UIDynamicItem protocol
UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 100.0f)];
UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(200.0f, 0.0f, 100.0f, 100.0f)];
 
// Create an attachment behavior between two dynamic items, v1 and v2
UIAttachmentBehavior *attachment1 = [[UIAttachmentBehavior alloc] initWithItem:v1 attachedToItem:v2];
 
// Configure damping and frequency (of oscillation) properties for attachment1
[attachment1 setDamping:0.5f];
[attachment1 setFrequency:0.2f];
 
// Create an attachment behavior between a dynamic item and an anchor point, v2, and a CGPoint
UIAttachmentBehavior *attachment2 = [[UIAttachmentBehavior alloc] initWithItem:v2 
	attachedToAnchor:CGPointMake(100.0f, 100.0f)];

Attachment behaviors are useful when the position of two items should exhibit a connected behavior. You can even chain attachments so that Item B follows Item A, and Item C follows Item B, and Item D follows Item C, and so on. There is a good example of this attachment behavior in iOS 7 – the scrolling action in the Chat application. As the message bubbles move up or down the screen you’ll notice that they appear to be attached by invisible springs, first separating then catching up to each other. Each bubble reacts to the immediately preceding bubble, depending on which way you are scrolling.

The Collision Behavior

The UICollisionBehavior class provides the ability for dynamic items to participate in collisions with each other and/or any boundary you specify. A boundary can be any of the following:

  1. The reference bounds of the animation, which refers to the bounds of the view that contains the items that you wish to animate. The reference bounds are set when you initialize a dynamic animator (see example code below). This type of bounds defines a rectangular area that will contain your collisions
  2. The reference bounds of the animation with a specified inset, for when you wish to define a smaller boundary area inside an existing view.
  3. A line segment connected by two points which effectively defines an invisible wall that your dynamic items can collide with.
  4. A boundary defined by a Bezier path is also possible. For example, if you want a dynamic item to collide and slide in a curved path, you could use this type of boundary.

You create a collision behavior by adding dynamic items during initialization. By rule, only dynamic items that are added to the same collision behavior are able collide with each other, as well as any boundaries defined by the behavior. You determine whether your items should collide with their sibling items and boundaries by setting a property called collisionMode. There are three options that control the intended outcome:

  • UICollisionBehaviorModeEverything
  • UICollisionBehaviorModeBoundaries
  • UICollisionBehaviorModeItems

The default is UICollisionBehaviorModeEverything, which means the collision behavior’s items will collide with each other as well as its configured boundaries. A setting of UICollisionBehaviorModeBoundaries results in the dynamic items colliding only with the behavior’s boundaries, while UICollisionBehaviorModeItems limits the interaction to just the dynamic items.

Creating a collision behavior involves identifying the participating dynamic items, setting a reference bounds, and then setting a collision mode.

// Suppose we already have a few UIViews that we want to participate in collisions
// v1, v2, v3, v4, v5, and v6
// Create a collision behavior and initialize with two of the views
UICollisionBehavior *collision1 = [[UICollisionBehavior alloc] initWithItems:@[v1, v2]];
 
// Set the bounds and collision mode = everything; v1 and v2 will collide with each other and its boundaries
[collision1 setTranslatesReferenceBoundsIntoBoundary:YES];
[collision1 setCollisionMode:UICollisionBehaviorModeEverything];
 
// Create a second collision behavior
UICollisionBehavior *collision2 = [[UICollisionBehavior alloc] initWithItems:@[v3, v4]];
 
// Set the bounds and collision mode = other items only; v3 and v4 will collide only with each other
[collision2 setTranslatesReferenceBoundsIntoBoundary:YES];
[collision2 setCollisionMode:UICollisionBehaviorModeItems];
 
// And a third collision behavior...
UICollisionBehavior *collision3 = [[UICollisionBehavior alloc] initWithItems:@[v5, v6]];
 
// Set the bounds and collision mode = only boundaries; v5 and v6 will collide only with it boundaries
[collision3 setTranslatesReferenceBoundsIntoBoundary:YES];
[collision3 setCollisionMode:UICollisionBehaviorModeBoundaries];

In the preceding example, the items added to collision1 will never collide with items in the collision2 and collision3 behaviors, only each other. It is also important to note that a single dynamic item can be associated with multiple collision behaviors, provided those behaviors are managed by the same dynamic animator (more on dynamic animators later in this post).

There are multiple delegate callbacks that indicate when an item begins or ends contact with another item or boundary – you simply conform to UICollisionBehaviorDelegate and set the collisionDelegate property of your behavior.

In the following example, we respond to the end of a collision. In this case, we do a quick check to make sure the item is what we expect it to be, and then write a simple message to the console. Recall that the reference bounds for our sample collision behaviors used setTranslatesReferenceBoundsIntoBoundary:YES. Because of this, the identifier parameter will be nil. In order to have an identifier you must explicitly create a line segment or Bezier path boundary. The initializers for both require that you provide an identifier parameter.

- (void)collisionBehavior:(UICollisionBehavior *)behavior endedContactForItem:(id<UIDynamicItem>)item 
	withBoundaryIdentifier:(id<NSCopying>)identifier {
 
	// Print a description of the item that collided, if it's one of our UIViews
	if ([item isKindOfClass:[UIView class]]) {
		NSLog(@"end collision for item: %@", item.description);
	}
}

Keep in mind that for any collision boundaries, you must ensure that the starting position of your dynamic items does not intersect with that boundary. If you don't check this, the resulting animation will be unpredictable.

Most of the behaviors permit us to add and remove items at any time. The collision behavior is one of these. All behaviors that allow this action will expose the same methods.

// Remove an item
[collision1 removeItem:v2];
 
// Add it back
[collision1 addItem:v2];

At the beginning of this post, I shared an example that employs a really effective collision implementation: the new iOS 7 Lock screen. Another great example is when you use a swipe gesture to pull down the Notification Center. In each of these examples, you release the gesture and there is a subsequent collision with the bottom of the screen. In the Lock screen, the collision is softer. The Notification Center provides a variable interaction – the quicker or more forcefully you gesture downwards before removing your tap, the more impact you will see with the bottom of the screen. This also results in more bounce.

Collisions are a nice addition when you need to present another view based on user gestures. Not only does it give a sense of control to the user, it gives the incoming view a sense of momentum. I highly recommend using collisions in this way to spice up your transitions and to add some weight to your views.

The Gravity Behavior

No set of physics behaviors is complete without a little gravity. In UIKit Dynamics, the UIGravityBehavior class allows you to implement Sir Isaac Newton’s favorite attractive force.

A directional vector and an acceleration value are used to model gravity within UIKit Dynamics. The default directional vector, set in the gravityDirection property, is towards the bottom of the device’s screen with no left or right movement, and is defined by an x value = 0.0f and and a y value = 1.0f. A y value of 1.0f signals that the acceleration is downwards at 1000 points per second^2, known as standard UIKit Gravity. Setting x or y to a negative value changes the direction. Also, setting either value to 0.5f results in an acceleration vector of 500 points per second^2 instead of the default 1000.

// Create a gravity behavior
UIGravityBehavior *gravity1 = [[UIGravityBehavior alloc] initWithItems:@[v1]];
 
// Set gravity to move downwards at 500 points per second^2
[gravity1 setGravityDirection:CGVectorMake(0.0, 0.5)];
 
// Set gravity to move left at 500 and upwards at 900 points per second^2
[gravity1 setGravityDirection:CGVectorMake(-0.5, -0.9)];

You can also configure the directional and acceleration vectors using the angle and magnitude properties. Each of these properties must be set together to properly configure a gravity vector. That is, if one is defined, the other must also be defined. The angle property is in radians. The magnitude property defaults to 1.0f.

// Set a magnitude and angle instead
[gravity1 setAngle:0.0 magnitude:0.5];

The gravity behavior is fairly simplistic. Once you apply gravity to an item, it will head off in the direction of the configured vector, continuing along its path until some other behavior causes it to stop - or until you programmatically remove the item from the behavior or remove the behavior itself. For this reason, a gravity behavior will usually be coupled with some other behavior to influence the animation. For example, coupling gravity with collision can ensure that your item stops moving when it contacts a collision boundary. Otherwise, your item will fall off the screen. In the new iOS 7 Lock screen, ending the swipe-up gesture short of a certain point triggers the addition of gravity and collision behaviors, which causes the Lock screen to fall back down until it collides with the bottom of the screen. Just like the collision behavior, gravity is useful when used with view transitions, especially when there is some influence imparted by user gestures. If you are controlling a transition with a gesture, and that gesture ends prematurely, employing gravity and collision is a great way to visually return the transition to its starting point.

The Push Behavior

The UIPushBehavior class provides for either a "continuous or instantaneous" force that is applied to one or multiple dynamic items. You must initialize the push behavior mode (UIPushBehaviorMode), which can be one of the following:

  • UIPushBehaviorContinuousMode
  • UIPushBehaviorInstantaneousMode

With continuous mode, the object will gradually pick up speed after it is pushed. Instantaneous mode means the object starts at peak speed immediately after the push. Choosing between the two depends on what you're trying to achieve in the animation. If your dynamic items needs to exhibit mass and density, you should use the continuous mode. If not, instantaneous mode is the way to go.

Just like the gravity behavior, there is an angle and magnitude associated with push behaviors. When this behavior is first instantiated the magnitude is nil, meaning there is no force at all. Your dynamic item won’t move anywhere until you supply a magnitude. A push behavior’s magnitude is represented by the UIKit Newton, described in the Apple developer documentation as "A continuous force vector with a magnitude of 1.0, applied to a 100 point x 100 point view whose density value is 1.0, results in view acceleration of 100 points / second² in the direction of the vector." The angle property is defined in radians.

// Create a push behavior with two UIViews and a continuous 'push' mode
UIPushBehavior *push = [[UIPushBehavior alloc] initWithItems:@[v1, v2] mode:UIPushBehaviorModeContinuous];
 
// Set an angle and magnitude
[push setAngle:0.2 magnitude:0.5];

Configuring a pushDirection is also an option:

@property(readwrite, nonatomic) CGVector pushDirection

By default, the push force is applied to the center of your dynamic item. You can specify an offset if you wish to direct the push force elsewhere on your item. Specifying an offset will effectively add angular momentum to your dynamic item, e.g. it will start spinning. This effect is easy to visualize; pushing on the corner of a box will direct a force that influences the box to rotate.

- (void)setTargetOffsetFromCenter:(UIOffset)o forItem:(id&lt;uidynamicitem&gt;)item

Like gravity behaviors, you can push your item off the screen unless you do something to intervene. When I described the involvement of gravity in the new Lock screen slide-up gesture, the view returned and collided with the bottom of the screen if the gesture ended short of a specified point. A push behavior is added when the gesture ends past that specified point. Instead of returning and colliding with the bottom of the screen, the Lock screen will continue until it has been pushed upwards off the screen. Again, we have a great way to add some spice to gesture-driven transitions. Push behaviors can also be coupled with the next behavior to create some very interesting animations.

The Snap Behavior

Creating a UISnapBehavior object requires the least amount of setup of all the behaviors but the animation it provides is perhaps the most eye-catching. This behavior allows you to snap a dynamic item to a point on the screen, almost like there is a magnetic attraction. But that’s not all you get – the snap behavior also allows you to specify a damping value that causes your dynamic item to oscillate and bounce around the intended ‘snap-to’ point until it settles into place.

// Create a snap behavior
// The default damping value is 0.5 which means there is medium oscillation
UISnapBehavior *snap = [[UISnapBehavior alloc] initWithItem:v1 snapToPoint:CGPointMake(100.0f, 100.0f)];

The damping property can have a value of 0.0f (no oscillation) to 1.0f (maximum oscillation). 0.5f is the default value.

// Damping = 0.0; no oscillation - the animation will appear as if the item is simply moving to another location
[snap setDamping:0.0];
 
// Damping = 1.0; maximum oscillation - the item will exhibit heightened oscillation around the 
// snapToPoint before settling to rest
[snap setDamping:1.0];

This behavior can be used to implement some creative animations. During one of the WWDC presentations for UIKit Dynamics, the presenter showed an example of the snap behavior that involved sorting photos. There was a stack of items at the bottom of the screen that represented the photos. A couple of other screen locations contained stacks used to organize the images into categories. The images could be tapped and dragged via gesture, and once the tap gesture ended, the image would snap and settle into one of the category stacks, depending on where the gesture ended. In your application, perhaps you want to allow users to configure the layout of their content. A snap behavior would provide a nice interaction between content areas and the positioning of your content – simply dragging a ‘content pane’ to another area would cause the panes to switch locations with a snap and slight oscillation. This type of animation is akin to common drag-and-drop functionality.

The Dynamic Item Behavior

Apple has included one more dynamic behavior that allows us to adjust the individual physical properties of a dynamic item. If we want to further define how an object should respond to a collision based on characteristics like mass and elasticity, the dynamic item behavior allows us to do so. We can also configure things like friction and resistance to slow down certain actions.

// Initialize a dynamic item behavior
// v1 and v2 are instances of UIView
UIDynamicItemBehavior *dynamicItemBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[v1, v2]];

Then we can configure the characteristics of the items in the behavior.

// The available properties we can set for a dynamic item behavior
[dynamicItemBehavior setAllowsRotation:YES];
[dynamicItemBehavior setDensity:1.0];
[dynamicItemBehavior setElasticity:1.0];
[dynamicItemBehavior setFriction:0.2];
[dynamicItemBehavior setResistance:0.0];
[dynamicItemBehavior setAngularResistance:0.0];

The density property directly relates to the effective mass of an object. Suppose we have two UIViews that are 100 x 100 points in size. If object A has a density of 1.0f (the default) and object B a density of 2.0f, object B will have two times the mass. Object B will act like the heavier object in a collision.

The elasticity property determines how elastic or bouncy the collision will be. This is usually a value between 0.0f and 1.0f but it can be higher. In practice, collisions that are bounded by a reference view can get ‘out of hand’ if the value is greater than 1.0f because objects can actually pick up speed on the rebound, eventually becoming a blur.

The resistance and angularResistance properties are important because they allow you to slowly, or rapidly, remove velocity from an item. Without any resistance, an item with angular velocity will just spin forever, assuming it doesn’t run into something that affects its rotation. Suppose we set the elasticity of an item to something greater than 1.0f – giving that item a little resistance could help to continually slow down its linear velocity, offsetting the motion blur I referred to.

In order to put the dynamic items in motion, we need to add linear or angular velocity, or both. Linear velocity is added to one dynamic item at a time and is translated into points per second.

[dynamicItemBehavior addLinearVelocity:CGPointMake(0.0, 100.0) forItem:v1];
[dynamicItemBehavior addLinearVelocity:CGPointMake(0.0, -100.0) forItem:v2];

In the same way, we can add angular velocities to our items, translated into radians per second.

// A positive value = clockwise spin; negative = counterclockwise
[dynamicItemBehavior addAngularVelocity:10.0 forItem:v1];
[dynamicItemBehavior addAngularVelocity:-30.0 forItem:v2];

It is very easy to impart a velocity to objects in a dynamic item behavior. Because of that, Apple states that one "notable and common use of a dynamic item behavior is to confer a velocity to a dynamic item to match the ending velocity of a user gesture." For example, we can do the following:

// Create a pan gesture recognizer
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self
action:@selector(handlePan:)];
 
// Set gesture delegate
[panGestureRecognizer setDelegate:self];
 
// Add gesture to item v1
[v1 setUserInteractionEnabled:YES];
[v1 addGestureRecognizer:panGestureRecognizer];
 
// Create gesture handler
- (void)handlePan:(UIPanGestureRecognizer*)gesture {
	CGPoint translation = [gesture translationInView:gesture.view];
	gesture.view.center = CGPointMake(gesture.view.center.x + translation.x, gesture.view.center.y
		+ translation.y);
	[gesture setTranslation:CGPointMake(0, 0) inView:gesture.view];
 
	// Get the x and y velocities of the gesture
	CGFloat xVelocity = [gesture velocityInView:gesture.view].x;
	CGFloat yVelocity = [gesture velocityInView:gesture.view].y;
 
	// Impart a linear velocity through your dynamic item behavior
	[dynamicItemBehavior addLinearVelocity:CGPointMake(xVelocity, yVelocity) forItem:v1];
 
	// The dynamic animator is discussed later
	// For now, animator is an instance of UIDynamicAnimator, used to add behaviors
	[animator addBehavior:dynamicItemBehavior];
}

The Dynamic Animator

At the heart of UIKit Dynamics is the UIDynamicAnimator class. A dynamic animator object manages all of your dynamic behaviors by providing a context for all of your animations. It can be described as the layer that sits between the iOS physics engine and your dynamic behaviors. Its other responsibilities include updating the position of a dynamic item during animation and managing all the necessary rotations. This is accomplished by keeping track of the dynamic items’ center, bounds, and transform properties, which is why all dynamic items must implement the UIDynamicItem protocol. The power of the UIDynamicAnimator class is easily overlooked because of its simplicity. All you have to do is add your behaviors and the dynamic animator handles the rest.

Before creating a dynamic animator object, you must first decide what it is you want to animate. Based on that decision, you will use one of two initializers:

  • initWithReferenceView:
  • initWithCollectionViewLayout:
// Initialize the animator
// refView = the reference view that defines the animation context and coordinate system
UIDynamicAnimator *dynamicAnimator = [[UIDynamicAnimator alloc] initWithReferenceView:self.refView];

Providing a reference view or collection view layout sets the overall coordinate system that your dynamic animator will manage. All animations will occur within this specified coordinate system.

If you create your own dynamic item that conforms to the UIDynamicItem protocol, you can subclass UIDynamicBehavior and override the supplied init method. In this case the result will be an abstract coordinate system that does not rely on a specific screen or view.

After initializing your dynamic animator, you can begin adding dynamic behaviors by calling the addBehavior method. Calling the removeBehavior method will remove the dynamic behavior and it will no longer influence the animation. There is also an option to remove all dynamic behaviors at once via the removeAllBehaviors method.

// add behaviors to the animator
[dynamicAnimator addBehavior:collisionBehavior];
[dynamicAnimator addBehavior:gravityBehavior];
 
// remove a behavior from the animator
[dynamicAnimator removeBehavior:collisionBehavior];

The following steps provide a general outline for making UIKit Dynamics work for you:

  1. Determine the types of items you wish to animate then initialize a dynamic animator with the correct initializer. Decide between a UIView, a UICollectionViewLayoutAttributes, or a custom object that conforms to UIDynamicItem.
  2. Identify the objects that you want to animate, making sure they implement the UIDynamicItem protocol. In most cases you won’t worry about this since you will be animating UIViews or UICollectionViews.
  3. Initialize a dynamic behavior and specify its dynamic items. Configure the behavior as necessary to define points, boundaries, modes, etc.
  4. Add one or more of your behaviors to a dynamic animator.

In closing, there are a few things you should keep in mind.  First, while it is possible to create simple games with UIKit Dynamics, it is recommended that you instead use the SpriteKit framework.  UIKit Dynamics is best used to add visual effects to your application, not as a method to manage a large number of simultaneous animations.  Second, the most effective animations in non-gaming applications rely on subtlety and seamless integration within your user interface.  Try to employ animations that begin and end based on a user action, such as a gesture.  Because UIKit Dynamics makes it so easy to animate objects, you might be tempted to do so when it isn't required.  Don't force the inclusion of a dynamic animation if it does not add to your user experience.  Finally, if your application does require simultaneous animations, CPU utilization may become an issue.  According to Apple, collisions are the most CPU-intensive dynamic animation, which makes sense if you consider all the calculations required to determine how colliding objects should react to each other.  The sample application includes functionality that will allow you to create a large number of views, add some angular velocity (rotation), and then make them collide with each other.  By adjusting the number of views, it is easy to guage how dynamic animations can affect CPU utilization.

I hope you find this post helpful.  Happy animating!