Animate in Playground

Prototyping animations with Swift 4 and Xcode 9

Watch Video

High-fidelity prototyping tools have become incredibly popular over the past years with Framer, Flinto and Principle leading the way. What those tools allow you to do is to animate specific elements of your user interface.

If a picture is worth 1000 words, a prototype is worth 1000 meetings. —saying at Ideo

Prototyping is an essential skill to have. It has always been. If you look at how Apple build their products, you get a sense of how much they prototype everything, not just for their software but also for their hardware.

Prototyping in Swift

Personally, I consider prototyping a little differently. I don’t separate the process from actual development. I don’t like to add extra processes between design and development. After all, even if your prototype ends up looking great, you’re not actually reducing the build time. You may in fact add to it. Your developer may reject it, because understandably they have too much on their plate. They do 70% of the work, hence why iOS apps traditionally take 10x longer than Websites.

What if your prototype can actually be a real product?

Most designers today know how to build a Website. We can certainly apply the same attitude towards making apps, by learning Swift and Storyboard instead of CSS and Javascript.

Downloads for Design in Playground

To follow this tutorial, you’ll need Xcode 9 and the starting Playground file. You can download the Final Playground file to help you compare with your own progress.

Final Result

We’re building a simple transition from the Main screen to the Section screen. While Playground may not allow you to test on devices, the code that you create here can easily be transported to Xcode. We’re using Playground to get a firm grasp at some of the basic concepts of animating in Swift.

Open Playground File

We’re going to use the Playground file we created from Design in Playground. Basically, we have set up the basic UI elements from scratch, so that we can animate them here.

Animation Basics

iOS 11 and Material Design set new standards for animations. Now, we expect screens to transition gracefully from one to another. If you plan your animations carefully, you can transition multiple elements to end like the next screen’s beginning state.

How do you animate, you may ask? To understand some of the theory behind animation, I suggest heading to the Animations section. Basically, you need to set the beginning state and end state. The beginning state is our current view. The end state is the Section view.

Animation Basics

UIViewPropertyAnimator

Let’s look at the  UIViewPropertyAnimator class. This is just one of the many ways to animate in Swift. With this, you can move things, change colors and sizes and have complete control over the animation later. In the End state, we’ll make sure to align and resize the same elements, like the cardView, coverImageView, titleLabel and captionLabel.

Let’s type this after we set up backgroundImageView. Also, make sure that the Assistant Editor is open.

let animator = UIViewPropertyAnimator(duration: 0.7, dampingRatio: 0.7) {
// End state
}
animator.startAnimation()
UIViewPropertyAnimator

Duration

Set how long you want your animation to run. Typically, it would be somewhere between 0.5 seconds to 2 seconds. In this case, set the Duration to 0.7.

Damping Ratio

Spring and bouncing animations are used everywhere in iOS, so we’re going to replicate that with the Damping Ratio combined with the duration. The great thing about this technique is that it’s fully customizable. Let’s set the Damping Ratio to 0.7.

Animate Card

You can change the position, the size and other properties available to your UI object. New in Xcode 9, you can now animate the cornerRadius as well. In this animation, we want to expand the card so that it takes the full size of the screen. Make sure that the cardView has a white background.

Write this code in the animator, between the curly brackets.

cardView.frame = CGRect(x: 0, y: 0, width: 375, height: 667)
cardView.layer.cornerRadius = 0

Finally, outside of the curly brackets, we’re going to start the animation by referencing the animator that we created and use the startAnimation method.

Animate Card

Customizing the Animation

At this point, you can start customizing the springiness of the animation. If you wish to have less bouncing and overshooting, you can use duration 1, damping 1. If you want more, you can use duration 0.5, damping 0.5. The lesser the values, the more exaggerated the animation is going to be.

Animate the Cover

The Cover image will be animated similarly to the Card. It’ll take the full width, but the height will be limited to 420. Also, the rounded corners will be set to 0.

coverImageView.frame = CGRect(x: 0, y: 0, width: 375, height: 420)
coverImageView.layer.cornerRadius = 0

Animate the Labels

Since we have more space for our design, we’ll push the margins a little for our titleLabel and captionLabel. Instead of a 16 pt margin, we’ll use a 20 pt margin. Additionally, the captionLabel will be further down.

titleLabel.frame = CGRect(x: 20, y: 20, width: 374, height: 38)
captionLabel.frame = CGRect(x: 20, y: 370, width: 272, height: 40)

Description Label

In the Section screen, we’ll want to have a body text. Let’s create one right before we declared the animator.

let descriptionLabel = UILabel()
descriptionLabel.frame = CGRect(x: 20, y: 448, width: 335, height: 132)
descriptionLabel.text = "Three years ago, Apple completely revamped their design language for the modern users. It is now much simpler, allowing designers to focus on animation and function rather than intricate visual details."
descriptionLabel.textColor = .black

Add the descriptionLabel to cardView, right after captionLabel.

cardView.addSubview(descriptionLabel)

Number of Lines

Notice that our long paragraph of text is not showing everything. We’ll need to spread that over multiple lines of text. Let’s use the property numberOfLines.

descriptionLabel.numberOfLines = 10

Add Delay

As we preview the animation on the right, we can see that our animation is happening too fast at the beginning, so it’s really hard to follow what’s going on. Let’s add a slight 1 second delay at the beginning. This is completely optional and will need to be removed at the end of the tutorial when we use tap events.

animator.startAnimation(afterDelay: 1)

Fade In Animation

We can animate the opacity to give a nice fade in animation. Let’s set the descriptionLabel opacity to 0 at the beginning, and opacity to 1 at the end state.

Outside of the animator class, right below where you set up descriptionLabel.

descriptionLabel.alpha = 0

Inside the animator’s curly brackets.

descriptionLabel.alpha = 1

UIButton

Once we reach the Section screen, we need a way to go back to the Chapters screen after the animation is completed. Let’s create a close button.

let closeButton = UIButton()
closeButton.frame = CGRect(x: 328, y: 20, width: 28, height: 28)

For the background color, we want to get a 50% black. Let’s use Color Literal and set it in the color picker.

closeButton.backgroundColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.5)
closeButton.layer.cornerRadius = 14
closeButton.alpha = 0

One of the things that set buttons apart is that they have different states. As a result, we must use the setImage method and specify not only the image, but also for which button state. The normal state is the state that you see by default.

closeButton.setImage(#imageLiteral(resourceName: "Action-Close@2x.png"), for: .normal)

When a user presses the button, we need to fire an event by using addTarget. Here, we’re firing the function  closeButtonTapped.

closeButton.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside)

Close Tap Function

Since the Close Button is calling a function called  closeButtonTapped, we’ll create that function outside of the loadView main function. It’s right before the last closing curly bracket.

Notice that new in Swift 4, we must add @objc in front when calling from a #selector, which is what the error recommends us to do.

@objc func closeButtonTapped() {
// Do something when user taps on close button
}

Moving Objects to Class

When you start referencing objects in the closeButtonTapped function, you’ll notice that the autocompletion doesn’t kick in. That’s because those objects were initialized inside of loadView, which is not inherited by closeButtonTapped. We need to move those object declarations at the root level of MyViewController instead, which then will be inherited by all functions inside.

For every object that we initialized, we’ll move them right before the func loadView.

let cardView = UIView()
let coverImageView = UIImageView()
let titleLabel = UILabel()
let captionLabel = UILabel()
let descriptionLabel = UILabel()
let backgroundImageView = UIImageView()
let closeButton = UIButton()
Moving Objects to Class

Using Self in a Nested Class

Class-wide objects can get conflicted with newly declared objects. For example, if you create a second object cardView in loadView, how do you differentiate between the two cardView?

let cardView = UIView()

In that case, you’ll need to be explicit and use self to specifically target the Class-wide cardView. Now self.cardView references the Class-wide object, but just cardView references the newly created object.

self.cardView

However, inside a nested Class, you cannot reference a Class-wide object unless you use self. So, inside our animator Class, we’ll have to use self for every object referenced.

let animator = UIViewPropertyAnimator(duration: 0.7, dampingRatio: 0.7) {
self.cardView.frame = CGRect(x: 0, y: 0, width: 375, height: 667)
self.cardView.layer.cornerRadius = 0
self.coverImageView.frame = CGRect(x: 0, y: 0, width: 375, height: 420)
self.coverImageView.layer.cornerRadius = 0
self.titleLabel.frame = CGRect(x: 20, y: 20, width: 374, height: 38)
self.captionLabel.frame = CGRect(x: 20, y: 370, width: 272, height: 40)
self.descriptionLabel.alpha = 1
self.closeButton.alpha = 1
}
animator.startAnimation()

Dismiss Animation

Now that all our objects are accessible Class-wide, we can finally reference them in the closeButtonTapped function. Let’s create the animation. Essentially, we need to set all the states back to their original properties before the first animation starts. You almost copy and paste the same properties found in the objects, before animation.

@objc func closeButtonTapped() {
let animator = UIViewPropertyAnimator(duration: 0.7, dampingRatio: 0.7) {
self.cardView.frame = CGRect(x: 20, y: 255, width: 300, height: 250)
self.cardView.layer.cornerRadius = 14
self.titleLabel.frame = CGRect(x: 16, y: 16, width: 272, height: 38)
self.captionLabel.frame = CGRect(x: 16, y: 204, width: 272, height: 40)
self.descriptionLabel.alpha = 0
self.coverImageView.frame = CGRect(x: 0, y: 0, width: 300, height: 250)
self.coverImageView.layer.cornerRadius = 14
self.closeButton.alpha = 0
}
animator.startAnimation()
}

Tap Event

Currently, the first animation is triggered 1 second after the view is loaded. The real interaction that we’re looking for is to start the animation when the user taps on the cardView. For this to happen, we’ll need to use a UITapGestureRecognizer class, which will call a cardViewTapped function. Additionally, we’re going to attach the tap gesture to cardView, and set isUserInteractionEnabled to true.

let tap = UITapGestureRecognizer(target: self, action: #selector(cardViewTapped))
cardView.addGestureRecognizer(tap)
cardView.isUserInteractionEnabled = true

Present Tap Function

Let’s create the missing cardViewTapped function.

@objc func cardViewTapped() {
// Do something when user taps on cardView
}

Move the Animator Code

Let’s move all our animator code from loadView to the cardViewTapped function. Make sure to remove the startAnimation delay.

@objc func cardViewTapped() {
let animator = UIViewPropertyAnimator(duration: 0.7, dampingRatio: 0.7) {
self.cardView.frame = CGRect(x: 0, y: 0, width: 375, height: 667)
self.cardView.layer.cornerRadius = 0
self.coverImageView.frame = CGRect(x: 0, y: 0, width: 375, height: 420)
self.coverImageView.layer.cornerRadius = 0
self.titleLabel.frame = CGRect(x: 20, y: 20, width: 374, height: 38)
self.captionLabel.frame = CGRect(x: 20, y: 370, width: 272, height: 40)
self.descriptionLabel.alpha = 1
self.closeButton.alpha = 1
}
animator.startAnimation()
}

Conclusion

Using these techniques, you can create a fairly complex animation prototype using real production code that can be used by you and your developer.

Tweet "Animate in Playground - Prototyping animations with Swift 4 and Xcode 9 by @MengTo"Tweet

Chapter 3: 19 Sections