iOS Programming Recipe 29: Implementing UIKit Dynamics

Hooray! Apple NDA is over on iOS 7. Which means, this is the very first iOS 7 post at NSCookbook. For the first recipe on iOS 7 we’re gonna talk about UIKit Dynamics a bit. For those of you that don’t know what it is. One word, Awesome! Dynamics is a new framework that lets you give physics inspired animation to views.

Assumptions

  • You have a firm understanding on navigating XCode and setting up view controllers
  • You are running iOS 7 with XCode 5
  • You have some understaing of what acceleration, velocity, and friction are. Basically some physics.

Setting Up Your Interface

For this tutorial, We’ll basically be doing a bunch of examples in the viewDidLoad method of your single view controller. If you’re just learning and having fun, it may be easiest for you to delete each section code from the view controller before moving on.

At any rate, You will need a single view application. Go ahead and title it “Dynamics Playground”.

Next you will need to add a .png image to your project. You can do this by dragging it into the project navigator from a finder window. For this tutorial, I have chosen a 64px X 64px Icon looking thing. Since I have no problem with shameless plugging, naturally it says NSCookbook.

In the storyboard, Go ahead and create a new UIImage view on the scene that is 64 Points X 64 Points and set the image to whatever image you choose from the attributes inspector. When you are done, your view controller should look like figure 1.

Figure_1

Figure 1 – Your stupid simple interface

Gravity

Ok , lets get start with gravity. gravity is super easy to do. Lets just jump into it shall we?

First, create an outlet in your ViewController.m file(implemtation file). For those that may have forgotten, control click and drag from the UIImage view to the implemation file as shown in figure 2. I gave the outlet the name “icon”, but you do what you want.

Figure_2
Figure 2 Connecting the outlet

next, you will need to add a property as well to the interface. This property is the animator property. Any time you add a behavior to an animator, that behavior starts. So here is your completed interface:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//
//  ViewController.m
//  Dynamics Playground
//


#import "ViewController.h"

@interface ViewController ()


@property (weak, nonatomic) IBOutlet UIImageView *icon;
@property (nonatomic) UIDynamicAnimator *animator;

@end

Now here comes the fun part. Modify the viewDidLoad method as follows.


1
2
3
4
5
6
[super viewDidLoad];

self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[self.icon]];

[self.animator addBehavior:gravityBehavior];

So whats going on here? First we initialize the animator. Then we create a behavior for the icon. Next we simply add that behavior to the animator….cake.

Now if you load it, your image will fall/accelerate downward off the screen….Like Gravity.

Gravity and Collision

Now lets make things a little more intersting. By making a simple modification to your code, you can make the icon stop and bounce when it hits the bottom of the screen.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)viewDidLoad
{
    [super viewDidLoad];


    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

    UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[self.icon]];
    UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.icon]];

    collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;

    [self.animator addBehavior:gravityBehavior];
    [self.animator addBehavior:collisionBehavior];



}

Here we simply add a new collision behavior and add it to the behavior. Since we want something for the icon to collide with, we create a collision boundary on the view bounds.

Now if you run it, it will fall, and then do a little bounce and settle as shown in Figure 3.

Figure_3

Figure 3 The Icon Once it has settled

Item Properties

One problem though, That was a pretty darn weak bounce. It went like a centimeter at best. No worries, we can fix this using an item property.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)viewDidLoad
{
    [super viewDidLoad];


    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

    UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[self.icon]];
    UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.icon]];

    UIDynamicItemBehavior* propertiesBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.icon]];
    propertiesBehavior.elasticity = 0.75f;


    collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;


    [self.animator addBehavior:propertiesBehavior];
    [self.animator addBehavior:gravityBehavior];
    [self.animator addBehavior:collisionBehavior];



}

Here we added a new UIDynamicItemBehavior and then set the elasticity property. Now if you run the application the square will bounce much higher.

One thing to note though. You have a bunch of properties to choose from. Here they are!

elasticity – A float value sets the collision elasticity. 0 for nonelastic, 1 for very elastic.

friction – A float value sets friction of an object between others, 0 for no friction.

Density – A float value for density, 1 is the default.

resistance – A float value sets velocity damping. 0 is no velocity damping.

angularResistance – A float value for angular velocity damping, 0 is no angular velocity
damping.

allowsRotation – A Boolean value sets wether or not an object will have locked rotation.

At this point you may be beginning to see the power this new framework offers. Lets carry on with this discussion and add a push vector.

Push Behaviors

With push behaviors you can create an acceleration or a velocity essentially, or as Apple would call it Continuous push and instantaneous push.

To create a push behavior, you will need a magnitude and an angle:

Magnitude – A float value for the force vector. A magnitude of 1.0 would move a 100 point x 100 point view with a density of 1.0 at an acceleration of 100 points/second^2.
Note In case you’re wondering, Dynamic views have a default density of 1.0, of course if you were paying attention I said that above.

Angle – This is a value in radians. negative values are counter-clockwise and positive values are clockwise.

Had enough theory? thought so. Here’s some code:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)viewDidLoad
{
    [super viewDidLoad];


    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

    UIPushBehavior *instantPushBehavior = [[UIPushBehavior alloc] initWithItems:@[self.icon] mode:UIPushBehaviorModeInstantaneous];


    instantPushBehavior.angle = 1.57;    
    instantPushBehavior.magnitude = 0.5;


    [self.animator addBehavior:instantPushBehavior];



}

As usual, we create the behavior and then add it to the animator. The difference here is we are setting a mode. You have two options for the mode, either “UIPushBehaviorModeInstaneous” or “UIPushBehaviorModeContinuous”. Then you set the angle to 90 degrees clockwise(down) and a magnitude of .5 seemed fast enough.

I’ll leave it to you to change the mode from UIPushBehaviorModeInstantaneous to UIPushBehaviorModeContinuous.

This is sweet, but I saved the neatest feature for last. The spring attachment.

Spring Attachment

Ok, so this one gets a little bit complicated. I also want to show you how you would create a custom behavior. First though, add another identicle image view to your view controller and give it an outlet named icon2. PROTIP – To copy items on the storyboard easily, hold alt and then click and drag from the item you want to copy. Your new View Controller should looke like figure 4.

Figure_4
Figure 4 The new View Controller.

Next you will create a new class by clicking the “+” in the bottom left hand side of the project navigator. You will want to give it a class that is descriptive of what you are doing, so call it “SpringAttachmentBehavior” and make it a subclass of “UIDynamicBehavior” as shown in figure 5

Figure_5
Figure 5 Creating a new UIDynamicBehavior subclass

Ok, so first open up the SpringAttachmentBehavior.h file and modify the interface as follows:


1
2
3
4
5
6
7
8
9
10
11
12
13
//
//  SpringAttachmentBehavior.h
//  Dynamics Playground
//


#import <UIKit/UIKit.h>

@interface SpringAttachmentBehavior : UIDynamicBehavior

-(instancetype)initWithItems:(NSArray *)items withAnchorPoint:(NSString *)anchorPointString;

@end

For this we want to create a custom initializer, so we’re declaring it in the header file. The initializer will take an NSString which we will convert to a CGPoint and it also takes an NSArray, which will consist of the two objects we put on the view controller.

Now, I’m just gonna plop a bunch of code here. This will go in the SpringAttachmentBehavior.m file. This is the completed intitializer.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
-(instancetype)initWithItems:(NSArray *)items withAnchorPoint:(NSString *)anchorPointString
{
    if(self=[super init])
    {


    CGPoint anchorPoint = CGPointFromString(anchorPointString);

    UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:items];
    UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:items];

    UIAttachmentBehavior *item1AttachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:[items objectAtIndex:0] attachedToAnchor:anchorPoint];

    UIAttachmentBehavior *item2AttachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:[items objectAtIndex:1] offsetFromCenter:UIOffsetMake(-20.0, 0) attachedToItem:[items objectAtIndex:0] offsetFromCenter:UIOffsetZero];

    collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;

    [item1AttachmentBehavior setFrequency:1.0];
    [item2AttachmentBehavior setDamping:0.65];

    [item1AttachmentBehavior setFrequency:1.0];
    [item2AttachmentBehavior setDamping:0.65];

    [self addChildBehavior:gravityBehavior];
    [self addChildBehavior:collisionBehavior];
    [self addChildBehavior:item1AttachmentBehavior];
    [self addChildBehavior:item2AttachmentBehavior];


    }

    return self;
}

So what we are doing here is first attaching icon 1 to an anchor point that gets passed in to the intializer. Then we are attaching icon 2 to icon 1. To make things interesting we make the attachment to icon 2 with a 20 point offset to the left it’s center. This will make for a nice dramatic spinning effect.

Then off course we set the frequency and damping of the attachment. This will give the springyness to the attachments.

Now you will need to switch back over to the ViewController.m file and first import the new class we created.


1
2
3
4
5
6
7
8
9
10
//
//  ViewController.m
//  Dynamics Playground
//
//...

#import "ViewController.h"
#import "SpringAttachmentBehavior.h"

@interface ViewController ()

Next you will need to fill out the viewDidLoad method to take advantage of the new method.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)viewDidLoad
{

    [super viewDidLoad];

    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

    CGPoint anchorPoint = CGPointMake(self.view.frame.size.width/2, 20);
    NSString *anchorPointString = NSStringFromCGPoint(anchorPoint);

    SpringAttachmentBehavior *springAttachmentBehavior = [[SpringAttachmentBehavior alloc] initWithItems:@[self.icon,self.icon2] withAnchorPoint:anchorPointString];

    [self.animator addBehavior:springAttachmentBehavior];

}

Now if you build and run the app, both icons will fall and then they will snap back and dangle and spin. I admit, it’s very entertaining to watch. Your App should now look like figure 6.

Figure_6

Figure 6 The bouncing colliding icons in action

Now you may be wondering in this initializer why we can’t just pass the CGPoint directly. EG.


1
-(instancetype)initWithItems:(NSArray *)items withAnchorPoint:(CGPoint)anchorPoint

This is because it’s a C struct and not an objective-C object, so we had to convert it to an NSString and then back to CGPoint.

Well that just about concludes this recipe. Hope you enjoyed it. One feature I left out was the snap behavior. I’ll leave it too you as a homework problem to figure that one out. Of course there are a bunch of things you can do with delegates and boundries as well, but I figure I left you with enough to chew on. There is a good bit of sample code on the Apple developer website that you should look at too at https://developer.apple.com/downloads/index.action

About Joe Hoffman

I am An Electrical Engineer that spends a lot of my off time doing web development and other programming. Currently I am trying to expand my knowledge with iOS and I find that writing tutorials helps me learn more thoroughly as well as provide some useful info to others.

Comments

  1. I want to make a correction to your article:

    “Now you may be wondering in this initializer why we can’t just pass the CGPoint directly. EG.

    -(instancetype)initWithItems:(NSArray *)items withAnchorPoint:(CGPoint)anchorPoint

    This is because it’s a C struct and not an objective-C object, so we had to convert it to an NSString and then back to CGPoint.”

    This is a blatantly wrong! You can absolutely pass a CGPoint to the method call as you have pointed out that you cant. Converting it to a string and back is simply not needed and should be discouraged. Using a pure CGPoint is more efficient in terms of both memory and CPU cycles as you do not need to do the round trip conversion.

    • Joe Hoffman says:

      Hi Nicholas,

      Passing a CGPoint directly gives me a compiler error. It seems most developers get around this by changing it to an object, which I used the NSString option, but the NSValue looks to be commonly used as well. Of course there are other methods, but as this is an object-oriented language, I prefer to use objects. I’m always interested in learning best practices, so if there’s a better way, please enlighten me.

      • Joe Alexander says:

        Yeah, that statement seemed so totally bizarre to me too.

        So long as your interface and implementation both use CGPoint you would not get a compiler error.

        Converting things to objects and back again is going to be really slow, error prone, and memory intensive.

        You didn’t say what compiler error you got, so it’s hard to know what you could have been doing wrong.

  2. David DelMonte says:

    Joe, I must be missing something.. I am getting a compile error at the end of the tutorial. ” No visible interface for SpringAttachmentBehavior declares addChildBehavior” What have I missed?

Speak Your Mind

*

css.php
Privacy Policy