Animations: Bringing the Host Passport to Life on iOS

Anne Lu
The Airbnb Tech Blog
9 min readMay 7, 2024

--

How Airbnb enabled hosts and guests to connect and introduce themselves through the Host Passport.

By: Anne Lu

Introduction

In May 2023 we introduced the Host Passport as part of our Summer Release. We wanted to give Hosts a way to introduce themselves, and start building a more personal connection with their guests. To that end, we created the Host Passport, which appears in the bottom corner of each Private Room listing result with a photo of the Host on the cover. Guests can tap it to fully open the Host Passport and learn more about the Host and get a sense for the real live person they would be staying with.

The Passport animation

The Host Passport offers Hosts a way to introduce themselves and set guest expectations, and allows guests to quickly start discovering who they could be sharing a space with.

Delivering this animation with high pixel accuracy, fluidity, high performance, and a spark of delight led us to encounter and solve many novel technical issues unique to each client platform that we support. While the Host Passport appears in the web, Android, and iOS apps, this article focuses specifically on the iOS implementation.

Implementing the Host Passport on iOS

While we’ve almost entirely switched over to SwiftUI when it comes to building new components and screens in our app, we opted to use UIKit for the passport animation. We did this for a couple of reasons. Firstly, at the time of this writing, SwiftUI does not have APIs supporting custom transitions and navigation patterns, so our screen navigation and transition layer remains in UIKit. And secondly, while keyframe timing was introduced for SwiftUI animations with iOS 17, our version support extended back to iOS 15 at the time of release.

UIKit provides a ready-to-use framework that enables the development of smooth, polished animations. Combined with our in-house declarative transition framework, we were starting with a solid foundation that we could leverage to create complex animations. The work lay in bridging the gap between established patterns and our novel requirements; while we were already experienced in creating delightful two-dimensional animations, three-dimensional animation was uncharted territory.

The complexity of this animation lies in its many moving parts. The challenge lies not in animating single properties, but rather coordinating many for a cohesive effect that is not only functional, but also delightful.

The Passport

The anchor point

The anchor point is a property of a view’s bounds, defaulting to the relative [0.5, 0.5], or exact center. Rotation animations rotate around this anchor point, so by default, views rotate around their midpoints, which gives a card rotating effect rather than a page flipping one.

A rounded rectangle rotating around a center anchor point

To achieve the desired page rotation, we faced a dilemma with the anchor point. Shifting the anchor point to [0, 0.5] in the coordinate space could accomplish the page turning effect by shifting it to the view’s leading side, but that approach had the potential to disrupt other aspects of the animation — this is because the anchor point is used not only as the basis for rotation, but for other transforms, such as scaling and translation. Altering the anchor point for three-dimensional rotation has a knock-on effect on these other transforms, causing unexpected side effects we would then have to work around.

With this in mind, we used an alternative approach: instead of directly manipulating the anchor point, we created transparent views where the visible content occupied only half of the space. As the rotation occurs, the view seemingly rotates around the left edge, while still leveraging the default center point for the actual rotation.

With this, we are able to animate our book page rotation without introducing complications to the other transforms. See the example below, where there is a border added around the entire view, including the transparent part, to show its actual size.

A rounded rectangle with a red half and a transparent half rotating around a center anchor point

Page composition

With the rotation solved, we next had to think about how to compose the view to look like a book. We ended up accomplishing that effect by using a compound view. At a basic level, the booklet is composed of a front page and two inside pages. That meant we needed three separate views:

A view that rotates like a folding page with View 1 (front), View 2 (inner left) and View 3 (inner right)

By stitching them together, we create the Passport booklet.

To create the impression of a page flip, we needed to employ another trick; while real life pages have a front and a back, the same is not true of a view. Therefore, in order to make it look like a page turning, we timed it so that during the page turn, the front view is swapped for the back view at the exact point when the page is completely orthogonal to the viewer’s perspective. This creates the illusion of a front and a back. Et voila!

A rotating rounded rectangle that changes from blue to red when it is perpendicular to the screen

Integrating with our Animation Framework

At this point, we had a passport booklet with the ability to flip open in three dimensions.

In order to accomplish the next step in the animation we needed to integrate our book animation with our declarative animation framework, which handled transitioning the animating passport from the listing results view onto the modal view. Our animation framework allows us to perform a shared element transition, where a view animates seamlessly between two separate screens, in just a few lines of code.

First, we created a transition definition that describes the type of animation we wanted:

let passportTransition: TransitionDefinition = [
SharedElementIdentifiers.Passport.passport(listingId): .sharedElement
]

Next, we attached these identifiers to the source view (the passport in the listing search results) and the destination (the open passport card in the context sheet.) We set the transition definition on the modal presentation, and from there, the framework created the animation that moved our passport view from its starting location in the listing results to its final location in the modal.

Under typical circumstances, our framework captures a “snapshot” of the view by rendering it as a static image. The snapshot is then animated from the initial position to the final position, while the original source and destination views are hidden during the animation. This allows us to play the animation of the view moving from one place to another in a performant way while keeping the view hierarchy intact.

In our case, however, a static snapshot didn’t have the functionality we needed, which was the ability to play the page flip animation alongside the shared element transition. Therefore, we created a custom snapshot that we used in place of the default static snapshot. This custom snapshot was a copy of the view that did have animation capabilities, that we then triggered to play alongside the animated transition so that they would be perfectly in sync. Enter UIViewPropertyAnimator: a class that allows us to define animation blocks and dynamically control their playback. It provides the flexibility to start, stop, or modify animations in real-time.

It neatly encapsulated our animations within a single object, which could then be passed along to our animation framework. As our framework handled the screen to screen transition, it triggered the custom animation to play in sync with that transition.

Timing

It isn’t only where a view moves that determines realism, but also very importantly when. The passport opens in the span of a moment, but the simple elegance belies the complexity underneath.

On a closer look, our animation consists of many synchronized individual animations. The passport grows in size, moves along the x and y axis, rotates its pages in 3D space, and shadows move to simulate light and movement. To get things just right, we use a separate timing curve for each property.

But we need even more specificity than that; our design calls for these to start and stop at different points along the animation duration. For that, we time specific events to relative points within the timing curve via keyframes. To expand on our earlier example, here is our animator with keyframes set.

let animator = UIViewPropertyAnimator(duration: 2.0, curve: .easeInOut) {

// Enable keyframe animations, inheriting the duration of the
// parent property animator
UIView.animateKeyframes(withDuration: 0, delay: 0) {

// At the start of the animation, translate the view 100 pixels downwards
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
cardView.transform = CGAffineTransform(translationX: 0, y: 100)
}

// At the halfway point, flip the color to coincide with the turning
// point of our view.
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0) {
cardView.backgroundColor = .red
}

// Return to original position
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
cardView.transform = .identity
}
}
}

animator.startAnimation()

Next, let’s take a closer look at spring timings and their unique characteristics. When creating animations, we have the option of different types of easing functions for a naturalistic feel.

Easing functions like linear and cubic are common timing curves, as depicted in the graphs below. They give us the ability to specify the speed of our animation over time.

Linear

A linear graph (left), a rounded rectangle translating on the Y axis with a linear timing curve (right)

Cubic

A cubic graph (left), a rounded rectangle translating on the Y axis with a cubic timing curve (right)

For a more advanced timing, we have the option to use springs. Spring functions are grounded in principles from real-life physics, giving animations a realistic sense of elasticity and bounce.

Spring

A spring graph (left), a rounded rectangle translating along the Y axis with a spring timing curve (right)

However, unlike linear and cubic functions, the end time of a spring animation is not always clear-cut. Spring timings have a long tail in which the oscillations gradually diminish in size, while theoretically never reaching rest. Practically speaking, this means that animations using spring timings can have extended durations, which can make them feel less responsive.

Additionally, our spring timing had to be tuned to work with the overall transition animation timing. As mentioned above, our animation framework uses a snapshot during the animated transition before switching the snapshot for the destination view at the conclusion of the animation. This means that the snapshot and the destination view must be in the same position at the time of the switch, to ensure a seamless changeover.

Below is an example of a mismatch in sync: on the left, the passport snaps into its final position at the last moment, whereas on the right it moves smoothly to its final destination.

The Passport animating onto the modal with a snap on the end (left), the Passport animating smoothly onto the modal (right)

Switching over too early resulted in the jump; switching over too late made the animation feel sluggish. The timing had to balance these considerations: the timing had to achieve the right feel, while landing in the right place at the end of the modal transition.

To this end, we tuned the springs extensively so that at the conclusion of the modal opening animation, the X and Y coordinates of the Passport animation aligned perfectly at the cutoff, despite the long tail of the spring curve technically extending beyond the transition duration.

Conclusion

And there we have it! Like any magic trick, a lot of behind-the-scenes effort goes into making things look and feel effortless.

Now that we have these new insights, we’re looking forward to bringing even more delightful animations to our applications in the future. If you share our excitement and are interested in contributing to this or other projects, we invite you to explore the career opportunities available at our careers page.

We hope you found this exploration of the iOS implementation of the Host Passport insightful. To learn more about the declarative transition framework that powers advanced transitions like this throughout Airbnb’s iOS app, you can read our previous post: “Motion Engineering at Scale”.

Acknowledgments

Thanks to:

  • Cal Stephens
  • Matthew Cheok
  • Alejandro Erviti
  • Julian Adams
  • Sergii Rudenko
  • Carol Leung
  • Jeduan Cornejo
  • Marjorie Kasten

****************

All product names, logos, and brands are property of their respective owners. All company, product and service names used in this website are for identification purposes only. Use of these names, logos, and brands does not imply endorsement.

--

--