**This article was adapted from UIKit Dynamics Tutorial, Colin Eberhardt, by permission of the publisher, Ray Wenderlich.
Please visit the Ray Wenderlich Tutorials site for this and many other fine tutorials.** The vast majority of the text of this article is from the original article. Rather than setting the original material in block quotes or double quotes, we’ve chosen instead to highlight the Xamarin.iOS-specific material by setting it in bold. Note from Ray: This is an abbreviated version of a chapter from iOS 7 by Tutorials that we (raywenderlich.com) are releasing as part of the iOS 7 Feast. We hope you enjoy!
You have probably come to realize that iOS 7 is something of a paradox; while you’re being encouraged to do away with real-world metaphors and skeuomorphism, Apple encourages you at the same time to create user interfaces that feel real.
What does this mean in practice? The design goals of iOS 7 encourage you to create digital interfaces that react to touch, gestures, and changes in orientation as if they were physical objects far beyond a simple collection of pixels. The end result gives the user a deeper connection with the interface than is possible through skin-deep skeuomorphism.
This sounds like a daunting task, as it is much easier to make a digital interface look real, than it is to make it feel real. However, you have some nifty new tools on your side: UIKit Dynamics and Motion Effects.
- UIKit Dynamics is a full physics engine integrated into UIKit. It allows you to create interfaces that feel real by adding behaviors such as gravity, attachments (springs) and forces. You define the physical traits that you would like your interface elements to adopt, and the dynamics engine takes care of the rest.
- Motion Effects allows you to create cool parallax effects like you see when you tilt the iOS 7 home screen. Basically you can harness the data supplied by the phone’s accelerometer in order to create interfaces that react to changes in phone orientation.
When used together, motion and dynamics form a powerhouse of user experience tools that make your digital interfaces come to life. Your users will connect with your app at a deeper level by seeing it respond to their actions in a natural, dynamic way.
Getting started
UIKit dynamics can be a lot of fun; the best way to start learning about them is to jump in feet-first with some small examples.
Open Xcode Xamarin Studio, select File / New / Project … File / New / Solution … then select iOS\Application\Single View Application C#\iOS\iPhone\Single View Application and name your project DynamicsPlayground. Change the Location if desired. Once the project solution has been created, open ViewController.m DynamicsPlaygroundViewController.cs and add the following code to the end of viewDidLoad
:
[sourcecode language=”csharp”] UIView square = new UIView (new RectangleF (100, 100, 100, 100)); square.BackgroundColor = UIColor.Gray; this.View.AddSubview (square); [/sourcecode]
The above code simply adds a square UIView
to the interface.
Build and run your app, and you’ll see a lonely square sitting on your screen, as shown below:
If you’re running your app on a physical device, try tilting your phone, turning it upside-down, or even shaking it. What happens? Nothing? That’s right—everything is working as designed. When you add a view to your interface you expect it to remain firmly stuck in place as defined by its frame—until you add some dynamic realism to your interface!
Adding gravity
Still working in ViewController.m DynamicsPlaygroundViewController.cs, add the following instance variables:
[sourcecode language=”csharp”] UIDynamicAnimator animator; UIGravityBehavior gravity; [/sourcecode]
Add the following to the end of viewDidLoad
:
[sourcecode language=”csharp”] animator = new UIDynamicAnimator (this.View); gravity = new UIGravityBehavior (new [] { square }); animator.AddBehavior(gravity); [/sourcecode]
I’ll explain this in a moment. For now, build and run your application. You should see your square slowly start to accelerate in a downward motion until it drops off the bottom of the screen, as so:
In the code you just added, there are a couple of dynamics classes at play here:
UIDynamicAnimator
is the UIKit physics engine. This class keeps track of the various behaviors that you add to the engine, such as gravity, and provides the overall context. When you create an instance of an animator, you pass in a reference view that the animator uses to define its coordinate system.UIGravityBehavior
models the behavior of gravity and exerts forces on one or more items, allowing you to model physical interactions. When you create an instance of a behavior, you associate it with a set of items—typically views. This way you can select which items are influenced by the behavior, in this case which items the gravitational forces affect.
Most behaviors have a number of configuration properties; for example, the gravity behavior allows you to change its angle and magnitude. Try modifying these properties to make your objects fall up, sideways, or diagonally with varying rates of acceleration.
NOTE: A quick word on units: in the physical world, gravity (g) is expressed in meters per second squared and is approximately equal to 9.8 m/s2. Using Newton’s second law, you can compute how far an object will fall under gravity’s influence with the following formula:
distance = 0.5 × g × time2
In UIKit Dynamics, the formula is the same but the units are different. Rather than meters, you work with units of thousands of pixels per second squared. Using Newton’s second law you can still work out exactly where your view will be at any time based on the gravity components you supply.
Do you really need to know all this? Not really; all you really need to know is that a bigger value for g means things will fall faster, but it never hurts to understand the math underneath.
Setting boundaries
Although you can’t see it, the square continues to fall even after it disappears off the bottom of your screen. In order to keep it within the bounds of the screen you need to define a boundary.
Add another instance variable in ViewController.m DynamicsPlaygroundViewController.cs:
[sourcecode language=”csharp”] UICollisionBehavior collision; [/sourcecode]
Add these lines to the bottom of viewDidLoad
:
[sourcecode language=”csharp”] collision = new UICollisionBehavior (new [] { square }); collision.TranslatesReferenceBoundsIntoBoundary = true; animator.AddBehavior (collision); [/sourcecode]
The above code creates a collision behavior, which defines one or more boundaries with which the associated items interact.
Rather than explicitly adding boundary co-ordinates, the above code sets the TranslatesReferenceBoundsIntoBoundary
property to YES true. This causes the boundary to use the bounds of the reference view supplied to the UIDynamicAnimator
.
Build and run; you’ll see the square collide with the bottom of the screen, bounce a little, then come to rest, as so:
That’s some pretty impressive behavior, especially when you consider just how little code you’ve added at this point.
Handling collisions
Next up you’ll add an immovable barrier that the falling square will collide and interact with.
Insert the following code to viewDidLoad
just after the lines that add the square to the view:
[sourcecode language=”csharp”] UIView barrier = new UIView (new RectangleF (0, 300, 130, 20)); barrier.BackgroundColor = UIColor.Red; this.View.AddSubview(barrier); [/sourcecode]
Build and run your app; you’ll see a red “barrier” extending halfway across the screen. However, it turns out the barrier isn’t that effective as the square falls straight through the barrier:
That’s not quite the effect you were looking for, but it does provide an important reminder: dynamics only affect views that have been associated with behaviors.
Time for a quick diagram:
UIDynamicAnimator
is associated with a reference view that provides the coordinate system. You then add one or more behaviors that exert forces on the items they are associated with. Most behaviors can be associated with multiple items, and each item can be associated with multiple behaviors. The above diagram shows the current behaviors and their associations within your app.
Neither of the behaviors in your current code is “aware” of the barrier, so as far as the underling dynamics engine is concerned, the barrier doesn’t even exist.
Making objects respond to collisions
To make the square collide with the barrier, find the line that initializes the collision behavior and replace it with the following:
[sourcecode language=”csharp”] collision = new UICollisionBehavior (new [] { square, barrier }); [/sourcecode]
The collision object needs to know about every view it should interact with; therefore adding the barrier to the list of items allows the collision object to act upon the barrier as well.
Build and run your app; the two objects collide and interact, as shown in the following screenshot:
The collision behavior forms a “boundary” around each item that it’s associated with; this changes them from objects that can pass through each other into something more solid.
Updating the earlier diagram, you can see that the collision behavior is now associated with both views:
However, there’s still something not quite right with the interaction between the two objects. The barrier is supposed to be immovable, but when the two objects collide in your current configuration the barrier is knocked out of place and starts spinning towards the bottom of the screen.
Even more oddly, the barrier bounces off the bottom of the screen and doesn’t quite settle down like the square—this makes sense because the gravity behavior doesn’t interact with the barrier. This also explains why the barrier doesn’t move until the square collides with it.
Looks like you need a different approach to the problem. Since the barrier view is immovable, there isn’t any need to for the dynamics engine to be aware of its existence. But how will the collision be detected?
Invisible boundaries and collisions
Change the collision behavior initialization back to its original form so that it’s only aware of the square:
[sourcecode language=”csharp”] collision = new UICollisionBehavior (new [] { square }); [/sourcecode]
Next, add a boundary as follows:
[sourcecode language=”csharp”] // Add a boundary that coincides with the top edge PointF rightEdge = new PointF( barrier.Frame.Location.X + barrier.Frame.Size.Width, barrier.Frame.Location.Y); collision.AddBoundary(new NSString(“barrier”), barrier.Frame.Location, rightEdge); [/sourcecode]
The above code adds an invisible boundary that coincides with the top edge of the barrier view. The red barrier remains visible to the user but not to the dynamics engine, while the boundary is visible to the dynamics engine but not the user. As the square falls, it appears to interact with the barrier, but it actually hits the immovable boundary line instead.
Build and run your app to see this in action, as below:
The square now bounces off the boundary, spins a little, and then continues its journey towards the bottom of the screen where it comes to rest.
By now the power of UIKit Dynamics is becoming rather clear: you can accomplish quite a lot with only a few lines of code. There’s a lot going on under the hood; the next section shows you some of the details of how the dynamic engine interacts with the objects in your app.
Behind the scenes of collisions
Each dynamic behavior has an action property where you supply a block to be executed with every step of the animation. Add the following code to viewDidLoad
:
[sourcecode language=”csharp”] collision.Action = new NSAction (delegate { Console.WriteLine(“{0}, {1}”, square.Transform, square.Center); }); [/sourcecode]
The above code logs the center and transform properties for the falling square. Build and run your app, and you’ll see these log messages in the Xcode console window.
For the first ~400 milliseconds you should see log messages like the following:
[sourcecode language=”text”] 2014-05-19 16:48:42.190 DynamicsPlayground[48788:70b] xx:1.0 yx:0.0 xy:0.0 yy:1.0 x0:0.0 y0:0.0, {X=150, Y=236} 2014-05-19 16:48:42.207 DynamicsPlayground[48788:70b] xx:1.0 yx:0.0 xy:0.0 yy:1.0 x0:0.0 y0:0.0, {X=150, Y=243} 2014-05-19 16:48:42.224 DynamicsPlayground[48788:70b] xx:1.0 yx:0.0 xy:0.0 yy:1.0 x0:0.0 y0:0.0, {X=150, Y=250} [/sourcecode]
Here you can see that the dynamics engine is changing the center of the square—that is, its frame—in each animation step.
As soon as the square hits the barrier, it starts to spin, which results in log messages like the following:
[sourcecode language=”text”] 2014-05-25 16:15:37.904 DynamicsPlayground[5616:70b] xx:0.09 yx:1.0 xy:-1.0 yy:0.09 x0:0.0 y0:0.0, {X=197, Y=326} 2014-05-25 16:15:37.921 DynamicsPlayground[5616:70b] xx:0.03 yx:1.0 xy:-1.0 yy:0.03 x0:0.0 y0:0.0, {X=199, Y=332} 2014-05-25 16:15:37.938 DynamicsPlayground[5616:70b] xx:-0.03 yx:1.0 xy:-1.0 yy:-0.03 x0:0.0 y0:0.0, {X=201, Y=338} [/sourcecode]
Here you can see that the dynamics engine is using a combination of a transform and a frame offset to position the view according to the underlying physics model.
While the exact values that dynamics applies to these properties are probably of little interest, it’s important to know that they are being applied. As a result, if you programmatically change the frame or transform properties of your object, you can expect that these values will be overwritten. This means that you can’t use a transform to scale your object while it is under the control of dynamics.
The method signatures for the dynamic behaviors use the term items rather than views. The only requirement to apply dynamic behavior to an object is that it adopts the implements the UIDynamicItem
protocolIUIDynamicItem
interface.
Your class can do this by implementing the IUIDynamicItem
directly or by inheriting from the UIDynamicItem
abstract class.
Since the UIView
class inherits from UIResponder
, it implement the IUIDynamicItem
interface.
The UIDynamicItem
protocol IUIDynamicItem
interface gives dynamics read and write access to the center and transform properties, allowing it to move the items based on its internal computations. It also has read access to bounds, which it uses to determine the size of the item. This allows it to create collision boundaries around the perimeter of the item as well as compute the item’s mass when forces are applied.
This means that dynamics is not tightly coupled to UIView
; indeed there is another UIKit class that adopts this protocol implements this interface—UICollectionViewLayoutAttributes
. This allows dynamics to animate items within collection views.
Collision notifications
So far you have added a few views and behaviors then let dynamics take over. In this next step you will look at how to receive notifications when items collide.
Open ViewController.m DynamicsPlaygroundViewController.cs adopt the .UICollisionBehaviorDelegate
protocol
Still in viewDidLoad
, set the view controller as the delegate just after the collision behavior has been instantiated, as follows:
Next, add an implementation for one of the collision behavior delegate methods:
[sourcecode language=”csharp”] collision.BeganBoundaryContact += (object sender, UICollisionBeganBoundaryContactEventArgs e) => { Console.WriteLine(“Boundary contact occurred - {0}”, e.BoundaryIdentifier); }; [/sourcecode]
This delegate method is fired off when a collision occurs and prints out a log message to the console. In order to avoid cluttering up your console log with lots of messages, feel free to remove the _collision.action
collision.Action
logging you added in the previous section.
Build and run; your objects will interact, and you’ll see the following entries in your console:
[sourcecode language=”text”] 2014-05-25 18:32:16.928 DynamicsPlayground[6956:70b] Boundary contact occurred - barrier 2014-05-25 18:32:17.144 DynamicsPlayground[6956:70b] Boundary contact occurred - barrier 2014-05-25 18:32:17.705 DynamicsPlayground[6956:70b] Boundary contact occurred - 2014-05-25 18:32:17.804 DynamicsPlayground[6956:70b] Boundary contact occurred - 2014-05-25 18:32:17.821 DynamicsPlayground[6956:70b] Boundary contact occurred - 2014-05-25 18:32:18.071 DynamicsPlayground[6956:70b] Boundary contact occurred - 2014-05-25 18:32:18.138 DynamicsPlayground[6956:70b] Boundary contact occurred - 2014-05-25 18:32:18.154 DynamicsPlayground[6956:70b] Boundary contact occurred - [/sourcecode]
From the log messages you can see that the square collides twice with the boundary identifier barrier; this is the invisible boundary you added earlier. The (null) lack of an identifier refers to the reference view boundary.
These log messages can be fascinating reading (seriously!), but it would be much more fun to provide a visual indication when the item bounces.
Below the line that sends message to the log, add the following:
[sourcecode language=”csharp”] UIView view = (UIView)e.DynamicItem; view.BackgroundColor = UIColor.Yellow; UIView.Animate(duration:0.3, animation:new NSAction (delegate { view.BackgroundColor = UIColor.Gray; })); [/sourcecode]
The above code changes the background color of the colliding item to yellow, and then fades it back to gray again.
Build and run to see this effect in action:
The square will flash yellow each time it hits a boundary.
So far UIKit Dynamics has automatically set the physical properties of your items (such as mass or elasticity) by calculating them based on your item’s bounds. Next up you’ll see how you can control these physical properties yourself by using the UIDynamicItemBehavior
class.
Configuring item properties
Within viewDidLoad
, add the following to the end of the method:
[sourcecode language=”csharp”] UIDynamicItemBehavior itemBehaviour = new UIDynamicItemBehavior (new [] { square }); itemBehaviour.Elasticity = 0.6f; animator.AddBehavior (itemBehaviour); [/sourcecode]
The above code creates an item behavior, associates it with the square, and then adds the behavior object to the animator. The elasticity property controls the bounciness of the item; a value of 1.0 represents a completely elastic collision; that is, where no energy or velocity is lost in a collision. You’ve set the elasticity of your square to 0.6, which means that the square will lose velocity with each bounce.
Build and run your app, and you’ll notice that the square now behaves in a bouncier manner, as below:
Note: If you are wondering how I produced the above image with trails that show the previous positions of the square, it was actually very easy! I simply added a block to the action property of one of the behaviors, and every fifth time the block code was executed, added a new square to the view using the current center and transform from the square.
In the above code you only changed the item’s elasticity; however, the item’s behavior class has a number of other properties that can be manipulated in code. They are as follows:
- elasticity — determines how ‘elastic’ collisions will be, i.e. how bouncy or ‘rubbery’ the item behaves in collisions.
- friction — determines the amount of resistance to movement when sliding along a surface.
- density — when combined with size, this will give the overall mass of an item. The greater the mass, the harder it is to accelerate or decelerate an object.
- resistance — determines the amount of resistance to any linear movement. This is in contrast to friction, which only applies to sliding movements.
- angularResistance — determines the amount of resistance to any rotational movement.
- allowsRotation — this is an interesting one that doesn’t model any real-world physics property. With this property set to NO the object will not rotate at all, regardless of any rotational forces that occur.
Adding behaviors dynamically
In its current state, your app sets up all of the behaviors of the system, then lets dynamics handle the physics of the system until all items come to rest. In this next step, you’ll see how behaviors can be added and removed dynamically.
Open ViewController.m DynamicsPlaygroundViewController.cs and add the following instance variable:
[sourcecode language=”csharp”] bool firstContact = true; [/sourcecode]
Add the following code to the end of the collision delegate method collisionBehavior:beganContactForItem:withBoundaryIdentifier:atPoint:
[sourcecode language=”csharp”] if (firstContact) { firstContact = false;
UIView square2 = new UIView (new RectangleF (30, 0, 100, 100)); square2.BackgroundColor = UIColor.Gray; this.View.AddSubview(square2);
collision.AddItem(square2); gravity.AddItem(square2);
UIAttachmentBehavior attach = new UIAttachmentBehavior (view, square2); animator.AddBehavior(attach); } [/sourcecode]
The above code detects the initial contact between the barrier and the square, creates a second square and adds it to the collision and gravity behaviors. In addition, you set up an attachment behavior to create the effect of attaching a pair of objects with a virtual spring.
Build and run your app; you should see a new square appear when the original square hits the barrier, as shown below:
While there appears to be a connection between the two squares, you can’t actually see the connection as a line or spring since nothing has been drawn on the screen to represent it.
Where To Go From Here?
At this point you should have a solid understanding of the core concepts of UIKit Dynamics.
If you’re interested in learning more about UIKit Dynamics, check out our book iOS 7 By Tutorials. The book takes what you’ve learned so far and goes a step further, showing you how to apply UIKit Dynamics in an real world scenario:
The user can pull up on a recipe to take a peek at it, and when they release the recipe, it will either drop back into the stack, or dock to the top of the screen. The end result is an application with a real-world physical feel.
I hope you enjoyed this UIKit Dynamics tutorial – we think it’s pretty cool and look forward to seeing the creative ways you use it in your apps. If you have any questions or comments, please join the forum discussion below add a comment below or join the forum discussion of the original article.
The full sourcecode for the Objective-C version of Dynamics Playground you have built in this tutorial is available on github, with a commit for each ‘build and run’ step.