Skip to content

Commit

Permalink
Improve organization and naming
Browse files Browse the repository at this point in the history
  • Loading branch information
calda committed Jul 26, 2023
1 parent 5b3585f commit 6f9d866
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 67 deletions.
8 changes: 8 additions & 0 deletions Lottie.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
objects = {

/* Begin PBXBuildFile section */
0819D2A12A718CAE00D7DE49 /* LottieAnimationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0819D2A02A718CAE00D7DE49 /* LottieAnimationSource.swift */; };
0819D2A22A718CAE00D7DE49 /* LottieAnimationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0819D2A02A718CAE00D7DE49 /* LottieAnimationSource.swift */; };
0819D2A32A718CAE00D7DE49 /* LottieAnimationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0819D2A02A718CAE00D7DE49 /* LottieAnimationSource.swift */; };
0887346F28F0CBDE00458627 /* LottieAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0887346E28F0CBDE00458627 /* LottieAnimation.swift */; };
0887347028F0CBDE00458627 /* LottieAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0887346E28F0CBDE00458627 /* LottieAnimation.swift */; };
0887347128F0CBDE00458627 /* LottieAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0887346E28F0CBDE00458627 /* LottieAnimation.swift */; };
Expand Down Expand Up @@ -833,6 +836,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
0819D2A02A718CAE00D7DE49 /* LottieAnimationSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieAnimationSource.swift; sourceTree = "<group>"; };
0887346E28F0CBDE00458627 /* LottieAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LottieAnimation.swift; sourceTree = "<group>"; };
0887347228F0CCDD00458627 /* LottieAnimationHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LottieAnimationHelpers.swift; sourceTree = "<group>"; };
0887347328F0CCDD00458627 /* LottieAnimationViewInitializers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LottieAnimationViewInitializers.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1756,6 +1760,7 @@
2E9C95C72822F43100677516 /* Primitives */,
2E9C95CE2822F43100677516 /* Interpolatable */,
2E9C95D12822F43100677516 /* Helpers */,
0819D2A02A718CAE00D7DE49 /* LottieAnimationSource.swift */,
);
path = Utility;
sourceTree = "<group>";
Expand Down Expand Up @@ -2267,6 +2272,7 @@
2E9C96422822F43100677516 /* RootAnimationLayer.swift in Sources */,
2E9C97712822F43100677516 /* AnimationContext.swift in Sources */,
08C002052A46150D00AB54BA /* Archive+Progress.swift in Sources */,
0819D2A12A718CAE00D7DE49 /* LottieAnimationSource.swift in Sources */,
08C002F52A461D6A00AB54BA /* LottieView.swift in Sources */,
2E9C96B12822F43100677516 /* NodeProperty.swift in Sources */,
2E9C965D2822F43100677516 /* MainThreadAnimationLayer.swift in Sources */,
Expand Down Expand Up @@ -2615,6 +2621,7 @@
2E9C95E92822F43100677516 /* Merge.swift in Sources */,
2E9C96042822F43100677516 /* ImageLayerModel.swift in Sources */,
19465F53282F998B00BB2C97 /* CachedImageProvider.swift in Sources */,
0819D2A22A718CAE00D7DE49 /* LottieAnimationSource.swift in Sources */,
08F8B20E2898A7B100CB5323 /* RepeaterLayer.swift in Sources */,
0887347928F0CCDD00458627 /* LottieAnimationViewInitializers.swift in Sources */,
2E9C96BB2822F43100677516 /* KeypathSearchable.swift in Sources */,
Expand Down Expand Up @@ -2832,6 +2839,7 @@
2E9C96442822F43100677516 /* RootAnimationLayer.swift in Sources */,
2E9C97732822F43200677516 /* AnimationContext.swift in Sources */,
08C002F62A461D6A00AB54BA /* LottieView.swift in Sources */,
0819D2A32A718CAE00D7DE49 /* LottieAnimationSource.swift in Sources */,
2E9C96B32822F43100677516 /* NodeProperty.swift in Sources */,
2E9C965F2822F43100677516 /* MainThreadAnimationLayer.swift in Sources */,
2E9C96502822F43100677516 /* SolidCompositionLayer.swift in Sources */,
Expand Down
10 changes: 10 additions & 0 deletions Sources/Private/Model/DotLottie/DotLottieImageProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,13 @@ class DotLottieImageProvider: AnimationImageProvider {
}

}

extension DotLottieImageProvider: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(filepath)
}

static func ==(_ lhs: DotLottieImageProvider, _ rhs: DotLottieImageProvider) -> Bool {
lhs.filepath == rhs.filepath
}
}
17 changes: 17 additions & 0 deletions Sources/Private/Utility/LottieAnimationSource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Created by Cal Stephens on 7/26/23.
// Copyright © 2023 Airbnb Inc. All rights reserved.

enum LottieAnimationSource {
case lottieAnimation(LottieAnimation)
case dotLottieFile(DotLottieFile)

/// The animation displayed by this data source
var animation: LottieAnimation? {
switch self {
case .lottieAnimation(let animation):
return animation
case .dotLottieFile(let dotLottieFile):
return dotLottieFile.animation()?.animation
}
}
}
11 changes: 11 additions & 0 deletions Sources/Public/Animation/LottieAnimationLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1258,6 +1258,17 @@ public class LottieAnimationLayer: CALayer {
updateRasterizationState()
}

func loadAnimation(_ animationSource: LottieAnimationSource?) {
switch animationSource {
case .lottieAnimation(let animation):
self.animation = animation
case .dotLottieFile(let dotLottieFile):
loadAnimation(from: dotLottieFile)
case nil:
animation = nil
}
}

// MARK: Private

static private let animationName = "Lottie"
Expand Down
41 changes: 41 additions & 0 deletions Sources/Public/Animation/LottieAnimationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,43 @@ open class LottieAnimationView: LottieAnimationViewBase {
commonInit()
}

convenience init(
animationSource: LottieAnimationSource?,
imageProvider: AnimationImageProvider? = nil,
textProvider: AnimationTextProvider = DefaultTextProvider(),
fontProvider: AnimationFontProvider = DefaultFontProvider(),
configuration: LottieConfiguration = .shared,
logger: LottieLogger = .shared)
{
switch animationSource {
case .lottieAnimation(let animation):
self.init(
animation: animation,
imageProvider: imageProvider,
textProvider: textProvider,
fontProvider: fontProvider,
configuration: configuration,
logger: logger)

case .dotLottieFile(let dotLottieFile):
self.init(
dotLottie: dotLottieFile,
textProvider: textProvider,
fontProvider: fontProvider,
configuration: configuration,
logger: logger)

case nil:
self.init(
animation: nil,
imageProvider: imageProvider,
textProvider: textProvider,
fontProvider: fontProvider,
configuration: configuration,
logger: logger)
}
}

// MARK: Open

/// Plays the animation from its current state to the end.
Expand Down Expand Up @@ -937,6 +974,10 @@ open class LottieAnimationView: LottieAnimationViewBase {
lottieAnimationLayer.updateInFlightAnimation()
}

func loadAnimation(_ animationSource: LottieAnimationSource?) {
lottieAnimationLayer.loadAnimation(animationSource)
}

// MARK: Fileprivate

fileprivate var waitingToPlayAnimation = false
Expand Down
132 changes: 66 additions & 66 deletions Sources/Public/Animation/LottieView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,48 +18,60 @@ public struct LottieView<Placeholder: View>: UIViewConfiguringSwiftUIView {
placeholder = nil
}

/// Creates a `LottieView` that displays the given `DotLottieFile`
public init(dotLottieFile: DotLottieFile?) where Placeholder == EmptyView {
_animationSource = State(initialValue: dotLottieFile.map(LottieAnimationSource.dotLottieAnimation))
_animationSource = State(initialValue: dotLottieFile.map(LottieAnimationSource.dotLottieFile))
placeholder = nil
}

public init(_ lottieAnimationProvider: @escaping () async throws -> LottieAnimation?) where Placeholder == EmptyView {
self.init(lottieAnimationProvider, placeholder: EmptyView.init)
/// Creates a `LottieView` that asynchronously loads and displays the given `LottieAnimation`.
/// The `loadAnimation` closure is called exactly once in `onAppear`.
public init(_ loadAnimation: @escaping () async throws -> LottieAnimation?) where Placeholder == EmptyView {
self.init(loadAnimation, placeholder: EmptyView.init)
}

/// Creates a `LottieView` that asynchronously loads and displays the given `LottieAnimation`.
/// The `loadAnimation` closure is called exactly once in `onAppear`.
/// While the animation is loading, the `placeholder` view is shown in place of the `LottieAnimationView`.
public init(
_ lottieAnimationProvider: @escaping () async throws -> LottieAnimation?,
_ loadAnimation: @escaping () async throws -> LottieAnimation?,
@ViewBuilder placeholder: @escaping (() -> Placeholder))
{
self.init(animationProvider: {
let animation = try await lottieAnimationProvider()
return animation.map(LottieAnimationSource.lottieAnimation)
}, placeholder: placeholder)
self.init {
try await loadAnimation().map(LottieAnimationSource.lottieAnimation)
} placeholder: {
placeholder()
}
}

public init(_ dotLottieAnimationProvider: @escaping () async throws -> DotLottieFile?) where Placeholder == EmptyView {
self.init(
dotLottieAnimationProvider,
placeholder: EmptyView.init)
/// Creates a `LottieView` that asynchronously loads and displays the given `DotLottieFile`.
/// The `loadDotLottieFile` closure is called exactly once in `onAppear`.
public init(_ loadDotLottieFile: @escaping () async throws -> DotLottieFile?) where Placeholder == EmptyView {
self.init(loadDotLottieFile, placeholder: EmptyView.init)
}

/// Creates a `LottieView` that asynchronously loads and displays the given `DotLottieFile`.
/// The `loadDotLottieFile` closure is called exactly once in `onAppear`.
/// While the animation is loading, the `placeholder` view is shown in place of the `LottieAnimationView`.
public init(
_ dotLottieAnimationProvider: @escaping () async throws -> DotLottieFile?,
_ loadDotLottieFile: @escaping () async throws -> DotLottieFile?,
@ViewBuilder placeholder: @escaping (() -> Placeholder))
{
self.init(
animationProvider: {
let animation = try await dotLottieAnimationProvider()
return animation.map(LottieAnimationSource.dotLottieAnimation)
},
placeholder: placeholder)
self.init {
try await loadDotLottieFile().map(LottieAnimationSource.dotLottieFile)
} placeholder: {
placeholder()
}
}

/// Creates a `LottieView` that asynchronously loads and displays the given `LottieAnimationSource`.
/// The `loadAnimation` closure is called exactly once in `onAppear`.
/// While the animation is loading, the `placeholder` view is shown in place of the `LottieAnimationView`.
fileprivate init(
animationProvider: @escaping () async throws -> LottieAnimationSource?,
loadAnimation: @escaping () async throws -> LottieAnimationSource?,
@ViewBuilder placeholder: @escaping () -> Placeholder)
{
self.animationProvider = animationProvider
self.loadAnimation = loadAnimation
self.placeholder = placeholder
_animationSource = State(initialValue: nil)
}
Expand All @@ -68,11 +80,10 @@ public struct LottieView<Placeholder: View>: UIViewConfiguringSwiftUIView {

public var body: some View {
ZStack {
switch animationSource {
case .lottieAnimation(let animation):
if let animationSource = animationSource {
LottieAnimationView.swiftUIView {
LottieAnimationView(
animation: animation,
animationSource: animationSource,
imageProvider: imageProvider,
textProvider: textProvider,
fontProvider: fontProvider,
Expand All @@ -83,30 +94,18 @@ public struct LottieView<Placeholder: View>: UIViewConfiguringSwiftUIView {
// We check referential equality of the animation before updating as updating the
// animation has a side-effect of rebuilding the animation layer, and it would be
// prohibitive to do so on every state update.
if animation !== context.view.animation {
context.view.animation = animation
if animationSource.animation !== context.view.animation {
context.view.loadAnimation(animationSource)
}
}
.configurations(configurations)
case .dotLottieAnimation(let dotLottieFile):
LottieAnimationView.swiftUIView {
LottieAnimationView(
dotLottie: dotLottieFile,
textProvider: textProvider,
fontProvider: fontProvider,
configuration: configuration)
}
.sizing(sizing)
.configurations(configurations)
case .none:
if let placeholder {
placeholder()
}
} else {
placeholder?()
}
}
.loadAnimation(
animationProvider: animationProvider,
animation: $animationSource)
.onAppear {
loadAnimationIfNecessary()
}
}

/// Returns a copy of this `LottieView` updated to have the given closure applied to its
Expand Down Expand Up @@ -158,7 +157,7 @@ public struct LottieView<Placeholder: View>: UIViewConfiguringSwiftUIView {
}
}

/// Returns a copt of this view with its `LottieConfiguration` updated to the given value.
/// Returns a copy of this view with its `LottieConfiguration` updated to the given value.
public func configuration(_ configuration: LottieConfiguration) -> Self {
var copy = self
copy.configuration = configuration
Expand All @@ -172,6 +171,15 @@ public struct LottieView<Placeholder: View>: UIViewConfiguringSwiftUIView {
return copy
}

/// Returns a copy of this view with its `LottieLogger` updated to the given value.
/// - The underlying `LottieAnimationView`'s `LottieLogger` is immutable after configured,
/// so this value is only used when initializing the `LottieAnimationView` for the first time.
public func logger(_ logger: LottieLogger) -> Self {
var copy = self
copy.logger = logger
return copy
}

/// Returns a copy of this view with its image provider updated to the given value.
/// The image provider must be `Equatable` to avoid unnecessary state updates / re-renders.
public func imageProvider<ImageProvider: AnimationImageProvider & Equatable>(_ imageProvider: ImageProvider) -> Self {
Expand Down Expand Up @@ -326,36 +334,28 @@ public struct LottieView<Placeholder: View>: UIViewConfiguringSwiftUIView {
// MARK: Private

@State private var animationSource: LottieAnimationSource?
private var loadAnimation: (() async throws -> LottieAnimationSource?)?
private var imageProvider: AnimationImageProvider?
private var textProvider: AnimationTextProvider = DefaultTextProvider()
private var fontProvider: AnimationFontProvider = DefaultFontProvider()
private var configuration: LottieConfiguration = .shared
private var logger: LottieLogger = .shared
private var sizing = SwiftUIMeasurementContainerStrategy.automatic
private var animationProvider: (() async throws -> LottieAnimationSource?)?
private let placeholder: (() -> Placeholder)?

}

// MARK: - LottieAnimationSource

private enum LottieAnimationSource {
case lottieAnimation(LottieAnimation)
case dotLottieAnimation(DotLottieFile)
}

@available(iOS 13.0, tvOS 13.0, macOS 10.15, *)
extension View {
fileprivate func loadAnimation(
animationProvider: (() async throws -> LottieAnimationSource?)?,
animation: Binding<LottieAnimationSource?>)
-> some View
{
onAppear {
Task {
if let animationLoaded = try? await animationProvider?() {
animation.wrappedValue = animationLoaded
}
private func loadAnimationIfNecessary() {
guard
let loadAnimation = loadAnimation,
animationSource == nil
else { return }

Task {
do {
animationSource = try await loadAnimation()
} catch {
logger.warn("Failed to load asynchronous Lottie animation with error: \(error)")
}
}
}

}
2 changes: 1 addition & 1 deletion Sources/Public/DotLottie/DotLottieFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public final class DotLottieFile {
// MARK: Internal

/// Image provider for animations
private(set) var imageProvider: AnimationImageProvider?
private(set) var imageProvider: DotLottieImageProvider?

/// Animations folder url
lazy var animationsUrl: URL = fileUrl.appendingPathComponent("\(DotLottieFile.animationsFolderName)")
Expand Down

0 comments on commit 6f9d866

Please sign in to comment.