Today we will take a look at a couple of quite simple examples, learn about pros and cons of what Apple has to offer in terms of animation and get ourselves familiar with some third-party frameworks I personally find to be quite useful for handling animation. I will also show you how to use UIKit Dynamics and Motion Effects available starting from iOS 7.
UIKit Dynamics is a physical engine that allows you to change elements by adding behaviors
, such as gravity
, springs
, forces
, etc. And the engine takes care of everything else.
Motion Effects — allows you to create parallax effects that you, for example, see on the Home screen when slightly tilting your phone. It looks like Home screen is slightly moving in one direction while icons are moving into another. It creates an effect of depth you can easily implement in your own apps.
UIKit Dynamics
Let’s add UIView
to the storyboard, make it gray and bind it using constraints. You’ve got to be attentive here since sometimes physical engine doesn’t handle this kind of binding well and you need to simply add an element somewhere in viewDidLoad()
and specify its size using frame
.
Next add several properties to ViewController
class:
var animator: UIDynamicAnimator! var gravity: UIGravityBehavior! var collision: UICollisionBehavior!
Now we initiate this properties in viewDidLoad()
:
animator = UIDynamicAnimator(referenceView: view) gravity = UIGravityBehavior(items:[box]) animator.addBehavior(gravity)
Start your animation in simulator to see how a grey square is falling somewhere behind the screen. Kinda fun but not much. Add UICollisionBehavior
and assign true
to translatesReferenceBoundsIntoBoundary
. This property is responsible for system boundaries and if its value is true
, views of your ViewController
will be used as boundaries. Don’t forget to add the new behavior to animator
object — it is responsible for all the calculations:
//add collision collision = UICollisionBehavior(items:[box]) collision.translatesReferenceBoundsIntoBoundary =true animator.addBehavior(collision)
Now the grey square is hitting the lower part of the screen — awesome!
Let’s go further and add a barrier. If barrier is added through the storyboard, the engine can’t calculate it precisely so we will add it manually:
//add barrier let barrier = UIView(frame: CGRect(x:0.0, y:400.0, width:200.0, height:20.0)) barrier.backgroundColor = UIColor.blackColor() view.addSubview(barrier)
The barrier is added and we need to add a collision
:
let rightEdge = CGPoint(x: barrier.frame.origin.x + barrier.frame.size.width, y: barrier.frame.origin.y) collision.addBoundaryWithIdentifier("barrier", fromPoint: barrier.frame.origin, toPoint: rightEdge)
Now everything is fine, the system works, the cube falls and collides. Let’s go further and add a little bit of elasticity to the cube:
let itemBehavior = UIDynamicItemBehavior(items:[box]) itemBehavior.elasticity =0.6 animator.addBehavior(itemBehavior)
Apart from elasticity
we have a couple of other properties up our sleeve to play with:
elasticity
friction
density
resistance
angularResistance
We can also monitor the moment when the cube is meeting obstacles. We’re signing controller as a delegate:
collision.collisionDelegate = self
And implement the following method:
extension ViewController: UICollisionBehaviorDelegate { func collisionBehavior(behavior: UICollisionBehavior, beganContactForItem item: UIDynamicItem, withBoundaryIdentifier identifier:NSCopying?, atPoint p: CGPoint){ //change box color, when it collideif let itemView = item as? UIView { itemView.backgroundColor = UIColor.redColor()}}}
And after each collision we will be changing cube’s color to red:
Now let’s talk about what’s happening behind the scenes. Every behavior
has action
property. We can add a block of code our animation will execute during each step (about 400 milliseconds). Let’s add:
//add trails collision.action ={ self.box.backgroundColor = UIColor.clearColor() let newBox = UIView(frame: self.box.frame) newBox.layer.borderWidth =1.0 newBox.layer.borderColor = UIColor.lightGrayColor().CGColor newBox.transform = self.box.transform newBox.center = self.box.center self.view.addSubview(newBox)}
On every step of the animation a new cube will be added that is using coordinates of our grey cube while our “main character” is hidden so that we can enjoy the effect we’ve created :)
Here’s the final result:
Motion Effects
For the next example you’ll need a real device. Simulator won’t do — I’ve checked it myself: you can tilt your head or your monitor — it doesn’t help, I swear XD
Here’s our storyboard:
Pay attention to constraints for backImView
as well:
It is done so that when backImView
(image with the sky) shifts, it won’t show a white color of the view
controller. And here’s the code:
override func viewDidLoad(){ super.viewDidLoad() addMotionEffectToView(backImView, magnitude:50.0) addMotionEffectToView(frontImView, magnitude:-20.0)} //MARK: Helpers func addMotionEffectToView(view: UIView, magnitude: CGFloat){ let xMotion = UIInterpolatingMotionEffect(keyPath:"center.x", type: .TiltAlongHorizontalAxis) xMotion.minimumRelativeValue =-magnitude xMotion.maximumRelativeValue = magnitude let yMotion = UIInterpolatingMotionEffect(keyPath:"center.y", type: .TiltAlongVerticalAxis) yMotion.minimumRelativeValue =-magnitude yMotion.maximumRelativeValue = magnitude let group = UIMotionEffectGroup() group.motionEffects =[xMotion, yMotion] view.addMotionEffect(group)}
We will add motion effect to the back (backImView
) and front view (frontImView
). We aren’t interested in midImView
here, it stays static.
Create a supplementary method called addMotionEffectToView
and initialize it with your UIImageView
with additional properties — and that’s it! Enjoy your parallax effect :)
Animated Popover with Transparent Background
The first step is to open the ViewController. The XIB we will be using in this example is called RateMeViewController
and to open it, we run:
let vc = RateMeViewController(nibName:"RateMeViewController", bundle:nil)//// Make changes to vc (for example to modalPresentationStyle)// self.present(vc, animated:true)
This would open the ViewController without any fancy animations, but we want more. So, to make the UI popover transparent, we need to set the modalPresentationStyle
to
.overFullScreen.
To avoid the blurry background swiping in from the bottom, we set the
modalTransitionStyle
to .crossDisolve.The result looks like this:
let vc = RateMeViewController(nibName:"RateMeViewController", bundle:nil) vc.modalPresentationStyle = .overFullScreen vc.modalTransitionStyle = .crossDissolve self.present(vc, animated:true)
Before we can animate the popover view (the actual Rate window) into the screen, we first need to hide it. As the view is probably somehow constrained into the UIViewController, we will be using the bottom constraint to hide and make the View appear. To make a change to the bottom constraint, we need to reference it into our ViewController. Find the bottom constraint.
Tap on the constraint (to make it bold) and CTRL drag it into your UIViewController. The constraint type should be NSLayoutConstraint . Let’s name it bottomConstraint.
Next, we need to set the constraint to a value that hides the popover view. As the popover view is 350 pixels in height, moving the view to -500 pixels should definitely move it out of the screen. So, we add the following line to viewDidLoad()
:
bottomConstraint.constant =-500
That’s all for hiding it.
Next, to make it appear again, we will be using the power of viewDidAppear
, called once the Controller has finished animating in. We need to reset the bottomConstraint of the popover view back to what it was initially (50 in my case). Using bottomConstraint.constant = 50
would do the trick, but I promised an awesome animation. So we need the following block for the magic
UIView.animate(withDuration:0.6, delay:0, usingSpringWithDamping:0.7, initialSpringVelocity:0.3, options: .curveEaseOut, animations:{ self.bottomConstraint.constant =50 self.view.layoutIfNeeded()})
A short explanation of what is happening here. We are using the UIView animation function, which contains all animations in a block of code. As only changing the constant to a different value wouldn’t run the animation, we also need to run self.view.layoutIfNeeded()
. The animation setup also contains a few parameters. First, we define a duration.
And that's done!
Cheetah
Personally, I like Cheetah framework. It is amazingly compact in terms of code and allows you to receive quite good results.
Open Cheetah_project. In storyboard you have one button we’re going to click at all times :) Its code is available below:
@IBAction func buttonAction(sender: AnyObject){ box.cheetah .scale(1.5) .duration(1.0) .spring() .cornerRadius(20.0) .wait() //reset .scale(1.0) .cornerRadius(0.0) .duration(0.5) .run()}
After writing a couple of code lines we get animation that would take us much more time if we used standard methods. And, of course, the more code we have, the harder it is to change.
I would also recommend you to check out Spring framework. Developers have prepared a very nice app you can use to explore capabilities of their product and even take a look at the code of the effect you’re interested in. DCAnimationKit is a very simple framework with a lot of ready-made decisions that is also worth checking out.
Don’t forget that these are ready-made solutions written with low-level C functions, such as CGAffineTransformMakeRotation()
, and if you’re serious about animation, you’ve got to deal with them.
Or, at least, you’ve got to be familiar with higher level methods, such as:
UIView.animateWithDuration
UIView.animateKeyframesWithDuration
UIView.addKeyframeWithRelativeStartTime
And you should understand CABasicAnimation()
, CAPropertyAnimation()
, etc. as well.
But if you just need to quickly implement a certain function, then one of the more “lazy solutions” described above is what you actually need.
Need MVP development, iOS and Android apps or prototyping? Check out our portfolio and make an order today!