Swift – Have fun with GravityBehavior and DynamicAnimator

Today a funny 😎 way to use device motion to move using the gravity the objects on your view controller, like this:


Setup

For this demo the interface is very simple: add lots of default view objects, like buttons, labels, switches, etc… 🪄

And connect everything via IBOutlets:

    @IBOutlet weak var label1: UILabel!
    @IBOutlet weak var label2: UILabel!
    @IBOutlet weak var button1: UIButton!
    @IBOutlet weak var button2: UIButton!
    @IBOutlet weak var switch1: UISwitch!
    @IBOutlet weak var switch2: UISwitch!

Now we need to add our code. First of all import the CoreMotion sdk:

import CoreMotion

next create the classes you need to create this effects: UIDynamicAnimator, UIGravityBehaviour and CMMotionManager.

var animator: UIDynamicAnimator!
var gravity: UIGravityBehavior!
var motion: CMMotionManager!

var queue: OperationQueue! // used for updating UI objects with motion

and initialize in viewDidLoad:

queue = OperationQueue.current
animator = UIDynamicAnimator(referenceView: self.view)
gravity = UIGravityBehavior(items: [label1, label2, button1, button2, switch1, switch2])
motion = CMMotionManager()

What happen here is that we have created a background queue, we have attached the animator the the view container, attached to the gravity object the elements you want to move and instantiate a motion manager.

The last step is to add to the animator the gravity behavior:

animator.addBehaviour(gravity)

Your code should be like this, you can build and run ▶️ on a physical device to see the effect:

override func viewDidLoad() {
    super.viewDidLoad()
        
    queue = OperationQueue.current
    animator = UIDynamicAnimator(referenceView: self.view)
    gravity = UIGravityBehavior(items: [label1, label2, button1, button2, switch1, switch2])
    motion = CMMotionManager()
        
    animator.addBehavior(gravity)
}

Cool, but without borders, the UI components fall outside your device… 😱😱😱


Add boundaries 🤟:

In order to avoid the falling out of objects you need to define a border and the objects that collides.

// the objects that responds to collision
let collision = UICollisionBehavior(items: [label1, label2, button1, button2, switch1, switch2])

// the boundary AKA the borders. In this case if the full ViewController view
collision.addBoundary(withIdentifier: "borders" as NSCopying, for: UIBezierPath(rect: self.view.frame))

Now you can add the CollisionBehaviour to your animation:

animator.addBehavior(collision)

Move objects using device motion 🏎:

You can use CoreMotion SDK to detect device movements and move objects around respecting always the gravity.

To do this, you can implement the startDeviceMotionUpdates of the motion manager:

motion.startDeviceMotionUpdates(to: queue) { motion, error in
    guard let motion = motion else { return }

    let grav: CMAcceleration = motion.gravity
    let x = CGFloat(grav.x)
    let y = CGFloat(grav.y)
    var p = CGPoint(x: x, y: y)

    if let orientation = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.windowScene?.interfaceOrientation {
        if orientation == .landscapeLeft {
            let t = p.x
            p.x = 0 - p.y
            p.y = t
        } else if orientation == .landscapeRight {
            let t = p.x
            p.x = p.y
            p.y = 0 - t
        } else if orientation == .portraitUpsideDown {
            p.x *= -1
            p.y *= -1
        }
    }

    let v = CGVector(dx: p.x, dy: 0 - p.y)
    self.gravity.gravityDirection = v
}

🎉 Your code is now complete!

Build and run ▶️ to see the effects like the first attached video!

Too lazy for following all the steps in this tutorial? You can import directly the Swift Package into Xcode: https://github.com/elpsk/Gravity

Have fun and enjoy gravity! ❤️

Alberto Pasca