Contents

Hide contents

iOS Animations by Tutorials

Section IV: Layer Animations

Section 4: 9 chapters
Show chapters Hide chapters

27 Intermediate 3D Animations
Written by Marin Todorov

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

In the previous chapter, you learned that applying perspective to a single view isn’t a complicated task; in fact, once you know the secret of m34 and camera distance you can create all kinds of 3D animations.

This chapter builds on what you’ve already learned and shows you how to create convincing 3D animations with more than one view.

The starter project for this chapter is a simple hurricane image gallery. By the end of this chapter, you’ll have a 3D effect to get an overall view of the images in the gallery:

You’ll be able to tap on a photo to bring it full screen, and tapping the top right button fans all the images open again — with a cool animation to take you between the two states, of course!

Ready to get started? Hang on to your hat and prepare to get blown away by this “stormy” project!

Exploring the Starter Project

Open the starter project from the Resources folder for this chapter; build and run it to see what you have to start with:

All you have is a blank screen with two bar buttons on top: the left one shows the NASA image credits and the right one invokes the method that shows or hides the gallery as appropriate.

First, you’ll need to display all images on the screen and set them up so they’re ready for your “fan” animation.

Open ViewController.swift and inspect the class code. You’ll see an array called images; this array contains some slightly customized image views. The ImageViewCard class inherits from UIImageView and adds a string property title to hold the hurricane title, and a property called didSelect so you can easily set a tap handler on the image.

Your first task is to add all images to the view controller’s view. Add the following code to the end of viewDidAppear(_:):

for image in images {
  image.layer.anchorPoint.y = 0.0
  image.frame = view.bounds
  
  view.addSubview(image)
}

In the code above, you loop over all images, set each image’s anchor point to 0.0 on the y-axis and resize each image so it takes up the full screen. When that’s done, you add each image to view.

Setting the anchor point lets the images rotate around their upper edge rather than the default of the center, as illustrated below:

This will make it a lot easer to fan out the images in 3D space.

Build and run your project to see what you’ve achieved so far:

Hmm — you only see the last image you added to view. That’s because all images have the same frame, so you only see the last image you added: Hurricane Irene.

To make it more obvious which hurricane image is being displayed, add the following line at the end of viewDidAppear(_:):

navigationItem.title = images.last?.title

This code takes the name of the last hurricane image in the collection and sets it as the navigation title of the view controller, like so.

Build and run again to familiarize yourself with Irene:

Notice that you didn’t set any perspective transforms on the images; you’re going to set a perspective directly on the view controller’s view instead.

In the previous chapter you adjusted the transform property on a single view and then rotated it in 3D space. But since your current project has more individual views that you’d care to manipulate in 3D, you can set the perspective of their parent view instead to save yourself a bunch of work.

Add the following code to viewDidAppear(_:):

var perspective = CATransform3DIdentity
perspective.m34 = -1.0 / 250.0
view.layer.sublayerTransform = perspective

Here you use the layer property sublayerTransform to set the perspective of all sublayers of the view controller’s layer. The sublayer transform is then combined with each individual layer’s own transform.

This lets you focus on managing the rotation or translation of your subviews without having to worry about perspective. You’ll see how this works in more detail in the next section.

Transforming the Gallery

toggleGallery(_:) is hooked up to the Browse bar button on the right and is where you’ll apply your 3D transform to the four images.

var imageYOffset: CGFloat = 50.0
for subview in view.subviews {
  guard let image = subview as? ImageViewCard else {
    continue
  }

  // more code here
}
var imageTransform = CATransform3DIdentity

// 1
imageTransform = CATransform3DTranslate(  
  imageTransform, 0.0, imageYOffset, 0.0)

// 2
imageTransform = CATransform3DScale(  
  imageTransform, 0.95, 0.6, 1.0)

// 3
imageTransform = CATransform3DRotate(  
  imageTransform, .pi / 8, -1.0, 0.0, 0.0)

image.layer.transform = imageTransform

imageYOffset += view.frame.height / CGFloat(images.count)

Animating the Gallery

Find the following line in toggleGallery(_:) where you set transform on each image:

image.layer.transform = imageTransform
let animation = CABasicAnimation(keyPath: "transform")
animation.fromValue = NSValue(caTransform3D:
  image.layer.transform)
animation.toValue = NSValue(caTransform3D: imageTransform)
animation.duration = 0.33
image.layer.add(animation, forKey: nil)

Bringing an Image to the Front

In this final section, you’ll add a bit of interactivity to the image gallery: tapping an image will make it jump in front of the other images so that the user can get a better look at it ImageViewCard already features a closure expression property named didSelect; this fires when the user taps on the image and receives the tapped image view as an input parameter.

image.didSelect = selectImage
func selectImage(selectedImage: ImageViewCard) {
  for subview in view.subviews {
    guard let image = subview as? ImageViewCard else {
      continue
    }
    if image === selectedImage {
      //selected image
    } else {
      //any other image
    }
  }
}
UIView.animate(
  withDuration: 0.33, 
  delay: 0.0, 
  options: .curveEaseIn, 
  animations: {
    image.alpha = 0.0
  }, 
  completion: { _ in
    image.alpha = 1.0
    image.layer.transform = CATransform3DIdentity
  })

UIView.animate(
  withDuration: 0.33, 
  delay: 0.0, 
  options: .curveEaseIn, 
  animations: {
    image.layer.transform = CATransform3DIdentity
  }, 
  completion: { _ in
    self.view.bringSubviewToFront(image)
  })
self.navigationItem.title = selectedImage.title

Key Points

  • When you set the 3D perspective via the m34 property and use the resulting transform to set sublayerTransform on a layer, all of its sub-layers can be animated in 3D space.
  • You can combine CATransform3D layer animations with UIKit view animations and let Core Animation automatically combine and render them on screen.

Challenges

Challenge 1: Toggle the Gallery with the Browse Button

Right now the user must choose an image from the gallery once they open it. In this challenge, you’ll make the Browse button work like a toggle to close the gallery view as well. Add a new property to ViewController named isGalleryOpen and set its initial value to false. You need to update the value of this property in couple of places in the code:

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2022 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.

Unlock Now