Skip to content
charmotion

Lottie in SwiftUI: LottieView, Playback and Looping

By the Charmotion team · Updated

Cover illustration for the iOS & Swift guide: Lottie in SwiftUI: LottieView, Playback and Looping, with Charmotion demo characters

To use Lottie in SwiftUI, add the lottie-spm package and write LottieView(animation: .named("idle")).looping(). Use .playing(loopMode: .playOnce) for a one-shot, .playbackMode(.playing(.fromProgress(0, toProgress: 1, loopMode: .loop))) for ranges, and .animationDidFinish to react when it ends. Lottie supports iOS, macOS, tvOS and visionOS, not watchOS.

Key takeaways

  • Install from https://github.com/airbnb/lottie-spm.git: Airbnb says it is under 500 KB to clone versus over 300 MB for the main repo.
  • LottieView is a SwiftUI view with chainable modifiers: .looping(), .playing(loopMode:), .playbackMode(_:), .resizable().
  • Named markers let one file hold several segments you select with .marker(_:loopMode:).
  • If the file contains a "reduced motion" marker, Lottie can play it instead when Reduce Motion is on.
  • Lottie has no data-bound state machine in LottieView; Rive is the better fit for a mascot that reacts to app state.

How do I install Lottie with Swift Package Manager?

In Xcode choose File → Add Package Dependencies and enter https://github.com/airbnb/lottie-spm.git. Airbnb's README recommends this wrapper repository for SwiftPM because the main lottie-ios repository is over 300 MB to clone, while lottie-spm is under 500 KB. Add the Lottie library to your app target and import Lottie.

Put your animation JSON (or .lottie file) in the app target. LottieAnimation.named("idle") looks up idle.json in the main bundle by default.

The lottie-spm manifest declares iOS, macOS, tvOS and visionOS. There is no watchOS platform, which matters if you share UI code with a Watch app.

How do I show a LottieView in SwiftUI?

LottieView has been part of lottie-ios since 4.3.0. The simplest form:

import SwiftUI
import Lottie

struct LoadingMascot: View {
    var body: some View {
        LottieView(animation: .named("loading"))
            .looping()
            .resizable()
            .scaledToFit()
            .frame(width: 180, height: 180)
            .accessibilityLabel("Loading")
    }
}

.resizable() lets the animation scale to its frame like a resizable Image; without it, the view uses the animation's intrinsic size. Other initializers include LottieView(dotLottieFile:) for .lottie files and async-closure variants that load an animation (for example from a URL) and show a placeholder while loading.

A note on cost: Lottie renders vector shapes every frame. For a small looping mascot this is fine; for many simultaneous animations in a scrolling list, prefer a static image in the list and animate only the focused item.

How do I loop or play a LottieView once?

The playback modifiers map to LottiePlaybackMode:

// Loop forever
LottieView(animation: .named("idle")).looping()

// Play once and stop on the last frame
LottieView(animation: .named("success")).playing(loopMode: .playOnce)

// Play a progress range on loop
LottieView(animation: .named("idle"))
    .playbackMode(.playing(.fromProgress(0, toProgress: 1, loopMode: .loop)))

// Paused on the current frame
LottieView(animation: .named("idle")).paused(at: .currentFrame)

To react when a one-shot ends, add .animationDidFinish { completed in … }. To restart a one-shot on demand, drive the playback mode from state and change it:

struct SaveButton: View {
    @State private var playback: LottiePlaybackMode = .paused(at: .progress(0))

    var body: some View {
        VStack {
            LottieView(animation: .named("success"))
                .playbackMode(playback)
                .animationDidFinish { _ in playback = .paused(at: .progress(0)) }
                .frame(width: 120, height: 120)
            Button("Save") {
                playback = .playing(.fromProgress(0, toProgress: 1, loopMode: .playOnce))
            }
        }
    }
}

Other loop modes include .autoReverse, .repeat(_:) and .repeatBackwards(_:).

Can I switch moods in Lottie without separate files?

Yes, with markers. If the designer adds named markers in After Effects or Lottie Creator (for example idle, happy, sad), you can play one segment at a time:

enum Mood: String { case idle, happy, sad }

struct MarkerMascot: View {
    let mood: Mood

    var body: some View {
        LottieView(animation: .named("mascot"))
            .playbackMode(.playing(.marker(mood.rawValue, loopMode: .loop)))
            .resizable()
            .scaledToFit()
    }
}

There is also .fromMarker(_:toMarker:loopMode:) and .markers([…]), which plays several markers in sequence, useful for "react, then settle."

The limitation is transitions. Changing the marker cuts to the new segment wherever the old one was; nothing blends unless the artwork is designed so every segment starts and ends on the same pose. That is the job a state machine does for you.

How does LottieView handle Reduce Motion?

Lottie 4.3.0 added reduced-motion support. If an animation contains a marker named reduced motion (the source also accepts reducedMotion, reduced_motion and reduced-motion), Lottie plays that marker instead of the full animation when the reduced-motion option is active. By default it follows the system setting.

If your file has no such marker, handle it yourself:

struct AdaptiveLottie: View {
    @Environment(\.accessibilityReduceMotion) private var reduceMotion

    var body: some View {
        LottieView(animation: .named("idle"))
            .playbackMode(reduceMotion ? .paused(at: .progress(0)) : .playing(.fromProgress(0, toProgress: 1, loopMode: .loop)))
            .resizable()
            .scaledToFit()
    }
}

Choose a pause frame that reads well as a still pose; frame 0 is often a neutral pose, but not always. More patterns: Reduce Motion in SwiftUI and UIKit.

How do I drive a LottieView from app state?

Keep the decision in your model and let the view pick the animation. A small enum keeps file names in one place and makes one-shots explicit:

enum UploadState: Equatable {
    case idle, uploading, done, failed

    var animationName: String {
        switch self {
        case .idle: return "idle"
        case .uploading: return "loading"
        case .done: return "success"
        case .failed: return "error"
        }
    }

    var loopMode: LottieLoopMode {
        switch self {
        case .idle, .uploading: return .loop
        case .done, .failed: return .playOnce
        }
    }
}

struct UploadMascot: View {
    let state: UploadState

    var body: some View {
        LottieView(animation: .named(state.animationName))
            .playing(loopMode: state.loopMode)
            .resizable()
            .scaledToFit()
            .id(state)                 // new view per state, so each clip starts from frame 0
            .transition(.opacity)
            .animation(.easeInOut(duration: 0.2), value: state)
    }
}

.id(state) forces a fresh view when the state changes, which restarts playback from the beginning and lets a short crossfade hide the cut. Without it, SwiftUI may reuse the view and keep the previous animation's progress. If you prefer one file with markers, replace the animation name with .playbackMode(.playing(.marker(…, loopMode:))) as shown above.

This is also where the limits of Lottie for characters become visible: you are writing the state machine yourself, one switch at a time.

Which platforms does Lottie support?

Platformlottie-spmRiveRuntime
iOSYes14.0+
macOSYes13.1+
tvOSYes16.0+
visionOSYes1.0+
watchOSNot declaredNot declared

Check the platforms list in each Package.swift for the exact minimum of the version you pin; it changes between releases. Neither package declares watchOS, so Apple Watch needs SwiftUI animators or pre-rendered frames; see watchOS and visionOS character animation.

On visionOS, LottieView works inside a regular SwiftUI window exactly as on iPhone. On macOS, the same SwiftUI code compiles, and AppKit apps can use LottieAnimationView directly. For UIKit screens on iOS, LottieAnimationView is the view class behind LottieView; it exposes play(), loopMode and animationSpeed as properties and methods.

When should I pick Rive over Lottie for a mascot?

Lottie (LottieView)Rive
FormatJSON or .lottie, usually from After Effects or Lottie CreatorBinary .riv from the Rive editor
Changing state from codePick a file, marker or progress rangeSet a number or fire a trigger; the state machine transitions
Blending between statesDesigned by hand, otherwise cutsBuilt into state machine transitions
Two-way dataNoData binding properties and trigger streams
Good fitFixed clips: loaders, success ticks, illustrationsCharacters that react to the app

Many apps use both: Lottie for a few decorative clips, Rive for the mascot. A Rive character with a mood number and triggers replaces what would otherwise be a folder of Lottie files and switching logic. See Lottie vs Rive state machines for a deeper comparison.

Add a looping Lottie animation to a SwiftUI view

  1. Add the package. In Xcode, add https://github.com/airbnb/lottie-spm.git and link the Lottie library.
  2. Bundle the file. Add idle.json (or a .lottie file) to the app target.
  3. Show it. Write LottieView(animation: .named("idle")).looping().resizable().scaledToFit() with a frame.
  4. Handle one-shots. Use .playing(loopMode: .playOnce) and .animationDidFinish, or drive .playbackMode from state.
  5. Respect Reduce Motion. Add a "reduced motion" marker to the file, or pause on a still frame when accessibilityReduceMotion is true.

Frequently asked questions

Why use lottie-spm instead of the lottie-ios repository?

Both give you the same Lottie library. Airbnb recommends lottie-spm for Swift Package Manager because the main lottie-ios repository is over 300 MB to clone, while lottie-spm is under 500 KB, which makes package resolution much faster in Xcode and on CI.

Does LottieView work on iOS 13 or 14?

Check the platforms list in the lottie-spm Package.swift for the version you pin, because minimums change between releases. LottieView itself shipped in lottie-ios 4.3.0. If your deployment target is below what the package declares, Xcode refuses to resolve it for that target.

Can LottieView play a .lottie (dotLottie) file?

Yes. LottieView has an initializer that takes a DotLottieFile, plus an async variant that loads one and shows a placeholder meanwhile. dotLottie is a zipped container that can hold several animations and their images in one file, which is often smaller than the equivalent JSON and keeps assets together.

Quick answers from this guide

Sources

Related guides