Click here to Skip to main content
15,887,027 members
Articles / UI
Tip/Trick

Making App Animations More Life-Like with UIKit Dynamics

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
12 Dec 2014CPOL3 min read 9.5K   1  
App animations with UIKit Dynamics

Introduction

Apple has always strived to introduce classes that can add some practicality to the user interfaces so that working on them isn't a pain-inducing exercise for the user. UIKit Dynamics is one of those tools that fuels them to achieve this objective, and in a manner most perfect.

Before the advent of the tools of the likes of UIKit Dynamics, it was a full of fuss job to accomplish this smooth interface operationability. With the UIKit Dynamics, you can add far more realism to the views in respect to their animated representations, which manages to be extremely close to how a tangible object will behave when subjected to stress and momentum.

Now, there are different classes that affect the behavior of design elements. We will explain each with elaborate examples.

UIGravityBehavior

UIGravityBehavior assigns the gravitational behavior to the views. It acts much like the real world objects which fall to the ground when there is an absence of force pulling them in any other direction.

We first have to initialize the view that the class will act upon, and this will be done in the GravityAndCollisionViewController class:

var squareView: UIView! 

We then add various methods like viewDidLoad(), the instance of UICollisionBehavior class and so on to bring the gravity into the effect.

The complete code for GravityAndCollisionViewController looks like this:

C++
import UIKit
class GravityBehaviourViewController: UIViewController {
    
    var boxView: UIView!
    var gravityBehaviour : UIGravityBehavior!
    var dynAnimationBehaviour: UIDynamicAnimator!
    var collision: UICollisionBehavior!
    var dynBehaviour: UIDynamicItemBehavior!
    override func viewDidLoad() {
        super.viewDidLoad()
    
        boxView = UIView(frame: CGRect
        (x: UIScreen.mainScreen().bounds.size.width/2-75, y: 50, width: 150, height: 150))
        boxView.backgroundColor = UIColor.redColor()
        view.addSubview(boxView)
        
        dynAnimationBehaviour = UIDynamicAnimator(referenceView: view)
        gravityBehaviour  = UIGravityBehavior(items: [boxView])
        dynAnimationBehaviour.addBehavior(gravityBehaviour )
        collision = UICollisionBehavior(items: [boxView])
        collision.translatesReferenceBoundsIntoBoundary = true
        dynAnimationBehaviour.addBehavior(collision)
        dynBehaviour = UIDynamicItemBehavior(items: [boxView])
        dynBehaviour.elasticity = 0.7
        dynAnimationBehaviour.addBehavior(dynBehaviour)
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

Snap

As the name suggests, this feature facilitates snapping the view to a desirable spot on the screen. What makes it good is that it is characterized by the elasticity that is much like in a real world. The UISnapBehavior class is used here.

You can find the Snap view controller at the Main.storyboard file. The Tap Gesture Recognizer is then needed to be dragged to view controller. Then, we move on to defining actions and naming them, before proceeding any further.

Here is how we will write the SnapViewController.swift file:

C++
import UIKit
class SnapBehaviourViewController: UIViewController {
    
    var boxView: UIView!
    var snapBehaviour : UISnapBehavior!
    var dynAnimator: UIDynamicAnimator!
    
    @IBAction func handleTap(sender: UITapGestureRecognizer) {
        // handletap is the action
        let tapPoint: CGPoint = sender.locationInView(view)
        
        if (snapBehaviour != nil) {
            dynAnimator.removeBehavior(snapBehaviour)
        }
        
        snapBehaviour = UISnapBehavior(item: boxView, snapToPoint: tapPoint)
        dynAnimator.addBehavior(snapBehaviour)    
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Make some more settings before the view is loaded.
        
        boxView = UIView(frame: CGRect(x: 100, y: 100, width: 150, height: 150))
        boxView.backgroundColor = UIColor.orangeColor()
        view.addSubview(boxView)
        
        dynAnimator = UIDynamicAnimator(referenceView: view)        
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

The UISnapBehavior is called instantly as and when the user touches the screen.

Attachment

Attach feature is leveraged to establish a connection between any two separate items, or for that matter, an item and a spot on screen. Let us elaborate it further by using the UIAttachmentBehavior class to attach the view to a point.

The Attach view controller can be found in the Main.storyboard file. We would then need a pan gesture recognizer in the view controller. And then, we can proceed with the rest of the steps like defining and assigning actions.

The code for AttachviewController.swift will look like:

C++
import UIKit
class AttachmentBehaviourViewController: UIViewController {
    
    var boxView: UIView!
    var referenceView: UIView!
    var attachment: UIAttachmentBehavior!
    var dynAnimationBehaviour: UIDynamicAnimator!
    var gravityBehaviour: UIGravityBehavior!
    @IBAction func handlePan(sender: UIPanGestureRecognizer) {
        
        attachment.anchorPoint = sender.locationInView(view)
        referenceView.center = sender.locationInView(view)        
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        boxView = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        boxView.backgroundColor = UIColor.greenColor()
        view.addSubview(boxView)
        
        referenceView = UIView
        (frame: CGRect(x: view.center.x, y: view.center.y, width: 30, height: 30))
        referenceView.backgroundColor = UIColor.blackColor()
        view.addSubview(referenceView)
        
        attachment = UIAttachmentBehavior(item: boxView, 
        attachedToAnchor: CGPointMake(referenceView.center.x, referenceView.center.y))
        
        dynAnimationBehaviour = UIDynamicAnimator(referenceView: view)
        dynAnimationBehaviour.addBehavior(attachment)
        
        gravityBehaviour = UIGravityBehavior(items: [boxView])
        dynAnimationBehaviour.addBehavior(gravityBehaviour)        
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}
Paragraph 4: Push Behaviour
import UIKit
class PushBehaviourViewController: UIViewController {
    
    var greyBoxView: UIView!
    var yelloBoxView: UIView!
    var dynAnimator: UIDynamicAnimator!
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        greyBoxView = UIView(frame: CGRect(x: 100, y: 100, width: 70, height: 70))
        greyBoxView.backgroundColor = UIColor.grayColor()
        view.addSubview(greyBoxView)
        
        yelloBoxView = UIView(frame: CGRect(x: 220, y: 100, width: 50, height: 50))
        yelloBoxView.backgroundColor = UIColor.yellowColor()
        view.addSubview(yelloBoxView)
        
        let geryBoxPushBehaviour: UIPushBehavior = UIPushBehavior
        (items: [greyBoxView], mode: UIPushBehaviorMode.Continuous)
        let yellowBoxPushBehaviour: UIPushBehavior = UIPushBehavior
        (items: [yelloBoxView], mode: UIPushBehaviorMode.Instantaneous)
        
        geryBoxPushBehaviour.setAngle( CGFloat(M_PI_2) , magnitude: 0.5);
        yellowBoxPushBehaviour.setAngle( CGFloat(M_PI_2) , magnitude: 0.5);
        
        dynAnimator = UIDynamicAnimator(referenceView: view)
        dynAnimator.addBehavior(geryBoxPushBehaviour)
        dynAnimator.addBehavior(yellowBoxPushBehaviour)        
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

Now, in the code written above, we have created more than one view and the UIAttachmentBehavior is instantiated with the squareView. Now, one of the boxes is falling down because of gravity, but the other box keeps it from falling to the ground because of the attachment property.

Push

Now, when it comes to the UIPushBehavior, there are two modes, namely, continuous push behavior and instantaneous push behavior. The former executes a push behavior that keeps running till the specified duration is completed. In the latter case, there is just a single instance of push.

With continuous push, as there is incessant energy applied, the view will accelerate.

Let's see a code for applying different pushes on two views:

C++
import UIKit
class PushBehaviourViewController: UIViewController {
    
    var greyBoxView: UIView!
    var yelloBoxView: UIView!
    var dynAnimator: UIDynamicAnimator!
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        greyBoxView = UIView(frame: CGRect(x: 100, y: 100, width: 70, height: 70))
        greyBoxView.backgroundColor = UIColor.grayColor()
        view.addSubview(greyBoxView)
        
        yelloBoxView = UIView(frame: CGRect(x: 220, y: 100, width: 50, height: 50))
        yelloBoxView.backgroundColor = UIColor.yellowColor()
        view.addSubview(yelloBoxView)
        
        let geryBoxPushBehaviour: UIPushBehavior = UIPushBehavior
        (items: [greyBoxView], mode: UIPushBehaviorMode.Continuous)
        let yellowBoxPushBehaviour: UIPushBehavior = UIPushBehavior
        (items: [yelloBoxView], mode: UIPushBehaviorMode.Instantaneous)
        
        geryBoxPushBehaviour.setAngle( CGFloat(M_PI_2) , magnitude: 0.5);
        yellowBoxPushBehaviour.setAngle( CGFloat(M_PI_2) , magnitude: 0.5);
        
        dynAnimator = UIDynamicAnimator(referenceView: view)
        dynAnimator.addBehavior(geryBoxPushBehaviour)
        dynAnimator.addBehavior(yellowBoxPushBehaviour)        
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

We have predefined the magnitude as well as the angles to dictate the direction in which the push is working. As a result of the code values, we can see two boxes starting at just about the same point, but one of them moving at a higher speed.

Let's See an Example that Combines Multiple Behaviors

Now that we have elaboratively discussed how each behavior works, let us get down to combining the different behaviors on views by citing an example in the shape of a code:

C++
import UIKit
class ExampleApplicationViewController: UIViewController {
    
    var overlayView: UIView!
    var alertView: UIView!
    var dynAnimationBehaviour: UIDynamicAnimator!
    var attachmentBehavior : UIAttachmentBehavior!
    var snapBehavior : UISnapBehavior!
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        // Initialize the animator
        dynAnimationBehaviour = UIDynamicAnimator(referenceView: view)
        
        // Create the dark background view and the alert view
        createOverlay()
        createAlert()
    }
    
    func createOverlay() {
        // Create a gray view and set its alpha to 0 so it isn't visible
        overlayView = UIView(frame: view.bounds)
        overlayView.backgroundColor = UIColor.blackColor()
        overlayView.alpha = 0.0
        view.addSubview(overlayView)
    }
    
    func createAlert() {
        // Here the red alert view is created. 
        // It is created with rounded corners and given a shadow around it
        let alertWidth: CGFloat = 250
        let alertHeight: CGFloat = 150
        let alertViewFrame: CGRect = CGRectMake(0, 0, alertWidth, alertHeight)
        alertView = UIView(frame: alertViewFrame)
        alertView.backgroundColor = UIColor.yellowColor()
        alertView.alpha = 0.0
        alertView.layer.cornerRadius = 10;
        alertView.layer.shadowColor = UIColor.blackColor().CGColor;
        alertView.layer.shadowOffset = CGSizeMake(0, 5);
        alertView.layer.shadowOpacity = 0.3;
        alertView.layer.shadowRadius = 10.0;
        
        // Create a button and set a listener on it for when it is tapped. 
        // Then the button is added to the alert view
        let button = UIButton.buttonWithType(UIButtonType.System) as UIButton
        button.setTitle("Dismiss", forState: UIControlState.Normal)
        button.backgroundColor = UIColor.whiteColor()
        button.frame = CGRectMake(0, 0, alertWidth, 40.0)
        
        button.addTarget(self, action: Selector("dismissAlert"), 
            forControlEvents: UIControlEvents.TouchUpInside)
        
        alertView.addSubview(button)
        view.addSubview(alertView)
    }
    
    func showAlert() {
        // When the alert view is dismissed, 
        // I destroy it, so I check for this condition here
        // since if the Show Alert button is tapped again after dismissing, 
        // alertView will be nil
        // and so should be created again
        if (alertView == nil) {
            createAlert()
        }
        
        // I create the pan gesture recognizer here and not in ViewDidLoad() to
        // prevent the user moving the alert view on the screen before it is shown.
        // Remember, on load, the alert view is created but invisible to user, so you
        // don't want the user moving it around when they swipe or drag on the screen.
        createGestureRecognizer()
        
        dynAnimationBehaviour.removeAllBehaviors()
        
        // Animate in the overlay
        UIView.animateWithDuration(0.4) {
            self.overlayView.alpha = 1.0
        }
        
        // Animate the alert view using UIKit Dynamics.
        alertView.alpha = 1.0
        
        var snapBehaviour: UISnapBehavior = 
            UISnapBehavior(item: alertView, snapToPoint: view.center)
        dynAnimationBehaviour.addBehavior(snapBehaviour)
    }
    
    func dismissAlert() {
        
        dynAnimationBehaviour.removeAllBehaviors()
        
        var gravityBehaviour: UIGravityBehavior = UIGravityBehavior(items: [alertView])
        gravityBehaviour.gravityDirection = CGVectorMake(0.0, 10.0);
        dynAnimationBehaviour.addBehavior(gravityBehaviour)
        
       
        var itemBehaviour: UIDynamicItemBehavior = UIDynamicItemBehavior(items: [alertView])
        itemBehaviour.addAngularVelocity(CGFloat(-M_PI_2), forItem: alertView)
        dynAnimationBehaviour.addBehavior(itemBehaviour)
        
        UIView.animateWithDuration(0.4, animations: {
            self.overlayView.alpha = 0.0
        }, completion: {
            (value: Bool) in
            self.alertView.removeFromSuperview()
            self.alertView = nil
        })        
    }
    
    @IBAction func showAlertView(sender: UIButton) {
        showAlert()
    }
    
    func createGestureRecognizer() {
        let panGestureRecognizer: UIPanGestureRecognizer = 
            UIPanGestureRecognizer(target: self, action: Selector("handlePan:"))
        view.addGestureRecognizer(panGestureRecognizer)
    }
    
    func handlePan(sender: UIPanGestureRecognizer) {
        
        if (alertView != nil) {
            let panLocationInView = sender.locationInView(view)
            let panLocationInAlertView = sender.locationInView(alertView)
            
            if sender.state == UIGestureRecognizerState.Began {
                dynAnimationBehaviour.removeAllBehaviors()
                
                let offset = UIOffsetMake(panLocationInAlertView.x - 
                    CGRectGetMidX(alertView.bounds), panLocationInAlertView.y - 
                    CGRectGetMidY(alertView.bounds));
                attachmentBehavior = UIAttachmentBehavior
                (item: alertView, offsetFromCenter: offset, attachedToAnchor: panLocationInView)
                
                dynAnimationBehaviour.addBehavior(attachmentBehavior)
            }
            else if sender.state == UIGestureRecognizerState.Changed {
                attachmentBehavior.anchorPoint = panLocationInView
            }
            else if sender.state == UIGestureRecognizerState.Ended {
                dynAnimationBehaviour.removeAllBehaviors()
                
                snapBehavior = UISnapBehavior(item: alertView, snapToPoint: view.center)
                dynAnimationBehaviour.addBehavior(snapBehavior)
                
                if sender.translationInView(view).y > 100 {
                    dismissAlert()
                }
            }
        }        
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

We thus explain how the UIKit Dynamics suite of functions can be leveraged to create views with properties akin to real life. There are endless ways you can implement the properties to the apps and gaining command over them would serve you well in the long run.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Appsted Ltd
United States United States
This member doesn't quite have enough reputation to be able to display their biography and homepage.

Comments and Discussions

 
-- There are no messages in this forum --