Chapters

Hide chapters

SwiftUI by Tutorials

Second Edition · iOS 13 · Swift 5.2 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section II: Building Blocks of SwiftUI

Section 2: 6 chapters
Show chapters Hide chapters

14. Animations
Written by Bill Morefield

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

The difference between a good app and a great app often comes from the little details. Using the right animations at the right places can delight users and make your app stand out in the crowded App Store.

Animations can make your app more fun to use, and they can play a powerful role in drawing the user’s attention in certain areas. Good animations make your app more appealing and easier to use.

Animation in SwiftUI is much simpler than animation in AppKit or UIKit. SwiftUI animations are higher-level abstractions that handle all the tedious work for you. If you have experience with animations in Apple platforms, a lot of this chapter will seem familiar. You’ll find it a lot less effort to produce animations in your app. You can combine or overlap animations and interrupt them without care. Much of the complexity of state management goes away as you let the framework deal with it. This frees you up to make great animations instead of handling edge cases and complexity.

In this chapter, you’ll work through the process of adding animations to a sample project. Time to get the screen shaking!

Animating state changes

First, open the starter project for this chapter. Build and run the project in XCode 11 or greater, and you’ll see an app that shows flight information for an airport. The lists provide flyers with the time and the gate where the flight will leave or arrive.

Note: Unfortunately, it’s challenging to show animations on a printed page. You’ll need to work through this chapter using preview, the simulator or on a device.

Adding animation

To start, open FlightBoardInformation.swift and look for the following code:

Button(action: {
 self.showDetails.toggle()
}) {
  HStack {
 if showDetails {
      Text("Hide Details")
      Spacer()
      Image(systemName: "chevron.up.square")
    } else {
      Text("Show Details")
      Spacer()
      Image(systemName: "chevron.down.square")
    }
  }
}
Button(action: {
 self.showDetails.toggle()
}) {
  HStack {
    Text(showDetails ? "Hide Details" : "Show Details")
    Spacer()
    Image(systemName: "chevron.up.square")
      .rotationEffect(.degrees(showDetails ? 0 : 180))
  }
}
Image(systemName: "chevron.up.square")
  .rotationEffect(.degrees(showDetails ? 0 : 180))
  .animation(.default)
.rotationEffect(.degrees(showDetails ? 0 : 180))

Animation types

So far, you’ve worked with a single type of animation: the default animation. SwiftUI provides more animation types. The differences can be subtle and hard to see on the small chevron. To make the changes easier to notice, you’ll add another animation to this view.

Animating the Details view

Stay in FlightBoardInformation.swift. The code to show the details for the flight looks like:

if showDetails {
  FlightDetails(flight: flight)
}
FlightDetails(flight: flight)
  .offset(x: showDetails ? 0 : -UIScreen.main.bounds.width)

Default animation

Now, you’ll change the code to add an animation. After the offset, add:

.animation(.default)

Eased animations

Eased animations might be the most common in apps. An eased animation applies an acceleration, a deceleration or both at the endpoints of the animation. They generally look more natural since it’s impossible for something to instantaneously change the speed in the real world. The animation reflects the acceleration or deceleration of real-world movement.

.animation(.easeOut)

.animation(.easeOut(duration: 2))

timingCurve(cx0, cy0, cx1, cy1)
tutopwXaqzo(ly9, dz9, mx7, wn6)

Spring animations

Eased animations always transition between the start and end states in a single direction. They also never pass either end state. The other category of SwiftUI animations let you add a bit of bounce at the end of the state change. The physical model for this type of animation gives it the name: a spring.

Why a spring makes a useful animation

Springs resist stretching and compression. The greater the stretch or compression of the spring, the more resistance the spring presents. Imagine you take a weight and attach it to one end of a spring. Then you attach the other end of the spring to a fixed point and let the spring drop vertically with the weight at the bottom.

Creating spring animations

Now that you have a bit more understanding of how a spring animation works, you’ll see how these parameters affect your animation. The traditional spring animation you create with the interpolatingSpring(mass:stiffness:damping:initialVelocity:) method uses these parameters. Change the animation line to:

animation(.interpolatingSpring(mass: 1, stiffness: 100,
                                damping: 10, initialVelocity: 0))
.animation(.spring(response: 0.55, dampingFraction: 0.45,
                   blendDuration: 0))

Removing and combining animations

There are times that you may apply modifications to a view, but you only want to animate some of them. You do this by passing a nil to the animation() method.

Button(action: {
 self.showDetails.toggle()
}) {
  HStack {
    Text(showDetails ? "Hide Details" : "Show Details")
    Spacer()
    Image(systemName: "chevron.up.square")
      .scaleEffect(showDetails ? 2 : 1)
      .rotationEffect(.degrees(showDetails ? 0 : 180))
      .animation(.easeInOut)
  }
}
Button(action: {
 self.showDetails.toggle()
}) {
  HStack {
    Text(showDetails ? "Hide Details" : "Show Details")
    Spacer()
    Image(systemName: "chevron.up.square")
      .scaleEffect(showDetails ? 2 : 1)
      .animation(.spring(response: 0.55, dampingFraction: 0.45,
                         blendDuration: 0))
      .rotationEffect(.degrees(showDetails ? 0 : 180))
      .animation(.easeInOut)
  }
}

Animating from state changes

To this point in the chapter, you’ve applied animations at the element of the view that changed. You can also apply the animation at the point where the state change occurs. When doing so, the animation applies to all changes that occur because of the state change. Modify the code for the Button and details view to:

Button(action: {
  withAnimation(.default) {
 self.showDetails.toggle()
  }
}) {
  HStack {
    Text(showDetails ? "Hide Details" : "Show Details")
    Spacer()
    Image(systemName: "chevron.up.square")
      .scaleEffect(showDetails ? 2 : 1)
      .rotationEffect(.degrees(showDetails ? 0 : 180))
  }
}
FlightDetails(flight: flight)
  .offset(x: showDetails ? 0 : -UIScreen.main.bounds.width)

Adjusting animations

There are a few instance methods common to all animations. These methods let you delay an animation, change the speed of the animation and repeat the animation.

Delay

The delay() method allows you to specify a time in seconds before the animation occurs. Change the spring animation added in the previous section so that the flight details view reads:

FlightDetails(flight: flight)
  .offset(x: showDetails ? 0 : -UIScreen.main.bounds.width)
  .animation(Animation.spring().delay(1))

Speed

You use the .speed() method to change the speed of the animation. This modifier multiplies the speed of the animation by the value you provide. If you have an animation that originally takes two seconds and apply .speed(0.5), it will occur at half of the original speed and therefore take twice as long to complete. This change causes the animation to last four seconds. This modifier can be useful to adjust the time of an animation lacking a direct time element such as default and spring animations. It also works well to match the times of concurrent animations.

 .animation(Animation.spring().speed(2))

Repeating animations

To repeat an animation you call .repeatCount(_:autoreverses:) with the number of times the animation will repeat. You can also control if the animation reverses before repeating. Without reversing, the animation will return to the initial state instantaneously. With reversing, the animation goes back to the initial state before repeating. The repeatForever(autoreverses:) loops the animation forever, but you still specify if the animation should reverse before repeating.

.animation(Animation.spring()
           .repeatCount(2, autoreverses: false))
.animation(.spring())

Extracting animations from the view

To this point, you’ve defined animations directly within the view. For exploring and learning, that works well. In real apps, it’s easier to maintain code when you keep different elements of your code separate. Animation can be defined outside the view where you can also reuse them. In FlightBoardInformation.swift, add the following code above the body structure:

var flightDetailAnimation : Animation {
  Animation.easeInOut
}
withAnimation(self.flightDetailAnimation) {

Animating view transitions

Note: Transitions sometimes render incorrectly in the preview. If you’re not seeing what you expect, then try running the app in the simulator or on a device.

if showDetails {
  FlightDetails(flight: flight)
    .transition(.slide)
}
Button(action: {
  withAnimation {
    self.showDetails.toggle()
  }
}) {
.animation(flightDetailAnimation)

View transition types

You used a slide transition above. The slide transition slides a view from the leading edge and leaves by sliding off the trailing edge. There are several other transition animations you can use.

.transition(.move(edge: .bottom))

Extracting transitions from the view

You can extract your transitions from the view as you did with animations. You do not add this at the struct level as with an animation but at the file scope. At the top of FlightBoardInformation.swift add the following:

extension AnyTransition {
  static var flightDetailsTransition: AnyTransition {
    AnyTransition.slide
  }
}
if showDetails {
  FlightDetails(flight: flight)
    .transition(.flightDetailsTransition)
}

Async transitions

SwiftUI lets you specify separate transitions when adding and removing a view. Change the static property to:

extension AnyTransition {
  static var flightDetailsTransition: AnyTransition {
    let insertion = AnyTransition.move(edge: .trailing)
      .combined(with: .opacity)
    let removal = AnyTransition.scale(scale: 0.0)
      .combined(with: .opacity)
    return .asymmetric(insertion: insertion, removal: removal)
  }
}

Challenge

Challenge: Changing the flight details view

Change the final project for this chapter so that the flight details view slides in from the leading edge. When you hide it, make the view slide to the bottom and fade away. Also, change the button text transition. When added, the button text view should move from the leading edge. When removed, the button text view should vanish using a scale transition.

Key points

  • Don’t use animations simply for the sake of doing so. Have a purpose for each animation.
  • Keep animations between 0.25 and 1.0 second in length. Shorter animations are often not noticeable. Longer animations risk annoying your user wanting to get something done.
  • Keep animations consistent within an app and with platform usage.
  • Animations should be optional. Respect accessibility settings to reduce or eliminate application animations.
  • Make sure animations are smooth and flow from one state to another.
  • Animations can make a huge difference in an app if used wisely.

Where to go from here?

This chapter focused on how to create animations and transitions, but not why and when to use them. A good starting point for UI related questions on Apple platforms is the Human Interface Guidelines here: https://developer.apple.com/design/human-interface-guidelines/. The WWDC 2018 session, Designing Fluid Interfaces, also goes into detail on gestures and motion in apps, which you can see, here: https://developer.apple.com/videos/play/wwdc2018/803.

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.
© 2024 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 Personal Plan.

Unlock now