Mobile Development 13 min read

How to Supercharge Dual‑State Lottie Animations on iOS: A 3‑Step Performance Boost

This article walks through the performance bottlenecks of using Lottie for dual‑state animations in mobile apps and presents a three‑stage progressive optimization—basic synchronous implementation, asynchronous loading with caching, and a state‑machine‑based pending mechanism—complete with Swift code samples, diagrams, and benchmark results.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
How to Supercharge Dual‑State Lottie Animations on iOS: A 3‑Step Performance Boost

Introduction

In mobile applications, dual‑state animation switches are one of the most common interaction patterns, such as TabBar icon focus/unfocus, button selected/unselected, and switch on/off states.

Problems with Traditional Lottie Approach

Startup blocking : Synchronously loading animation resources on the main thread causes noticeable UI jank.

Switch lag : Each state change re‑parses the JSON file, leading to repeated I/O and CPU overhead.

Three Progressive Optimizations

Stage 1 – Basic Synchronous Solution

The initial implementation loads the animation directly in init and recreates the view on every state change.

class DualStateLottieView: UIView {
    private var animationView: LottieAnimationView!
    
    init(activePath: String, inactivePath: String) {
        // Synchronously load inactive state (blocks main thread)
        animationView = LottieAnimationView(filePath: inactivePath)
        super.init(frame: .zero)
        addSubview(animationView)
    }
    
    func setActive(_ isActive: Bool) {
        let path = isActive ? activePath : inactivePath
        // Reload on every switch (performance black hole!)
        animationView.removeFromSuperview()
        animationView = LottieAnimationView(filePath: path)
        addSubview(animationView)
        animationView.play()
    }
}

Issues identified:

Main‑thread blocking during initialization.

Repeated creation and destruction of view objects.

Strong coupling between resource loading and view rendering.

Stage 2 – Asynchronous Loading & Caching

To eliminate main‑thread stalls, the code is refactored to load animations asynchronously and cache the parsed LottieAnimation objects.

class DualStateLottieView: UIView {
    private var activeAnimation: LottieAnimation?
    private var inactiveAnimation: LottieAnimation?
    private let animationView = LottieAnimationView()
    
    func loadResources() {
        // Async load active animation
        DispatchQueue.global().async {
            let anim = LottieAnimation.filepath(activePath)
            DispatchQueue.main.async { self.activeAnimation = anim }
        }
        // Async load inactive animation (similar)
    }
    
    func setActive(_ isActive: Bool) {
        animationView.animation = isActive ? activeAnimation : inactiveAnimation
        animationView.play()
    }
}

Key improvements:

Animation data is cached, avoiding repeated JSON parsing.

Resources are loaded on a background thread, removing UI blocking.

State switching becomes a lightweight assignment.

Stage 3 – State Machine & Pending Mechanism

When users toggle states rapidly, the animation may not be ready yet. A state machine with pending states ensures correct behavior.

enum AnimationState { case active, inactive, pendingActive, pendingInactive }
private var currentState: AnimationState = .inactive

func setActive(_ isActive: Bool) {
    let targetState: AnimationState = isActive ? .active : .inactive
    switch (targetState, activeAnimation, inactiveAnimation) {
    case (.active, let anim?, _):
        play(animation: anim) // immediate
    case (.active, nil, _):
        currentState = .pendingActive // suspend request
    default:
        // handle other cases
    }
}

private func handleActiveLoaded() {
    if case .pendingActive = currentState {
        play(animation: activeAnimation!)
        currentState = .active
    }
}

The view’s didMoveToWindow method adds a lifecycle fallback to resolve pending states once the view is attached to a window.

override func didMoveToWindow() {
    super.didMoveToWindow()
    guard window != nil else { return }
    switch currentState {
    case .pendingActive where activeAnimation != nil:
        play(animation: activeAnimation!)
        currentState = .active
    // other pending cases …
    default: break
    }
}

Resource Isolation for Images

Lottie supports a custom FilepathImageProvider to specify an independent images directory, preventing conflicts when different animations use separate image assets.

// Create independent image provider
let activeProvider = FilepathImageProvider(filepath: URL(fileURLWithPath: activePath)
    .deletingLastPathComponent()
    .appendingPathComponent("images").path)

func play(animation: LottieAnimation, provider: AnimationImageProvider) {
    animationView.imageProvider = provider // switch resources first
    animationView.animation = animation   // then set animation data
    animationView.play()
}

Performance Comparison

Benchmark results show dramatic improvements:

Startup time reduced from ~89 ms to ~2 ms (≈95 % faster).

First‑switch latency dropped from 6.16 ms to 4.57 ms (≈25 % faster).

Subsequent switches improved from ~6.9 ms to ~0.04 ms (≈99 % faster) with zero memory allocations.

Conclusion

By decoupling resource loading, introducing a state‑machine‑driven pending mechanism, and isolating image resources, the solution eliminates 99 % of main‑thread blocking, achieves near‑zero‑cost state switches, and provides a robust, reusable architecture for any dual‑state animation scenario on iOS.

State Switch Flowchart
State Switch Flowchart
Performance Bottleneck
Performance Bottleneck
State Loss Issue
State Loss Issue
Architecture Overview
Architecture Overview
animationLottieperformance optimizationiOSstate machinemobile UIAsynchronous Loading
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.