Lottie in SwiftUI: LottieView, Playback and Looping
By the Charmotion team · Updated

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. LottieViewis 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?
| Platform | lottie-spm | RiveRuntime |
|---|---|---|
| iOS | Yes | 14.0+ |
| macOS | Yes | 13.1+ |
| tvOS | Yes | 16.0+ |
| visionOS | Yes | 1.0+ |
| watchOS | Not declared | Not 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 | |
|---|---|---|
| Format | JSON or .lottie, usually from After Effects or Lottie Creator | Binary .riv from the Rive editor |
| Changing state from code | Pick a file, marker or progress range | Set a number or fire a trigger; the state machine transitions |
| Blending between states | Designed by hand, otherwise cuts | Built into state machine transitions |
| Two-way data | No | Data binding properties and trigger streams |
| Good fit | Fixed clips: loaders, success ticks, illustrations | Characters 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
- Add the package. In Xcode, add
https://github.com/airbnb/lottie-spm.gitand link theLottielibrary. - Bundle the file. Add
idle.json(or a .lottie file) to the app target. - Show it. Write
LottieView(animation: .named("idle")).looping().resizable().scaledToFit()with a frame. - Handle one-shots. Use
.playing(loopMode: .playOnce)and.animationDidFinish, or drive.playbackModefrom state. - Respect Reduce Motion. Add a "reduced motion" marker to the file, or pause on a still frame when
accessibilityReduceMotionis 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
- lottie-ios — Airbnb (GitHub)
- LottieView.swift — Airbnb (GitHub)
- LottiePlaybackMode.swift — Airbnb (GitHub)
- lottie-spm Package.swift — Airbnb (GitHub)
- lottie-ios 4.3.0 release notes — Airbnb (GitHub)
- Lottie for iOS documentation — Airbnb
- rive-ios Package.swift — Rive (GitHub)