Sunday, March 29, 2015

Overview

This is part 2 of the animations post. In this post, we will look at how to add special animations to views. Like when you slide from the bottom of an iPhone and the menu appears with a small bounce.

Content

Create a UIDynamicAnimator

1
var animator = UIDynamicAnimator(referenceView: UIView)

Add UIDynamicBehaviors to it (gravity, collisions etc.)

1
2
3
4
let gravity = UIGravityBehavior()
animator.addBehavior(gravity)
let collider = UICollisionBehavior()
animator.addBehavior(collider)

Add UIDynamicItems (usually UIViews) to the UIDynamicBehaviors.

1
2
3
4
5
6
let item1: UIDynamicItem = ... // usually a UIView
let item2: UIDynamicItem = ... // usually a UIView

gravity.addItem(item1)
collider.addItem(item1)
gravity.addItem(item2)

UIDynamicItem

The UIDynamicItem protocol looks like this. UIView implements this protocol and if you change center or transform while the animator is running, you must call this method in UIDynamicAnimator: func updateItemUsingCurrentState(item: DynamicItem)`

1
2
3
4
5
protocol UIDynamicItem {
    var bounds: CGRect { get }
    var center: CGPoint { get set }
    var transform: CGAffineTransform { get set }
}

UIGravityBehavior

1
2
var angle: CGFloat      // in radians; 0 is to the right; positive numbers are counter-clockwise
var magniture: CGFloat  // 1.0 is 1000 points/s/s

UIAttachmentBehavior

1
2
3
4
5
init(item: UIDynamicItem, attachedToAnchor: CGPoint)
init(item: UIDynamicItem, attachedToItem: UIDynamicItem)
init(item: UIDynamicItem, offsetFromCenter: CGPoint, attachedToItem/Anchor...)
var length: CGFloat
var anchorPoint: CGPoint

UICollisionBehavior

1
2
3
4
5
6
7
var collisionMode: UICollisionBehaviorMode // .Items, .Boundaries, .Everything
// with .Items, any items you add to a UICollisionBehavior will bounce off of each other
// with .Boundaries, then you add UIBezierPath boundaries for items to bounce off of

func addBoundaryWithIdentifier(identifier: NSCopying, forPath: UIBezierPath)
func removeBoundaryWithIdentifier(identifier: NSCopying)
var translatesReferenceBoundsIntoBoundary: Bool // referencesView's edges

Example

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
34
35
class DropItBehavior: UIDynamicBehavior {
    let gravity = UIGravityBehavior()
    lazy var collider: UICollisionBehavior {
       let lazyCollider = UICollisionBehavior() 
       lazyCollider.translateReferenceBoundsIntoBoundary = true
       return lazyCollider
    }()
    lazy var dropBehavior: UIDynamicItemBehavior = {
        let lazyDrop = UIDynamicItemBehavior()
        lazyDrop.allowsRotation = false
        lazyDrop.elasticity = 0.75
        return lazyDrop
    }()
    
    override init() {
        super.init()
        addChildBehavior(gravity)
        addChildBehavior(collider)
        addChildBehavior(dropBehavior)
    }
    
    func addDrop(drop: UIView) {
        dynamicAnimator?.referenceView?.addSubview(drop)
        gravity.addItem(drop)
        collider.addItem(drop)
        dropBehavior.addItem(drop)
    }
    
    func removeDrop(drop: UIView) {
        gravity.removeItem(drop)
        collider.removeItem(drop)
        dropBehavior.removeItem(drop)
        drop.removeFromSuperview()
    }
}
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
34
35
36
37
38
39
40
class DropBlockViewController: UIViewController {
    @IBOutlet weak var gameView: UIView!
    var dropsPerRow = 10
    var dropSize: CGSize {
        let size = gameView.bounds.size.width / CGFloat(dropsPerRow)
        return CGSize(width:size, height:size)
    }
    
    let dropBehavior = DropItBehavior()
    
    lazy var animator: UIDynamicAnimator = {
        // set to gameView in viewDidLoad because it has to set Outlet
        return UIDynamicAnimator(referenceView: self.gameView)
    }()
    
    @IBAction func drop(sender: UITapGestureRecognizer) {
        drop()
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        animator.addBehavior(dropBehavior)
    }
    
    func drop() {
        var frame = CGRect(origin: CGPointZero, size:dropSize)
        frame.origin.x = CGFloat.random(dropsPerRow) * dropSize.width
        
        let dropView = UIView(frame:frame)
        dropView.backgroundColor = UIColor.random
        
        dropBehavior.addDrop(dropView)
    }
}

private extension CGFloat {
    static func random(max: Int) -> CGFloat {
        return CGfloat(arc4random() % UInt32(max))
    }
}

Random Posts