Chapter     13

Animation and Physics Recipes

On the iOS platform, animation plays an integral role in how users interact with devices. Sometimes animation can provide information, such as a spinning icon that lets users know when data is loading. At other times, animation is added for no other reason than to please the user.

Animation gives developers the ability to tell a story in a realistic and appealing way. With the new iOS 7 framework, UIKit Dynamics, you have a plethora of physics-influenced animation to choose from to make the story even more realistic. UIKit Dynamics allows you to add acceleration, spring behavior, and collision effects to view objects. Before this great new framework, developers had to use third-party libraries to achieve the same behavior. Using both UIView Animation and UIKit Dynamics, you can create a user experience that is immersive and captivating without using hundreds of lines of code.

Throughout this chapter, you will first learn the basics of animation where you will create animations of objects moving, rotating, and changing size. Then you will move into dynamic animations where you will learn how to add acceleration, force, collision, attachment, and spring behavior to your view objects.

Recipe 13-1. View Animation Using UIKit

In iOS you can animate using both the Core Animation framework and the UIKit Animation framework. The UIKit Animation framework is built on the Core Animation framework and gives you APIs that allow you to do high-level animations on view objects of all types. In this recipe, we will focus on the UIKit Animation framework and basic tasks such as moving, rotating, and scaling objects.

Start by creating a new single view application. To begin, you’ll be creating a simple animation of a ball that goes from the top of the screen to the center of the screen. You can download the ball image (Ball.png) from the download page for this book at the Apress web site. Add the ball image to your project by dragging it into the resources folder from the Finder.

Create a new outlet in the ViewController.h file. You don’t need to make any interface connections as you will be creating the ball image programmatically. Listing 13-1 shows the addition of this property.

Listing 13-1.  Adding a UIImageView to the ViewController.h File

//
//  ViewController.h
//  Recipe 13-1 View Animation Using UIKit
//

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (strong, nonatomic) UIImageView *blueBall;

@end

Now, move to the viewController.m file and modify the viewDidLoad method. The first bit of code, shown in bold in Listing 13-2, will be necessary for creating the ball image and defining the starting point.

Listing 13-2.  Modifying the viewDidLoad Method, Initializing the Ball Image, and Setting Its Starting Point

- (void)viewDidLoad
{
    [super viewDidLoad];

    //Create ball image and add it to the view
    UIImage *blueBallImage = [[UIImage alloc] init];
    blueBallImage = [UIImage imageNamed:@"Ball"];
    self.blueBall = [[UIImageView alloc] initWithImage:blueBallImage];
    self.blueBall.frame = CGRectMake(self.view.frame.size.width/2-32.0f,
                                     20.0f,
                                     64.0f,
                                     64.0f);
    
    [self.view addSubview:self.blueBall];

}

At this point, you can run the application; you should see a ball at the top of the screen, as shown in Figure 13-1.

9781430259596_Fig13-01.jpg

Figure 13-1. The application with the blue ball image

Here you see that you have created an image and set it in the top middle of the screen with a size of 64 x 64 points. Next, you need to create the animation. There are two ways to do this, but the best way is using the block approach. Back in iOS 4, Apple introduced the block approach, which you will see here. Before the block approach, each step of the animation was broken up into separate lines of code. The block approach is a much more concise way of doing animations because it uses much less code and it’s easier to read. There is no need to learn the nonblock approach unless your application is targeted to iOS 3.2 and earlier.

To start with, you will be adding the animateWithDuration method call, as shown in bold in Listing 13-3. In this code, you set up an animation with a three-second duration. When the animation is completed, it will print “Animation Finished” to the console.

Listing 13-3.  Setting Up the animateWithDuration: Method Call

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    //Create ball image and add it to the view
    UIImage *blueBallImage = [[UIImage alloc] init];
    blueBallImage = [UIImage imageNamed:@"Ball"];
    self.blueBall = [[UIImageView alloc] initWithImage:blueBallImage];
    self.blueBall.frame = CGRectMake(self.view.frame.size.width/2-32.0f,
                                     20.0f,
                                     64.0f,
                                     64.0f);
    
    [self.view addSubview:self.blueBall];
    
[UIView animateWithDuration:3.0f
                     animations:^{
                         //start animations here
                    }
                     completion:^(BOOL finished) {
                         NSLog(@"Animation Finished");
                     }];

}

Next, you will complete the animation code. The code in bold in Listing 13-4 sets the new location of the ball directly in the middle of the screen once the animation has completed.

Listing 13-4.  Modifying the viewDidLoad Method to Add the End Point for the Ball Animation

- (void)viewDidLoad
{
    [super viewDidLoad];
//...
    [UIView animateWithDuration:3.0f
                     animations:^{
                         self.blueBall.frame = CGRectMake(self.view.frame.size.width/2-32.0f,
                                                          self.view.frame.size.height/2 -32.0f,
                                                          64.0f,
                                                          64.0f);
    
                     }
                     completion:^(BOOL finished) {
                         NSLog(@"Animation Finished");
                     }];
//...
}

If you run the application, you will see the blue ball move from the top of the screen to the middle of the screen, as shown in Figure 13-2.

9781430259596_Fig13-02.jpg

Figure 13-2. The app with the ball animation at its final destination

That’s it for simple animation from one point to another! Now let’s add to the complexity a bit by changing the size and alpha values of the ball image when it starts. Changing the alpha value will cause the animation to gradually become less transparent.

Changing Size and Transparency

Changing the size and alpha is easy when using UIKit Animation. In this section, you create an effect that will make the ball fade from nothing while gradually getting larger before it settles in the center of the view. To do this, modify the viewDidLoad method, as shown in Listing 13-5.

Listing 13-5.  Modifying the viewDidLoad Method to Create the Growing, Fading Image Effect

- (void)viewDidLoad
{
    [super viewDidLoad];

    //Create ball image and add it to the view
    UIImage *blueBallImage = [[UIImage alloc] init];
    blueBallImage = [UIImage imageNamed:@"Ball"];
    self.blueBall = [[UIImageView alloc] initWithImage:blueBallImage];
    self.blueBall.frame = CGRectMake(self.view.frame.size.width/2- 5,
                                     20.0f,
                                     10.0f,
                                     10.0f);
    self.blueBall.alpha = 0.0f;

    [self.view addSubview:self.blueBall];

    [UIView animateWithDuration:3.0f
                     animations:^{
                         self.blueBall.frame = CGRectMake(self.view.frame.size.width/2-32.0f,
                                                          self.view.frame.size.height/2 -32.0f,
                                                          64.0f,
                                                          64.0f);
                         self.blueBall.alpha = 1.0f;
    
                     }
                     completion:^(BOOL finished) {
                         NSLog(@"Animation Finished");
                     }];

}

Here you simply changed the size of the ball image by adjusting the frame. You also had to subtract 5 instead of 32 from half of the view so the ball would be centered at the top. Then you set the alpha value to 0 in the initial ball image position and back to 100 percent when the ball image reaches its destination.

If you run the application, you will see the image fade from the top of the screen to the middle of the screen while simultaneously getting larger.

Handling Rotation and Chaining Animation

Now that you have enabled movement, fading, and size, let’s complete this recipe by adding rotation to the animation. You will want this new animation to occur once the first animation is complete. Once the first animation has completed and the ball goes to the center of the screen, you will then make the ball rotate 180 degrees.

Start by creating a new method in the ViewController.m file, as shown in Listing 13-6. You will use the animateWithDuration method as you did earlier.

Listing 13-6.  Implementing the startRotationOfBall Method Without Adding the Animation

-(void)startRotationOfBall
{
    
    [UIView animateWithDuration:1.5f
                     animations:^{
                         //implement rotation code here
                     }
                     completion:^(BOOL finished) {
                         NSLog(@"Rotation Finished");
                     }];
    
}

Now let’s add a rotation to the animation block. In Listing 13-7 you create a transform using the CGAffineTranformMakeRotation function. You might notice that you use the constant M_PI, which is 3.14 radians, or 180 degrees. You can change the rotation direction by making the M_PI value negative. A positive value will be counterclockwise, and a negative value will be clockwise.

Listing 13-7.  Completing the Ball Rotation Animation Method

-(void)startRotationOfBall
{
    
    [UIView animateWithDuration:1.5f
                     animations:^{
                         self.blueBall.transform = CGAffineTransformMakeRotation(M_PI);
                     }
                     completion:^(BOOL finished) {
                         NSLog(@"Rotation Finished");
                     }];
    
}

The last thing to do is call this new method from the completion block of the first animation. This is a simple one-line call, as you can see in Listing 13-8.

Listing 13-8.  Modifying the Completion Block of the First animateWithDuration: Method to Create the Rotation

- (void)viewDidLoad
{
//...

    [UIView animateWithDuration:3.0f
                     animations:^{
                         self.blueBall.frame = CGRectMake(self.view.frame.size.width/2-32.0f,
                                                          self.view.frame.size.height/2 -32.0f,
                                                          64.0f,
                                                          64.0f);
                         self.blueBall.alpha = 1.0f;
    
                     }
                     completion:^(BOOL finished) {
                         NSLog(@"Animation Finished");
                           [self startRotationOfBall];
                     }];

}

That’s it! If you run your application, you will see the first animation finish, and then the ball will rotate 180 degrees counterclockwise. When the animations have completed, your application should look like Figure 13-3.

9781430259596_Fig13-03.jpg

Figure 13-3. The completed app with rotation

In the next recipe, we’ll create more realistic and interesting animations using UIKit Dynamics.

Recipe 13-2. Implementing UIKit Dynamics

UIKit Dynamics is a new framework in iOS 7 that gives developers the ability to add real-life movement to their animations. These movements include gravity, bouncing, collision, and even subtle effects such as friction.

In this recipe, we will show you many of the dynamic effects you can create Using UIKit dynamics as well as how to combine multiple effects. Near the end of this recipe, you will learn how to create a custom behavior class, which will allow you to bundle many custom behaviors and easily add effects to view components.

Using Gravity

To start, create a single view application and title it “Recipe 13-2 Implementing UIKit Dynamics.” This time, fill in the class prefix with Gravity, as shown in Figure 13-4. For this recipe, you’ll make somewhat extensive use of storyboards, so you might want to skim Chapter 2 to familiarize yourself. Not to worry, though, we’ll be explaining all the steps necessary.

9781430259596_Fig13-04.jpg

Figure 13-4. Choosing options for your app

Once the project is created, navigate to the Main.storyboard file and drag a new table view controller onto the storyboard. With the table view controller selected, choose Editor arrow.jpg Embed In arrow.jpg Navigation Controller from the Xcode file menu. Your storyboard should be arranged as shown in Figure 13-5.

9781430259596_Fig13-05.jpg

Figure 13-5. The navigation controller without connections

Next, select the table view and change the content from Dynamic Prototypes to Static Cells, as shown in Figure 13-6.

9781430259596_Fig13-06.jpg

Figure 13-6. Changing the table view cell type

Once you change to static cells, the prototype cell will be replaced with three static cells. Control-click and drag from the first table view cell to the view controller, as shown in Figure 13-7. When the dialog box appears, choose Push under Selection Segue.

9781430259596_Fig13-07.jpg

Figure 13-7. Connecting the first table view cell to the gravity view controller

Next, click and drag the arrow that is connected only to the gravity view controller and move it to the left side of the navigation controller, as shown in Figure 13-8.

9781430259596_Fig13-08.jpg

Figure 13-8. Relocating the start scene arrow

Finally, drag an image view onto the gravity view controller and change its size to 64 x 64 points. As you did in Recipe 13-1, drag the Ball.png image file into the resources folder in the project navigator.

Next, set the image view image to Ball.png, as you did in Recipe 13-1. Also, change the title of the view controller to “Gravity” and the title of the table view controller to “Dynamics Playground.” Your completed storyboard should look like Figure 13-9. You will also need to change the style of the table view cell from “custom” to “basic” from the attributes inspector with the cell selected. This will allow you to edit the title.

9781430259596_Fig13-09.jpg

Figure 13-9. The complete interface

Now, if you run the application, you can tap the gravity table view cell, and it will take you to the gravity view controller with the ball. Of course, the ball will not do anything, but you are now set up with a good framework for the rest of this recipe.

Now we will implement the gravity behavior. Create an outlet from the blue ball to the GravityViewController.h file. Title the new outlet “blueBall.” Also, add a new UIDynamicsAnimator property titled “animator” to the header file. When you are done, your GravityViewController.h file should have an outlet and a property, as shown in Listing 13-9.

Listing 13-9.  The Start ViewController.h File

//
//  GravityViewController.h
//  Recipe 13-2 Implementing UIKit Dynamics
//
#import <UIKit/UIKit.h>

@interface GravityViewController : UIViewController

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

@end

Now you can switch to the GravityViewController.m file and start editing the viewDidLoad method, as shown in Listing 13-10. For this part of the recipe, you will give the ball a gravity effect when the view loads. This will make the ball accelerate toward the bottom of the screen and eventually fall off the screen.

Listing 13-10.  Modifying the viewDidLoad Method to Create the Gravity Behavior

- (void)viewDidLoad
{
    [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[self.blueBall]];
    
    [self.animator addBehavior:gravityBehavior];
}

In Listing 13-10, all you do is allocate and initialize the animator with the current view and create a gravity behavior. Then you add that behavior to the animator. If you load the application and select Gravity from the table view, the ball will drop down and off the screen once the view loads.

Gravity and Collision

Now you’ll build on the previous example by making a collision with the view boundary. The steps to build this are as follows:

  1. Drag a new view controller onto the storyboard.
  2. As you did with the first view controller, Control-drag from the second table view cell to the new view controller and create a push selection segue.
  3. Create a new image view that is 64 points x 64 points and set its image to “Ball.png.”
  4. Update the table view title with the value “Gravity with Collision” and give the same title to the new view controller.

When you are done with these steps, your storyboard scene should resemble Figure 13-10.

9781430259596_Fig13-10.jpg

Figure 13-10. Creating a new view controller with segue

Because you created a new view controller on the storyboard, you will also need to create a new class with a UIViewControllersubclass. Title the new class GravityWithCollisionViewControllerand make sure the “With XIB for interface” check box is not selected.

Next, you will need to tie your new GravityWithCollisionViewController class to the new view controller. Do this by selecting the view and choosing the class from the identity inspector, as shown in Figure 13-11.

9781430259596_Fig13-11.jpg

Figure 13-11. Setting the view controller class in a storyboard

Now you are ready to add some code. First, as you did in the GravityViewController class, create an outlet for the blue ball and a property for the animator. Listing 13-11 shows these changes.

Listing 13-11.  The Finished GravityWithCollisionViewController.h File

//
//  GravityWithCollisionViewController.h
//  Recipe 13-2 Implementing UIKit Dynamics
//
#import <UIKit/UIKit.h>

@interface GravityWithCollisionViewController : UIViewController

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

@end

This time the viewDidLoad method will look a bit different. This code builds on the previous example and adds a new behavior. The viewDidLoad method should now look like Listing 13-12.

Listing 13-12.  The Completed GravityWithCollisionViewController viewDidLoad Method

- (void)viewDidLoad
{
    [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[self.blueBall]];
    UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.blueBall]];
    
    collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
    
    [self.animator addBehavior:gravityBehavior];
    [self.animator addBehavior:collisionBehavior];

}

In Listing 13-12 you are creating a new collision behavior. You do this by setting the translatesReferenceBoundsIntoBoundary property to “YES” so that the ball will collide with the bounds of the view. Finally, you add the new behavior to the animator.

If you run the application and select the Gravity with Collision table cell, the ball with fall and hit the bottom boundary and then bounce before it settles at the location, as shown in Figure 13-12.

9781430259596_Fig13-12.jpg

Figure 13-12. Gravity with collision once the ball has settled

This is great, but what if you want to know when the ball began the collision or ended a collision? Fortunately, iOS 7 has some tools to handle this as well. For this example, you will modify the code so the ball will turn semi-transparent while bouncing. More specifically, the ball will hit the bottom of the frame and bounce. After the bounce, the ball will turn transparent every time it is not in contact with the bottom of the frame.

To do this, you’ll need to declare the UICollisionBehaviorDelegate in the GravityWithCollisionViewController.h file, as shown in Listing 13-13.

Listing 13-13.  Declaring the UICollisionBehaviorDelegate

//
//  GravityWithCollisionViewController.h
//  Recipe 13-2 Implementing UIKit Dynamics
//
#import <UIKit/UIKit.h>

@interface GravityWithCollisionViewController : UIViewController <UICollisionBehaviorDelegate>

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

@end

Next, you will need to set the collision delegate. To do this, modify the viewDidLoad method, as shown in Listing 13-14.

Listing 13-14.  Setting the UICollisionBehaviorDelegate

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[self.blueBall]];
    UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.blueBall]];
    
    collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
    collisionBehavior.collisionDelegate = self;
    
    [self.animator addBehavior:gravityBehavior];
    [self.animator addBehavior:collisionBehavior];

}

Listing 13-15 shows the necessary delegate methods that need to be added. The first method will set the alpha of the blue ball to 100 percent when the ball is in contact with the boundary. The second method will set the blue ball to an alpha value of 50 percent right after the collision has occurred. The resulting effect, as mentioned earlier, is that the ball will be semi-transparent any time it is not touching the boundary after the first bounce.

Listing 13-15.  Implementing the Two collisionBehavior Delegates

-(void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p
{
    [self.blueBall setAlpha:1.0f];
}

-(void)collisionBehavior:(UICollisionBehavior *)behavior endedContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier
{
    [self.blueBall setAlpha:0.5f];
}

If you run the application now and select Gravity with Collision, you will see the ball fall from the top of the screen, bounce and turn semi-transparent, and then settle at the bottom without transparency.

Using Item Properties

Now you know how to add a gravity and collision behavior to a view, but what if you wanted to set how high the ball will bounce? To do this, you can add an elasticity property to the ball.

To create a new view controller, use the following steps:

  1. Drag a new view controller onto the storyboard and connect it to the third table view cell with a push selection segue.
  2. This time, add two balls to the new view and situate them as shown in Figure 13-13.

    9781430259596_Fig13-13.jpg

    Figure 13-13. Setting up the item property view controller

  3. As you did with GravityWithCollisionViewController, create a new UIViewController subclass and title it ItemPropertyViewController.
  4. Create two new outlets for the balls. The ball on the left should have an outlet titled “ball1,” and the ball on the right should be titled “ball2.”
  5. Create an animator property in the header file.

When you have finished these steps, your header file should resemble Listing 13-16.

Listing 13-16.  The Final ItemPropertyViewController.h File

//
//  ItemPropertyViewController.h
//  Recipe 13-2 Implementing UIKit Dynamics
//
#import <UIKit/UIKit.h>

@interface ItemPropertyViewController : UIViewController

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

@end

Now you will edit your viewDidLoad method to include gravity and collision for both balls. This time you will leave out the delegate for detecting collision and add the gravity and collision behaviors necessary for both balls. These changes are shown in Listing 13-17.

Listing 13-17.  Setting Up the viewDidLoad Method to Add Gravity and Collision Behaviors to the Balls

- (void)viewDidLoad
{
    [super viewDidLoad];

self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[self.ball1,self.ball2]];
    UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.ball1,self.ball2]];
    
    collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
    
    [self.animator addBehavior:gravityBehavior];
    [self.animator addBehavior:collisionBehavior];
}

Now comes the fun part. You are going to add a property to the second ball’s behavior. There are actually quite a few properties to choose from. Here are all of the available properties:

  • elasticity: A float value sets the collision elasticity; use 0 for nonelastic or 1 for very elastic.
  • friction: A float value sets the friction of an object between others; use 0 for no friction.
  • density: This is a float value for density; 1 is the default.
  • resistance: A float value sets velocity damping; 0 is no velocity damping.
  • angularResistance: This is a float value for angular velocity damping; 0 is no angular velocity damping.
  • allowsRotation: A Boolean value sets whether an object will have locked rotation.

For this example, we will set the elasticity for the second ball image as shown in Listing 13-18, which will make it bounce higher when it collides with the boundary at the bottom.

Listing 13-18.  Setting the Behavior Property on the Second Ball

- (void)viewDidLoad
//
//  ItemPropertyViewController.m
//  Recipe 13-2 Implementing UIKit Dynamics
//

{
    [super viewDidLoad];

    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[self.ball1,self.ball2]];
    UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.ball1,self.ball2]];
    UIDynamicItemBehavior* propertiesBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.ball2]];
    propertiesBehavior.elasticity = 0.75f;
    
    collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
    
    [self.animator addBehavior:propertiesBehavior];
    [self.animator addBehavior:gravityBehavior];
    [self.animator addBehavior:collisionBehavior];
}

If you run the app now, you will notice the two balls will fall and collide with the bottom of the view at the same time. The ball on the left will bounce once, but the ball on the right will bounce more than once and bounce much higher.

We encourage you to rearrange the balls in several ways and change the behaviors. For example, if you add resistance, the ball will resist gravity and accelerate more slowly.

Adding Snap

Now let’s explore a behavior that will allow you to snap an object to a point on the view. The point in the view will be defined by a touch gesture, and the object in this case will be the ball. The snap behavior is kind of like a magnetic behavior. Wherever you touch the screen, the ball will shoot over to that location as if being attracted there by a magnetic force.

As usual, follow these steps to get started with a new view controller:

  1. Drag a view controller onto the storyboard and give it an accompanying class titled SnapViewController.
  2. Add one ball image view and an outlet for it titled “blueBall.”
  3. Add a UIDynamicAnimator property to the header file as you did before.
  4. Because we have exceeded the number of table view cells provided, you will need to drag a new one onto the table view from the object library.
  5. When the new cell is created, make a push selection segue connection between it and the new view controller.

By now your storyboard should look similar to the one in Figure 13-14.

9781430259596_Fig13-14.jpg

Figure 13-14. The full storyboard with the snap view controller added  

Next, you will need to add a tap gesture recognizer, a property that recognizes a touch event, to the snap view controller. Do this by dragging a Tap Gesture Recognizer from the object library onto the new view controller in the storyboard. When you have done this correctly, you should see a gesture recognizer icon show up at the bottom of the view controller, as shown in Figure 13-15.

9781430259596_Fig13-15.jpg

Figure 13-15. Icon indicating added gesture recognizer

Now you will need to make an action for the newly added gesture recognizer. To do this, Control-drag from the gesture icon shown in Figure 13-15 to the SnapViewController.h file shown in Figure 13-16. You can give this action a name of handleGestureRecognizer. Make sure you choose the type of action as UITapGestureRecognizer instead of id.

9781430259596_Fig13-16.jpg

Figure 13-16. Adding a gesture action

Now your complete SnapViewController.h file should have two properties and a gesture action, as shown in Listing 13-19.

Listing 13-19.  The Complete SnapViewController.h File

//
//  SnapViewController.h
//  Recipe 13-2 Implementing UIKit Dynamics
//

#import <UIKit/UIKit.h>

@interface SnapViewController : UIViewController

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

- (IBAction)handleGestureRecognizer:(UITapGestureRecognizer *)sender;

@end

Next, you will need to allocate and initialize the animator in the viewDidLoad method, as shown in Listing 13-20.

Listing 13-20.  Initializing the Animator

- (void)viewDidLoad
{
    [super viewDidLoad];

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

Finally, you need to fill in the handleGestureRecognizeraction method so that it creates the snap behavior when the user touches the screen. Listing 13-21 shows the implementation of this method.

Listing 13-21.  Implementation of the handleGestureRecognizer: Action Method

- (IBAction)handleGestureRecognizer:(UITapGestureRecognizer *)sender
{
    CGPoint point = [sender locationInView:self.view];
    
    if([self.animator behaviors])
    {
        [self.animator removeAllBehaviors];
        
        UISnapBehavior* snapBehavior = [[UISnapBehavior alloc] initWithItem:self.blueBall snapToPoint:point];
        [self.animator addBehavior:snapBehavior];

    }
    else
    {
        UISnapBehavior* snapBehavior = [[UISnapBehavior alloc] initWithItem:self.blueBall snapToPoint:point];
        [self.animator addBehavior:snapBehavior];
    }
}

In Listing 13-21, you first set a CGPoint variable, which is a primitive data type that contains a screen coordinate. You set the CGPoint to the point in the view where the user touched. Then you check to see whether the animator already has a behavior. If there are behaviors, you remove them and add the new behavior. If this is the first touch, you just add the new behavior.

If you run the application and navigate to the snap view controller, you can touch anywhere in the view, and you will see the ball shoot over to where you touched. The behavior also adds a nice circle effect as the ball settles to the point.

Creating Push Behaviors

For this next view controller, you will be implementing both a continuous push behavior and an instantaneous push behavior. The continuous push will apply a magnitude to the view for the duration of the push. If you know anything about physics, this means it will accelerate because you are continuously adding more energy to the view. You can think of it like a car accelerating: the wheels are continuously pushing the car, so it accelerates. An instantaneous push, on the other hand, is more like velocity. This is similar to a pool cue hitting a cue ball. Once the ball has been hit by the cue, the ball will not speed up or slow down (much).

In this example, we will demonstrate these behaviors by applying each one of them to a separate ball image. As you will see, one ball will move along at a constant pace, while the other one will gradually pick up speed. Before we delve into the code, we’ll provide brief explanations of the behavior properties.

Push behaviors need two properties in order to work:

  • Magnitude: This is a float value defined by Apple as the “magnitude of the force vector for the push behavior.” The default magnitude is nil, which means the object won’t go anywhere. A magnitude of 1.0 will move a 100-point x 100-point view with a density of 1.0 at an acceleration of 100 points/second^2. So, after the first second, the view will have traveled 100 points; after the second, it will have traveled 300 points, and so on.
  • Angle: This is a float value in radians. A positive value is clockwise, and a negative value is counterclockwise.

Now that you have a little background, follow these steps to get started:

  1. Create a new view controller and tie it to the table view.
  2. Create a new class titled PushViewControllerand connect it to the view controller.
  3. Change the title of table view cell and the view controller to “Push.” Create a new table view cell if needed.
  4. Create a push selection segue between the table view cell and the view controller.
  5. Add two 64-point x 64-point image views to the view controller with the image Ball.png, as shown in Figure 13-17.

    9781430259596_Fig13-17.jpg

    Figure 13-17. Push view controller layout

  6. Add two outlets for the balls titled “ball1” and “ball2.”
  7. Add an animator property to the header file.

Once you are finished with these steps, your header file should look like Listing 13-22.

Listing 13-22.  The Complete PushViewController.h File

//
//  PushViewController.h
//  Recipe 13-2 Implementing UIKit Dynamics
//
#import <UIKit/UIKit.h>

@interface PushViewController : UIViewController

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

@end

Now you will add the code to the viewDidLoad method to actually create the push behaviors. Listing 13-23 shows the completed viewDidLoad method. In this code, you first create two types of behaviors, one behavior for each ball. You set the angle and magnitude for each behavior. The angle is negative because you want the balls to go upward, or 90 degrees in the counterclockwise direction. Lastly, you add the behaviors to the animator.

Listing 13-23.  The Complete viewDidLoad Method

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    
    UIPushBehavior *instantPushBehavior = [[UIPushBehavior alloc] initWithItems:@[self.ball1] mode:UIPushBehaviorModeInstantaneous];
    UIPushBehavior *continuousPushBehavior = [[UIPushBehavior alloc] initWithItems:@[self.ball2] mode:UIPushBehaviorModeContinuous];
    
    instantPushBehavior.angle = -1.57;
    continuousPushBehavior.angle = -1.57;
    
    instantPushBehavior.magnitude = 0.5;
    continuousPushBehavior.magnitude = 0.5;
    
    [self.animator addBehavior:instantPushBehavior];
    [self.animator addBehavior:continuousPushBehavior];
    
}

Now if you build and select the Push from the table view, you will see the two balls take off at the same time. The left ball will have more velocity but will not speed up or slow down. The right ball will start off slow but quickly gain speed.

Note   You might be wondering at this point how the ball can be accelerating without any density. By default, dynamic items all have a density of 1.

Spring and Attachment

The last pieces of the dynamics puzzle are springs and attachments. Using the attachment, you can attach two views with either a ridged attachment or a spring-like attachment. UIKit Dynamics gives us the power to choose anchor points to both arbitrary points or on the views themselves. Depending on how you attach these views, you can make some really cool effects using spring behavior.

For this section, you will attach a ball to an anchor point and then attach a star to the ball. The star will have an attachment a little left of center, which will cause the star to spin. The attachments will use the spring behavior, so they will bounce around. For added effect, you will also implement gravity and collision behaviors.

Once again, follow these steps to set up a new view controller:

  1. Drag a new view controller onto the storyboard and connect it to a new table view cell.
  2. Give the new view controller and table view cell a title of “Spring Attachment.”
  3. Create a push selection segue between the table view cell and the view controller.
  4. Add a class titled SpringAttachmentViewControllerand connect it to the view controller.
  5. Arrange the new view controller, as shown in Figure 13-18, with two 64-point x 64-point image views and set their images to Ball.png and Star.png. You can obtain the star from the Apress download page for this book.

    9781430259596_Fig13-18.jpg

    Figure 13-18. Spring attachment view controller layout

  6. Add outlets for the image views and name them “ball” and “star,” respectively.
  7. Create an animator property.

The finished header file should look like Listing 13-24.

Listing 13-24.  The Complete SpringAttachmentViewController.h File

//
//  SpringAttachmentViewController.h
//  Recipe 13-2 Implementing UIKit Dynamics
//
#import <UIKit/UIKit.h>

@interface SpringAttachmentViewController : UIViewController

@property (weak, nonatomic) IBOutlet UIImageView *ball;
@property (weak, nonatomic) IBOutlet UIImageView *star;
@property (nonatomic) UIDynamicAnimator *animator;
@end

First, you need to initialize the animator and add the gravity and collision behavior to it in the viewDidLoad method, as shown in Listing 13-25.

Listing 13-25.  Adding Gravity and Collision Behaviors to the Ball and Star

//
//  SpringAttachmentViewController.m
//  Recipe 13-2 Implementing UIKit Dynamics
//

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    
    UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[self.ball,self.star]];
    UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.ball,self.star]];
    
    collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;

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

Next, you will create an anchor point. This anchor point is in the middle of the screen and 20 points down from the top of the screen. You will also create two attachment behaviors: one for the ball and one for the star. The ball is attached directly to the anchor point. The star is attached to the ball. The star also has an attachment offset 20 points left of center. As mentioned previously, this offset attachment will add a nice spinning effect. Each attachment will have a damping property and a frequency property. This is what gives you the spring effect. Feel free to adjust the values to see how it effects the animation later. Listing 13-26 shows this added behavior.

Listing 13-26.  Adding Attachments and Spring Effects to the Ball and Star

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    
    UIGravityBehavior *gravityBehavior = [[UIGravityBehavior alloc] initWithItems:@[self.ball,self.star]];
    UICollisionBehavior *collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.ball,self.star]];
    
    CGPoint anchorPoint = CGPointMake(self.view.frame.size.width/2, 20);
    
    UIAttachmentBehavior *ballAttachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:self.ball attachedToAnchor:anchorPoint];
    
    UIAttachmentBehavior *starAttachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:self.star offsetFromCenter:UIOffsetMake(-20.0, 0) attachedToItem:self.ball offsetFromCenter:UIOffsetZero];
    
    collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
    
    [ballAttachmentBehavior setFrequency:1.0];
    [ballAttachmentBehavior setDamping:0.65];
    
    [starAttachmentBehavior setFrequency:1.0];
    [starAttachmentBehavior setDamping:0.65];
    
    [self.animator addBehavior:ballAttachmentBehavior];
    [self.animator addBehavior:starAttachmentBehavior];
    [self.animator addBehavior:collisionBehavior];
    [self.animator addBehavior:gravityBehavior];
}

After building the application, you will notice that the ball and star will drop and start bouncing around as if they are attached by a spring. The star will begin to erratically spin as it dangles from the ball. The star will hang below the ball when the animation settles, as shown in Figure 13-19.

9781430259596_Fig13-19.jpg

Figure 13-19. Ball and star bouncing and spinning about

At this point, we have covered all the new dynamics behaviors. As you can see, there is a lot of power here at your disposal. Next, we will show you how to create a custom behavior class by subclassing the UIDynamicBehavior class. This will allow you to easily add custom behaviors to objects in the same way you add built-in behaviors.

Creating a Custom Behavior Class

Before we conclude this recipe, let’s briefly discuss how to create a custom behavior class. When combining behaviors, it is a good practice to create a class to encompass all of them. The new class would typically have a descriptive title such as “BouncingCollisionBehavior” or something to that effect.

As an example, you will be combining the behaviors in SpringAttachmentViewControllerinto a class. To start, create a new subclass of UIDynamicBehaviortitled BouncingSpringBehavior.

Now open the header file for the new class and create a declaration for a custom initializer. The custom initializer will take an array of dynamic view items and a string that will store the anchor coordinate. Listing 13-27 shows this method declaration.

Listing 13-27.  Declaring a Custom Initializer in the BouncingSpringBehavior Class

//
//  BouncingSpringBehavior.h
//  Recipe 13-2 Implementing UIKit Dynamics
//

#import <UIKit/UIKit.h>

@interface BouncingSpringBehavior : UIDynamicBehavior

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

@end

Now switch to the implementation file and fill out the custom initializer, as shown in Listing 13-28. The items that were changed from the previous example are now in bold.

Listing 13-28.  The initWithItems:withAnchorPoint: Initializer Implementation

-(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;
        
        [item1AttachmentBehaviorsetFrequency:1.0];
        [item2AttachmentBehaviorsetDamping:0.65];
        
        [item1AttachmentBehaviorsetFrequency:1.0];
        [item2AttachmentBehaviorsetDamping:0.65];
        
        [self addChildBehavior:gravityBehavior];
        [self addChildBehavior:collisionBehavior];
        [self addChildBehavior:item1AttachmentBehavior];
        [self addChildBehavior:item2AttachmentBehavior];
        
    }
    
    return self;
}

The preceding code should look familiar. The difference is now it is inside an initializer of a different class. The initializer takes a string for the anchorPoint instead of a CGPoint. This is because CGPoint is a C struct and not an Objective-C object, which makes it difficult to pass as a parameter. To get around this, you’ll use a nifty function to convert a string to a CGPoint. This CGPoint will be used to define the anchor point for the ball. When we create a behavior instance, we use a function to do the opposite.

As you have seen before, you will first create behaviors for gravity, collision, and the two attachments. Because the attachments are passed in as an array, the item at index 0 and index 1 are the ball and star, respectively. As you’ve done before, set the collision bound property to “YES.” Then set frequency and damping behaviors for the spring effect. Lastly, add child behaviors to the BouncingSpringBehavior class and return an instance of itself.

The last thing left to do is change the SpringAttachmentViewController implementation file to take advantage of the class. Make the changes shown in Listing 13-29 in this file.

Listing 13-29.  Setting the Newly Created UIDynamicBehavior Class

//
//  SpringAttachmentViewController.m
//  Recipe 13-2 Implementing UIKit Dynamics
//

#import "SpringAttachmentViewController.h"
#import "BouncingSpringBehavior.h"

@interface SpringAttachmentViewController ()

//...

- (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);
    
    BouncingSpringBehavior *bouncingSpringBehavior = [[BouncingSpringBehavior alloc] initWithItems:@[self.ball,self.star] withAnchorPoint:anchorPointString];
    
    [self.animator addBehavior:bouncingSpringBehavior];
    
}

You can see from Listing 13-29 that you have significantly reduced the lines of code in the view controller from what it was previously. The implementation is now much easier to read. First, you create your animator and anchor point. Next, you convert that anchor point to a string and pass it into the new instance behavior initialization. Finally, you simply add that behavior to the animator.

That’s it for this recipe. Even with everything shown here, we have only scratched the surface of what’s possible with UIKit Dynamics. There are many ways properties and behaviors can be combined to create unique dynamic scenes.

Summary

In this chapter, you have learned the basics of using both UIView Animation and UIKit Dynamics. You now know how to create simple animations that vary in size, opacity, and rotation. You also know how to use UIKit Dynamics to create powerful effects replicating gravity, velocity, friction, spring motion, and countless combinations. These tools will give you the power to create amazing and interactive next-generation applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset