UIKit Dynamics, Core Animation Layers and Autolayout Constraints

An iOS SDK Tutorial 


This article teaches developers how to combine UIKit Dynamics and other UIKit APIs, when using the iOS SDK.

UIKit Dynamics is a new API that allows developers to add real-world inspired interactions to an app’s UI. The core object of this API is the dynamic animator (an object of type UIDynamicAnimator), which animates a dynamic item according to the behavior you add to it. A dynamic item is an object conforming to the UIDynamicItem protocol, and with iOS 7, UIView and UICollectionView conform to this protocol by default.

I was really excited when I saw UIKit Dynamics for the first time at the WWDC 2013, because the physics engine was one wish on my WWDC wish-list (last year, Apple almost made all my wishes real!!!). However, when I went through the documentation I found that the CALayer class does not conform to the UIDynamicItem protocol. At first, I thought it would be easy to make the CALayer conform to this protocol. But I found a problem immediately when I subclassed CALayer and added UIDynamicItem as a protocol:

@interface MyLayer : CALayer <UIDynamicItem>

This didn’t work because the UIDynamicItem protocol provides 3 properties:

@property(nonatomic, readonly) CGRect bounds;
@property(nonatomic, readwrite) CGPoint center;
@property(nonatomic, readwrite) CGAffineTransform transform;

The bounds and the transform properties collide against the bounds and the transform properties of CALayer defined in this way:

@property CGRect bounds;
@property CATransform3D transform;

To solve it, I decided to take a different approach. This same approach can be used to add dynamics to other UIKit objects. Let’s build an example.

Dynamic layers

Create a single-view Xcode project and name it DynamicLayer. Let’s create a snap behavior and try to animate a layer with this behavior. In the ViewController.m, create a property for the dynamic animator:

@interface ViewController ()
@property (nonatomic) UIDynamicAnimator *animator; 
@end

and in the viewDidLoad create the animator and assign it to this property:

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

Now, let’s add a new class (subclass of NSObject) to the project. Name it DynamicHub and make it conform to UIDynamicItem protocol.

#import <Foundation/Foundation.h>
@interface DynamicHub : NSObject <UIDynamicItem>
@property(nonatomic, readonly) CGRect bounds;
@property(nonatomic, readwrite) CGPoint center;
@property(nonatomic, readwrite) CGAffineTransform transform;
@end

In the implementation, add the following init method.

— (id)init {
    self = [super init];
    if (self) {
        _bounds = CGRectMake(0, 0, 100, 100); // a
    }
    return self;
}

The initialization of the _bounds instance variable (line a) is necessary, because the dynamic behavior expects the item with a size different than zero.

Let’s go back to the ViewController.m and create an instance of the DynamicHub class.

 DynamicHub *dynamicHub = [[DynamicHub alloc] init];

After doing this, let’s add a Core Animation layer with some bounds, position, and background color, and add it to the viewcontroller view layer to render it:

CALayer *layer = [CALayer layer];
[layer setPosition:CGPointMake(160, 0)];
[layer setBounds:CGRectMake(0, 0, 100, 100)];
[layer setBackgroundColor:[[UIColor redColor] CGColor]];
[layer setCornerRadius:8.0];
[self.view.layer addSublayer:layer];

Finally, let’s create a snap behavior. To learn more about other available behaviors, and how to create your own custom behavior, take a look at our previous post on UIKit Dynamics. Also consider attending one of our training classes, where we cover this and other topics in-depth. So, after the previous chunk of code, add the following lines:

UISnapBehavior *snap = [[UISnapBehavior alloc] initWithItem:dynamicHub snapToPoint:CGPointMake(160, 300)];
[snap setDamping:0.1];

Now, notice that I am using the dynamicHub object as dynamic item for the snap behavior. During the animation, the bounds, the center and transform properties of this object gets modified by the animator once we add the behavior to the animator. But, how can we transfer this behavior onto the rendered layer? Well, remember that a dynamic behavior is a subclass of a UIDynamicBehavior class. One of its properties is the action block. During every animation frame, the animator executes this action block for each frame. You can add any functionality here, but because it is called so frequently, performance is critical. For our case, the block should look like this:

snap.action = ^{
     [CATransaction begin]; //1
     [CATransaction setDisableActions:YES]; // 2
     layer.position = [dynamicHub center]; // 3
     [CATransaction commit]; // 4
};
[self.animator addBehavior:snap];

The last line of code adds the behavior to the animator and starts the animation. Lines 1, 2 and 4 are used to switch off the implicit animations of the layer. If you do not do this, every time the animator executes this block an implicit animation is generated and the final result looks strange. Line 3 is where the magic happens. For each animation frame, we are reading the center value of the dynamicHub object and updating the layer position.

Now, this example looks really easy. So, you might also ask me: can we do the same animation just using the backing layer of the view? To which, I would answer: “yes, of course!” However, with this approach, you can animate any layer property, and more importantly, you can animate any layer type: transform layers (CATransformLayer), replicator layers (CAReplicatorLayer), text layers (CATextLayer), and so on.

Sometime ago, we published a couple of posts about Replicator Layers and Transform Layers. Take a look at these posts. Maybe you will get some interesting ideas for new animations.

Dynamic layout constraints

Autolayout was introduced in iOS 6, but, initially, its adoption by the developer community was very poor. Lack of compatibility with iOS 5 caused many developers to simply ignored it. This changed when iOS 7 came out last year. Suddenly, developers were forced to use auto layout because of the different metrics between iOS 6 and iOS 7. Xcode 5 also simplified its usage.

We showed how to use Auto Layout in combination with Core Animation in this post.

Here, we will reveal how UIKit Dynamics can be applied to the layout constraints to generate nice animation effects. Again we will use the DynamicHub class from the previous example.

Let’s create a new single-view project and name it DynamicLayout. In the Main.storyboard file, add a 100x100 view to the viewcontroller view and change is color to orange. Center the view in the viewcontroller view and add 4 constraints to fix the view to the edges of its container (see next figure). Also add a button at the bottom of the viewcontroller view and constrain it to the view center 20 pixels from the view bottom.

In the ViewController.h, create 4 outlets for the 4 orange view constraints:

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *rightConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *leftConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *topConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomConstraint;

Then, connect them to the 4 constraints in Interface Builder as shown here:

Also add an outlet and an action for the button:

@property (weak, nonatomic) IBOutlet UIButton *startButton;
— (IBAction)start☹id)sender;

and connect them in Interface Builder with the button object. Let’s also add a property for the animator as we did it in the previous example:

@property (nonatomic) UIDynamicAnimator *animator;

Add the UIDynamicAnimatorDelegate as protocol of the ViewController class in the ViewController.m file:

@interface ViewController () <UIDynamicAnimatorDelegate>

In the viewDidLoad add these two lines of code:

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

In the button action, we add the code to generate the behavior animation. Before that, let’s add a dynamic animator delegate method to enable the button after the animation is completed and remove the behavior from the animator:

— (void)dynamicAnimatorDidPause☹UIDynamicAnimator *)animator {
    self.startButton.enabled = YES;
    [animator removeAllBehaviors];
}

Now, let’s build the important functionality. The button action contains this code:

self.startButton.enabled = NO; // 1
DynamicHub *dynamicHub = [[DynamicHub alloc] init]; // 2
UISnapBehavior *snapBehavior = [[UISnapBehavior alloc] initWithItem:dynamicHub snapToPoint:CGPointMake(50.0, 150.0)];
[snapBehavior setDamping:.1];
snapBehavior.action = ^{
    self.topConstraint.constant = [dynamicHub center].y;
    self.bottomConstraint.constant = [dynamicHub center].y;
    self.leftConstraint.constant = [dynamicHub center].x;
    self.rightConstraint.constant = [dynamicHub center].x;
};
[self.animator addBehavior:snapBehavior]; // 3

In line 1, we disable the button to avoid having the user continue to fire this method until the animation is completed. In line 2, we create our dynamicHub object as we did in the previous example with the layer. Then, we again create a snap behavior with the dynamicHub as a dynamic item and a given snap point (we will discuss this a little bit later). Finally, we build again the action block. This time, we use the center components (x and y) to change the constant property of each constraint. I decided to use the x for the horizontal constraints and the y for the vertical constraints. If you use just one of them (x or y) for the 4 constraints, you will get a slightly different animation (try that). Line 3 adds the snapBehavior to the animator to fire the animation.

The following video shows the final result.

http://www.youtube.com/embed/QWPPChc9RZk

Conclusions

As explained in Apple documentation and videos, and in various blogs around in the Internet, you can use UIKit Dynamics to animate views and collection views.

Here, however, I have shown how to use UIKit Dynamics differently. Try this approach for other objects. For example, try to animate the contentOffset of a scrollview using the gravity behavior. Or try to animate the color components of a layer using the transform property of the UIDynamicItem protocol.

The possibilities are endless.

Keep coding,

Geppy


Geppy Parziale has 15+ years of professional experience in software design and development, with extensive expertise in biometrics applications, computer vision and pattern recognition.

Geppy’s proficiency in Apple-related technologies, such as Cocoa and Cocoa Touch, is exemplary, and he worked as a senior iOS and OS X engineer for computer vision and image processing applications at Apple. Geppy’s work is included in the latest versions of iOS, OS X and Xcode.

Geppy has taught iOS development since 2008, personally consulting many Fortune 100 companies. Learn more about his training classes here: https://www.invasivecode.com/ios-development-training-class.html