Files
mobile-ios/Rosetta/DesignSystem/Components/LottieView.swift

128 lines
3.7 KiB
Swift

import SwiftUI
import Lottie
// MARK: - Animation Cache
final class LottieAnimationCache {
static let shared = LottieAnimationCache()
private var cache: [String: LottieAnimation] = [:]
private let lock = NSLock()
private init() {}
func animation(named name: String) -> LottieAnimation? {
lock.lock()
defer { lock.unlock() }
if let cached = cache[name] {
return cached
}
if let animation = LottieAnimation.named(name) {
cache[name] = animation
return animation
}
return nil
}
func preload(_ names: [String]) {
for name in names {
_ = animation(named: name)
}
}
}
// MARK: - LottieView
struct LottieView: UIViewRepresentable, Equatable {
let animationName: String
var loopMode: LottieLoopMode = .playOnce
var animationSpeed: CGFloat = 1.5
var isPlaying: Bool = true
static func == (lhs: LottieView, rhs: LottieView) -> Bool {
lhs.animationName == rhs.animationName &&
lhs.loopMode == rhs.loopMode &&
lhs.animationSpeed == rhs.animationSpeed &&
lhs.isPlaying == rhs.isPlaying
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
final class Coordinator {
var didPlayOnce = false
var lastAnimationName = ""
}
func makeUIView(context: Context) -> LottieAnimationView {
let animationView: LottieAnimationView
if let cached = LottieAnimationCache.shared.animation(named: animationName) {
animationView = LottieAnimationView(animation: cached)
} else {
animationView = LottieAnimationView(name: animationName)
}
animationView.contentMode = .scaleAspectFit
animationView.loopMode = loopMode
animationView.animationSpeed = animationSpeed
animationView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
animationView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
context.coordinator.lastAnimationName = animationName
playIfNeeded(animationView, coordinator: context.coordinator)
return animationView
}
func updateUIView(_ uiView: LottieAnimationView, context: Context) {
if context.coordinator.lastAnimationName != animationName {
if let cached = LottieAnimationCache.shared.animation(named: animationName) {
uiView.animation = cached
} else {
uiView.animation = LottieAnimation.named(animationName)
}
context.coordinator.lastAnimationName = animationName
context.coordinator.didPlayOnce = false
}
uiView.loopMode = loopMode
uiView.animationSpeed = animationSpeed
if !isPlaying {
context.coordinator.didPlayOnce = false
if uiView.isAnimationPlaying {
uiView.stop()
}
return
}
playIfNeeded(uiView, coordinator: context.coordinator)
}
private func playIfNeeded(_ view: LottieAnimationView, coordinator: Coordinator) {
if loopMode == .playOnce {
guard !coordinator.didPlayOnce else { return }
coordinator.didPlayOnce = true
view.play()
return
}
if !view.isAnimationPlaying {
view.play()
}
}
}
// MARK: - Crossfading Lottie Container
struct CrossfadingLottieView: View {
let animationName: String
let animationID: Int
var body: some View {
LottieView(
animationName: animationName,
loopMode: .playOnce,
animationSpeed: 1.5
)
.id(animationID)
.transition(.opacity.animation(.easeInOut(duration: 0.4)))
}
}