lottie-ios 4.2.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/main.yml +117 -11
- package/Gemfile +1 -0
- package/Gemfile.lock +5 -0
- package/Lottie.xcodeproj/project.pbxproj +1542 -194
- package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (iOS).xcscheme +1 -1
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (macOS).xcscheme +1 -1
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (tvOS).xcscheme +4 -5
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (visionOS).xcscheme +66 -0
- package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +18 -0
- package/Lottie.xcworkspace/xcshareddata/swiftpm/Package.resolved +2 -11
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +346 -102
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +16 -5
- package/Package.resolved +2 -2
- package/Package.swift +16 -9
- package/README.md +2 -2
- package/Rakefile +122 -25
- package/Sources/Private/CoreAnimation/Animations/DropShadowAnimation.swift +160 -0
- package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +83 -10
- package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +85 -79
- package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +7 -7
- package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +67 -6
- package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +2 -2
- package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +9 -0
- package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +1 -0
- package/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift +1 -1
- package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +2 -0
- package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +11 -13
- package/Sources/Private/CoreAnimation/Layers/SolidLayer.swift +20 -5
- package/Sources/Private/CoreAnimation/Layers/TextLayer.swift +13 -3
- package/Sources/Private/CoreAnimation/ValueProviderStore.swift +6 -4
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Collection+Diff.swift +263 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Diffable.swift +18 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/DiffableSection.swift +16 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/IndexChangeset.swift +187 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/SectionedChangeset.swift +32 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Logging/EpoxyLogger.swift +99 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/CallbackContextEpoxyModeled.swift +8 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelArrayBuilder.swift +48 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelProperty.swift +158 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelStorage.swift +88 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModeled.swift +54 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/AnyEpoxyModelProperty.swift +29 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/ClassReference.swift +39 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/AnimatedProviding.swift +10 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DataIDProviding.swift +57 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidDisplayProviding.swift +41 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidEndDisplayingProviding.swift +41 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidSelectProviding.swift +36 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ErasedContentProviding.swift +49 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/MakeViewProviding.swift +60 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetBehaviorsProviding.swift +38 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetContentProviding.swift +38 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/StyleIDProviding.swift +37 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/TraitCollectionProviding.swift +14 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewDifferentiatorProviding.swift +34 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewProviding.swift +13 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/WillDisplayProviding.swift +41 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/ViewEpoxyModeled.swift +10 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/README.md +31 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingController.swift +46 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingView.swift +391 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIIntrinsicContentSizeInvalidator.swift +44 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUILayoutMargins.swift +51 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxyableView+SwiftUIView.swift +172 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/MeasuringViewRepresentable.swift +128 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift +452 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/SwiftUIView.swift +148 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIView+SwiftUIView.swift +40 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIViewConfiguringSwiftUIView.swift +43 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/BehaviorsConfigurableView.swift +45 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ContentConfigurableView.swift +36 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/EpoxyableView.swift +5 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/StyledView.swift +42 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ViewType.swift +51 -0
- package/Sources/Private/EmbeddedLibraries/README.md +27 -0
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+BackingConfiguration.swift +4 -4
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+MemoryFile.swift +2 -2
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Writing.swift +4 -4
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive.swift +8 -7
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Compression.swift +5 -5
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/FileManager+ZIP.swift +4 -4
- package/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.swift +6 -0
- package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +3 -1
- package/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift +18 -5
- package/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift +31 -3
- package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +33 -8
- package/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift +8 -1
- package/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift +13 -4
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift +2 -2
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.swift +1 -0
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift +4 -4
- package/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +3 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +2 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift +2 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +2 -2
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +6 -2
- package/Sources/Private/Model/Assets/Asset.swift +8 -0
- package/Sources/Private/Model/Assets/AssetLibrary.swift +2 -2
- package/Sources/Private/Model/Assets/ImageAsset.swift +3 -3
- package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +16 -2
- package/Sources/Private/Model/DotLottie/DotLottieImageProvider.swift +20 -7
- package/Sources/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +26 -0
- package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +8 -5
- package/Sources/Private/Model/LayerEffects/DropShadowEffect.swift +45 -0
- package/Sources/Private/Model/LayerEffects/EffectValues/ColorEffectValue.swift +38 -0
- package/Sources/Private/Model/LayerEffects/EffectValues/EffectValue.swift +98 -0
- package/Sources/Private/Model/LayerEffects/EffectValues/Vector1DEffectValue.swift +38 -0
- package/Sources/Private/Model/LayerEffects/LayerEffect.swift +103 -0
- package/Sources/Private/Model/LayerStyles/DropShadowStyle.swift +72 -0
- package/Sources/Private/Model/LayerStyles/LayerStyle.swift +85 -0
- package/Sources/Private/Model/Layers/LayerModel.swift +27 -0
- package/Sources/Private/Model/Objects/Marker.swift +1 -1
- package/Sources/Private/Model/Objects/Transform.swift +1 -2
- package/Sources/Private/Model/ShapeItems/GradientFill.swift +1 -1
- package/Sources/Private/Model/ShapeItems/GradientStroke.swift +2 -2
- package/Sources/Private/Model/ShapeItems/Merge.swift +1 -1
- package/Sources/Private/Model/ShapeItems/ShapeItem.swift +31 -26
- package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +0 -9
- package/Sources/Private/Model/ShapeItems/Star.swift +1 -1
- package/Sources/Private/Model/Text/Font.swift +2 -2
- package/Sources/Private/Model/Text/Glyph.swift +1 -1
- package/Sources/Private/RootAnimationLayer.swift +2 -2
- package/Sources/Private/Utility/Debugging/LayerDebugging.swift +3 -1
- package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +32 -2
- package/Sources/Private/Utility/Helpers/AnimationContext.swift +4 -2
- package/Sources/Private/Utility/Helpers/AnyEquatable.swift +24 -0
- package/Sources/Private/Utility/Helpers/Binding+Map.swift +18 -0
- package/Sources/Private/Utility/Helpers/View+ValueChanged.swift +20 -0
- package/Sources/Private/Utility/LottieAnimationSource.swift +41 -0
- package/Sources/Private/Utility/Primitives/BezierPath.swift +2 -2
- package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +2 -2
- package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +1 -1
- package/Sources/Public/Animation/LottieAnimation.swift +21 -2
- package/Sources/Public/Animation/LottieAnimationLayer.swift +1464 -0
- package/Sources/Public/Animation/LottieAnimationView.swift +258 -735
- package/Sources/Public/Animation/LottiePlaybackMode.swift +117 -0
- package/Sources/Public/Animation/LottieView.swift +462 -0
- package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +2 -1
- package/Sources/Public/AnimationCache/DefaultAnimationCache.swift +5 -6
- package/Sources/Public/Configuration/DecodingStrategy.swift +15 -0
- package/Sources/Public/Configuration/LottieConfiguration.swift +47 -0
- package/Sources/Public/Configuration/ReducedMotionOption.swift +114 -0
- package/Sources/Public/{LottieConfiguration.swift → Configuration/RenderingEngineOption.swift} +2 -57
- package/Sources/Public/{iOS → Controls}/AnimatedButton.swift +56 -13
- package/Sources/Public/{iOS → Controls}/AnimatedControl.swift +80 -8
- package/Sources/Public/{iOS → Controls}/AnimatedSwitch.swift +71 -31
- package/Sources/Public/Controls/LottieButton.swift +122 -0
- package/Sources/Public/Controls/LottieSwitch.swift +144 -0
- package/Sources/Public/Controls/LottieViewType.swift +79 -0
- package/Sources/Public/DotLottie/DotLottieConfiguration.swift +24 -0
- package/Sources/Public/DotLottie/DotLottieFile.swift +21 -6
- package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +79 -0
- package/Sources/Public/DynamicProperties/AnimationKeypath.swift +11 -1
- package/Sources/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +14 -0
- package/Sources/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +13 -0
- package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +22 -5
- package/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +14 -0
- package/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +13 -0
- package/Sources/Public/FontProvider/AnimationFontProvider.swift +8 -0
- package/Sources/Public/ImageProvider/AnimationImageProvider.swift +24 -0
- package/Sources/Public/Keyframes/Keyframe.swift +6 -0
- package/Sources/Public/Primitives/Vectors.swift +2 -2
- package/Sources/Public/TextProvider/AnimationTextProvider.swift +79 -6
- package/Sources/Public/iOS/AnimationSubview.swift +1 -1
- package/Sources/Public/iOS/BundleImageProvider.swift +16 -2
- package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +2 -2
- package/Sources/Public/iOS/FilepathImageProvider.swift +22 -3
- package/Sources/Public/iOS/LottieAnimationViewBase.swift +7 -1
- package/Sources/Public/macOS/BundleImageProvider.macOS.swift +15 -1
- package/Sources/Public/macOS/FilepathImageProvider.macOS.swift +21 -2
- package/lottie-ios.podspec +2 -1
- package/package.json +1 -1
- package/Sources/Private/Model/DotLottie/DotLottieConfiguration.swift +0 -15
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Helpers.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Progress.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Reading.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ReadingDeprecated.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+WritingDeprecated.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ZIP64.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+CompressionDeprecated.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Serialization.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+Serialization.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+ZIP64.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/README.md +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/URL+ZIP.swift +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Created by Cal Stephens on 8/3/23.
|
|
2
|
+
// Copyright © 2023 Airbnb Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
import Foundation
|
|
5
|
+
|
|
6
|
+
// MARK: - LottiePlaybackMode
|
|
7
|
+
|
|
8
|
+
/// Configuration for how a Lottie animation should be played
|
|
9
|
+
public enum LottiePlaybackMode: Hashable {
|
|
10
|
+
/// The animation is paused at the given progress value,
|
|
11
|
+
/// a value between 0.0 (0% progress) and 1.0 (100% progress).
|
|
12
|
+
case progress(_ progress: AnimationProgressTime)
|
|
13
|
+
|
|
14
|
+
/// The animation is paused at the given frame of the animation.
|
|
15
|
+
case frame(_ frame: AnimationFrameTime)
|
|
16
|
+
|
|
17
|
+
/// The animation is paused at the given time value from the start of the animation.
|
|
18
|
+
case time(_ time: TimeInterval)
|
|
19
|
+
|
|
20
|
+
/// Any existing animation will be paused at the current frame.
|
|
21
|
+
case pause
|
|
22
|
+
|
|
23
|
+
/// Plays the animation from a progress (0-1) to a progress (0-1).
|
|
24
|
+
/// - Parameter fromProgress: The start progress of the animation. If `nil` the animation will start at the current progress.
|
|
25
|
+
/// - Parameter toProgress: The end progress of the animation.
|
|
26
|
+
/// - Parameter loopMode: The loop behavior of the animation.
|
|
27
|
+
case fromProgress(_ fromProgress: AnimationProgressTime?, toProgress: AnimationProgressTime, loopMode: LottieLoopMode)
|
|
28
|
+
|
|
29
|
+
/// The animation plays from the given `fromFrame` to the given `toFrame`.
|
|
30
|
+
/// - Parameter fromFrame: The start frame of the animation. If `nil` the animation will start at the current frame.
|
|
31
|
+
/// - Parameter toFrame: The end frame of the animation.
|
|
32
|
+
/// - Parameter loopMode: The loop behavior of the animation.
|
|
33
|
+
case fromFrame(_ fromFrame: AnimationFrameTime?, toFrame: AnimationFrameTime, loopMode: LottieLoopMode)
|
|
34
|
+
|
|
35
|
+
/// Plays the animation from a named marker to another marker.
|
|
36
|
+
///
|
|
37
|
+
/// Markers are point in time that are encoded into the Animation data and assigned a name.
|
|
38
|
+
///
|
|
39
|
+
/// NOTE: If markers are not found the play command will exit.
|
|
40
|
+
///
|
|
41
|
+
/// - Parameter fromMarker: The start marker for the animation playback. If `nil` the
|
|
42
|
+
/// animation will start at the current progress.
|
|
43
|
+
/// - Parameter toMarker: The end marker for the animation playback.
|
|
44
|
+
/// - Parameter playEndMarkerFrame: A flag to determine whether or not to play the frame of the end marker. If the
|
|
45
|
+
/// end marker represents the end of the section to play, it should be to true. If the provided end marker
|
|
46
|
+
/// represents the beginning of the next section, it should be false.
|
|
47
|
+
/// - Parameter loopMode: The loop behavior of the animation.
|
|
48
|
+
case fromMarker(
|
|
49
|
+
_ fromMarker: String?,
|
|
50
|
+
toMarker: String,
|
|
51
|
+
playEndMarkerFrame: Bool = true,
|
|
52
|
+
loopMode: LottieLoopMode)
|
|
53
|
+
|
|
54
|
+
/// Plays the animation from a named marker to the end of the marker's duration.
|
|
55
|
+
///
|
|
56
|
+
/// A marker is a point in time with an associated duration that is encoded into the
|
|
57
|
+
/// animation data and assigned a name.
|
|
58
|
+
///
|
|
59
|
+
/// NOTE: If marker is not found the play command will exit.
|
|
60
|
+
///
|
|
61
|
+
/// - Parameter marker: The start marker for the animation playback.
|
|
62
|
+
/// - Parameter loopMode: The loop behavior of the animation.
|
|
63
|
+
case marker(_ marker: String, loopMode: LottieLoopMode)
|
|
64
|
+
|
|
65
|
+
/// Plays the given markers sequentially in order.
|
|
66
|
+
///
|
|
67
|
+
/// A marker is a point in time with an associated duration that is encoded into the
|
|
68
|
+
/// animation data and assigned a name. Multiple markers can be played sequentially
|
|
69
|
+
/// to create programmable animations.
|
|
70
|
+
///
|
|
71
|
+
/// If a marker is not found, it will be skipped.
|
|
72
|
+
///
|
|
73
|
+
/// If a marker doesn't have a duration value, it will play with a duration of 0
|
|
74
|
+
/// (effectively being skipped).
|
|
75
|
+
///
|
|
76
|
+
/// If another animation is played (by calling any `play` method) while this
|
|
77
|
+
/// marker sequence is playing, the marker sequence will be cancelled.
|
|
78
|
+
///
|
|
79
|
+
/// - Parameter markers: The list of markers to play sequentially.
|
|
80
|
+
case markers(_ markers: [String])
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
extension LottiePlaybackMode {
|
|
84
|
+
/// Plays the animation from the current progress to a progress value (0-1).
|
|
85
|
+
/// - Parameter toProgress: The end progress of the animation.
|
|
86
|
+
/// - Parameter loopMode: The loop behavior of the animation.
|
|
87
|
+
public static func toProgress(_ toProgress: AnimationProgressTime, loopMode: LottieLoopMode) -> LottiePlaybackMode {
|
|
88
|
+
.fromProgress(nil, toProgress: toProgress, loopMode: loopMode)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Plays the animation from the current frame to the given frame.
|
|
92
|
+
/// - Parameter toFrame: The end frame of the animation.
|
|
93
|
+
/// - Parameter loopMode: The loop behavior of the animation.
|
|
94
|
+
public static func toFrame(_ toFrame: AnimationFrameTime, loopMode: LottieLoopMode) -> LottiePlaybackMode {
|
|
95
|
+
.fromFrame(nil, toFrame: toFrame, loopMode: loopMode)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Plays the animation from the current frame to some marker.
|
|
99
|
+
///
|
|
100
|
+
/// Markers are point in time that are encoded into the Animation data and assigned a name.
|
|
101
|
+
///
|
|
102
|
+
/// NOTE: If the marker isn't found the play command will exit.
|
|
103
|
+
///
|
|
104
|
+
/// - Parameter toMarker: The end marker for the animation playback.
|
|
105
|
+
/// - Parameter playEndMarkerFrame: A flag to determine whether or not to play the frame of the end marker. If the
|
|
106
|
+
/// end marker represents the end of the section to play, it should be to true. If the provided end marker
|
|
107
|
+
/// represents the beginning of the next section, it should be false.
|
|
108
|
+
/// - Parameter loopMode: The loop behavior of the animation.
|
|
109
|
+
public static func toMarker(
|
|
110
|
+
_ toMarker: String,
|
|
111
|
+
playEndMarkerFrame: Bool = true,
|
|
112
|
+
loopMode: LottieLoopMode)
|
|
113
|
+
-> LottiePlaybackMode
|
|
114
|
+
{
|
|
115
|
+
.fromMarker(nil, toMarker: toMarker, playEndMarkerFrame: playEndMarkerFrame, loopMode: loopMode)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
// Created by Bryn Bodayle on 1/20/22.
|
|
2
|
+
// Copyright © 2022 Airbnb Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
import SwiftUI
|
|
5
|
+
|
|
6
|
+
// MARK: - LottieView
|
|
7
|
+
|
|
8
|
+
/// A wrapper which exposes Lottie's `LottieAnimationView` to SwiftUI
|
|
9
|
+
@available(iOS 13.0, tvOS 13.0, macOS 10.15, *)
|
|
10
|
+
public struct LottieView<Placeholder: View>: UIViewConfiguringSwiftUIView {
|
|
11
|
+
|
|
12
|
+
// MARK: Lifecycle
|
|
13
|
+
|
|
14
|
+
/// Creates a `LottieView` that displays the given animation
|
|
15
|
+
public init(animation: LottieAnimation?) where Placeholder == EmptyView {
|
|
16
|
+
_animationSource = State(initialValue: animation.map(LottieAnimationSource.lottieAnimation))
|
|
17
|
+
placeholder = nil
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Initializes a `LottieView` with the provided `DotLottieFile` for display.
|
|
21
|
+
///
|
|
22
|
+
/// - Important: Avoid using this initializer with the `SynchronouslyBlockingCurrentThread` APIs.
|
|
23
|
+
/// If decompression of a `.lottie` file is necessary, prefer using the `.init(_ loadAnimation:)`
|
|
24
|
+
/// initializer, which takes an asynchronous closure:
|
|
25
|
+
/// ```
|
|
26
|
+
/// LottieView {
|
|
27
|
+
/// try await DotLottieFile.named(name)
|
|
28
|
+
/// }
|
|
29
|
+
/// ```
|
|
30
|
+
public init(dotLottieFile: DotLottieFile?) where Placeholder == EmptyView {
|
|
31
|
+
_animationSource = State(initialValue: dotLottieFile.map(LottieAnimationSource.dotLottieFile))
|
|
32
|
+
placeholder = nil
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Creates a `LottieView` that asynchronously loads and displays the given `LottieAnimation`.
|
|
36
|
+
/// The `loadAnimation` closure is called exactly once in `onAppear`.
|
|
37
|
+
/// If you wish to call `loadAnimation` again at a different time, you can use `.reloadAnimationTrigger(...)`.
|
|
38
|
+
public init(_ loadAnimation: @escaping () async throws -> LottieAnimation?) where Placeholder == EmptyView {
|
|
39
|
+
self.init(loadAnimation, placeholder: EmptyView.init)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Creates a `LottieView` that asynchronously loads and displays the given `LottieAnimation`.
|
|
43
|
+
/// The `loadAnimation` closure is called exactly once in `onAppear`.
|
|
44
|
+
/// If you wish to call `loadAnimation` again at a different time, you can use `.reloadAnimationTrigger(...)`.
|
|
45
|
+
/// While the animation is loading, the `placeholder` view is shown in place of the `LottieAnimationView`.
|
|
46
|
+
public init(
|
|
47
|
+
_ loadAnimation: @escaping () async throws -> LottieAnimation?,
|
|
48
|
+
@ViewBuilder placeholder: @escaping (() -> Placeholder))
|
|
49
|
+
{
|
|
50
|
+
self.init {
|
|
51
|
+
try await loadAnimation().map(LottieAnimationSource.lottieAnimation)
|
|
52
|
+
} placeholder: {
|
|
53
|
+
placeholder()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Creates a `LottieView` that asynchronously loads and displays the given `DotLottieFile`.
|
|
58
|
+
/// The `loadDotLottieFile` closure is called exactly once in `onAppear`.
|
|
59
|
+
/// If you wish to call `loadAnimation` again at a different time, you can use `.reloadAnimationTrigger(...)`.
|
|
60
|
+
/// You can use the `DotLottieFile` static methods API which use Swift concurrency to load your `.lottie` files:
|
|
61
|
+
/// ```
|
|
62
|
+
/// LottieView {
|
|
63
|
+
/// try await DotLottieFile.named(name)
|
|
64
|
+
/// }
|
|
65
|
+
/// ```
|
|
66
|
+
public init(_ loadDotLottieFile: @escaping () async throws -> DotLottieFile?) where Placeholder == EmptyView {
|
|
67
|
+
self.init(loadDotLottieFile, placeholder: EmptyView.init)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Creates a `LottieView` that asynchronously loads and displays the given `DotLottieFile`.
|
|
71
|
+
/// The `loadDotLottieFile` closure is called exactly once in `onAppear`.
|
|
72
|
+
/// If you wish to call `loadAnimation` again at a different time, you can use `.reloadAnimationTrigger(...)`.
|
|
73
|
+
/// While the animation is loading, the `placeholder` view is shown in place of the `LottieAnimationView`.
|
|
74
|
+
/// You can use the `DotLottieFile` static methods API which use Swift concurrency to load your `.lottie` files:
|
|
75
|
+
/// ```
|
|
76
|
+
/// LottieView {
|
|
77
|
+
/// try await DotLottieFile.named(name)
|
|
78
|
+
/// } placeholder: {
|
|
79
|
+
/// LoadingView()
|
|
80
|
+
/// }
|
|
81
|
+
/// ```
|
|
82
|
+
public init(
|
|
83
|
+
_ loadDotLottieFile: @escaping () async throws -> DotLottieFile?,
|
|
84
|
+
@ViewBuilder placeholder: @escaping (() -> Placeholder))
|
|
85
|
+
{
|
|
86
|
+
self.init {
|
|
87
|
+
try await loadDotLottieFile().map(LottieAnimationSource.dotLottieFile)
|
|
88
|
+
} placeholder: {
|
|
89
|
+
placeholder()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Creates a `LottieView` that asynchronously loads and displays the given `LottieAnimationSource`.
|
|
94
|
+
/// The `loadAnimation` closure is called exactly once in `onAppear`.
|
|
95
|
+
/// If you wish to call `loadAnimation` again at a different time, you can use `.reloadAnimationTrigger(...)`.
|
|
96
|
+
/// While the animation is loading, the `placeholder` view is shown in place of the `LottieAnimationView`.
|
|
97
|
+
public init(_ loadAnimation: @escaping () async throws -> LottieAnimationSource?) where Placeholder == EmptyView {
|
|
98
|
+
self.init(loadAnimation, placeholder: EmptyView.init)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Creates a `LottieView` that asynchronously loads and displays the given `LottieAnimationSource`.
|
|
102
|
+
/// The `loadAnimation` closure is called exactly once in `onAppear`.
|
|
103
|
+
/// If you wish to call `loadAnimation` again at a different time, you can use `.reloadAnimationTrigger(...)`.
|
|
104
|
+
/// While the animation is loading, the `placeholder` view is shown in place of the `LottieAnimationView`.
|
|
105
|
+
public init(
|
|
106
|
+
_ loadAnimation: @escaping () async throws -> LottieAnimationSource?,
|
|
107
|
+
@ViewBuilder placeholder: @escaping () -> Placeholder)
|
|
108
|
+
{
|
|
109
|
+
self.loadAnimation = loadAnimation
|
|
110
|
+
self.placeholder = placeholder
|
|
111
|
+
_animationSource = State(initialValue: nil)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// MARK: Public
|
|
115
|
+
|
|
116
|
+
public var body: some View {
|
|
117
|
+
ZStack {
|
|
118
|
+
if let animationSource = animationSource {
|
|
119
|
+
LottieAnimationView.swiftUIView {
|
|
120
|
+
defer { animationDidLoad?(animationSource) }
|
|
121
|
+
return LottieAnimationView(
|
|
122
|
+
animationSource: animationSource,
|
|
123
|
+
imageProvider: imageProvider,
|
|
124
|
+
textProvider: textProvider,
|
|
125
|
+
fontProvider: fontProvider,
|
|
126
|
+
configuration: configuration,
|
|
127
|
+
logger: logger)
|
|
128
|
+
}
|
|
129
|
+
.sizing(sizing)
|
|
130
|
+
.configure { context in
|
|
131
|
+
// We check referential equality of the animation before updating as updating the
|
|
132
|
+
// animation has a side-effect of rebuilding the animation layer, and it would be
|
|
133
|
+
// prohibitive to do so on every state update.
|
|
134
|
+
if animationSource.animation !== context.view.animation {
|
|
135
|
+
context.view.loadAnimation(animationSource)
|
|
136
|
+
animationDidLoad?(animationSource)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if
|
|
140
|
+
let playbackMode = playbackMode,
|
|
141
|
+
playbackMode != context.view.currentPlaybackMode
|
|
142
|
+
{
|
|
143
|
+
context.view.play(playbackMode, animationCompletionHandler: animationCompletionHandler)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
.configurations(configurations)
|
|
147
|
+
} else {
|
|
148
|
+
placeholder?()
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
.onAppear {
|
|
152
|
+
loadAnimationIfNecessary()
|
|
153
|
+
}
|
|
154
|
+
.valueChanged(value: reloadAnimationTrigger) { _ in
|
|
155
|
+
reloadAnimationTriggerDidChange()
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/// Returns a copy of this `LottieView` updated to have the given closure applied to its
|
|
160
|
+
/// represented `LottieAnimationView` whenever it is updated via the `updateUIView(…)`
|
|
161
|
+
/// or `updateNSView(…)` method.
|
|
162
|
+
public func configure(_ configure: @escaping (LottieAnimationView) -> Void) -> Self {
|
|
163
|
+
var copy = self
|
|
164
|
+
copy.configurations.append { context in
|
|
165
|
+
configure(context.view)
|
|
166
|
+
}
|
|
167
|
+
return copy
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/// Returns a copy of this view that can be resized by scaling its animation to fit the size
|
|
171
|
+
/// offered by its parent.
|
|
172
|
+
public func resizable() -> Self {
|
|
173
|
+
var copy = self
|
|
174
|
+
copy.sizing = .proposed
|
|
175
|
+
return copy
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Returns a copy of this view playing once from the current frame to the end frame
|
|
179
|
+
public func play() -> Self {
|
|
180
|
+
play(loopMode: .playOnce)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// Returns a copy of this view that loops its animation from the start to end whenever visible
|
|
184
|
+
public func looping() -> Self {
|
|
185
|
+
play(.fromProgress(0, toProgress: 1, loopMode: .loop))
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/// Returns a copy of this view playing from the current frame to the end frame,
|
|
189
|
+
/// with the given `LottiePlaybackMode`.
|
|
190
|
+
public func play(loopMode: LottieLoopMode = .playOnce) -> Self {
|
|
191
|
+
play(.toProgress(1, loopMode: loopMode))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// Returns a copy of this view playing with the given `LottiePlaybackMode`
|
|
195
|
+
public func play(_ playbackMode: LottiePlaybackMode) -> Self {
|
|
196
|
+
self.playbackMode(playbackMode)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// Returns a copy of this view using the given `LottiePlaybackMode`
|
|
200
|
+
public func playbackMode(_ playbackMode: LottiePlaybackMode) -> Self {
|
|
201
|
+
var copy = self
|
|
202
|
+
copy.playbackMode = playbackMode
|
|
203
|
+
return copy
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/// Returns a copy of this view with the given closure that is called whenever the
|
|
207
|
+
/// `LottieAnimationSource` provided via `init` is loaded and applied to the underlying `LottieAnimationView`.
|
|
208
|
+
public func animationDidLoad(_ animationDidLoad: @escaping (LottieAnimationSource) -> Void) -> Self {
|
|
209
|
+
var copy = self
|
|
210
|
+
copy.animationDidLoad = animationDidLoad
|
|
211
|
+
return copy
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/// Returns a copy of this view with the given `LottieCompletionBlock` that is called
|
|
215
|
+
/// when an animation finishes playing.
|
|
216
|
+
public func animationDidFinish(_ animationCompletionHandler: LottieCompletionBlock?) -> Self {
|
|
217
|
+
var copy = self
|
|
218
|
+
copy.animationCompletionHandler = { [previousCompletionHandler = self.animationCompletionHandler] completed in
|
|
219
|
+
previousCompletionHandler?(completed)
|
|
220
|
+
animationCompletionHandler?(completed)
|
|
221
|
+
}
|
|
222
|
+
return copy
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/// Returns a copy of this view updated to have the provided background behavior.
|
|
226
|
+
public func backgroundBehavior(_ value: LottieBackgroundBehavior) -> Self {
|
|
227
|
+
configure { view in
|
|
228
|
+
view.backgroundBehavior = value
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/// Returns a copy of this view with its accessibility label updated to the given value.
|
|
233
|
+
public func accessibilityLabel(_ accessibilityLabel: String?) -> Self {
|
|
234
|
+
configure { view in
|
|
235
|
+
#if os(macOS)
|
|
236
|
+
view.setAccessibilityElement(accessibilityLabel != nil)
|
|
237
|
+
view.setAccessibilityLabel(accessibilityLabel)
|
|
238
|
+
#else
|
|
239
|
+
view.isAccessibilityElement = accessibilityLabel != nil
|
|
240
|
+
view.accessibilityLabel = accessibilityLabel
|
|
241
|
+
#endif
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/// Returns a copy of this view with its `LottieConfiguration` updated to the given value.
|
|
246
|
+
public func configuration(_ configuration: LottieConfiguration) -> Self {
|
|
247
|
+
var copy = self
|
|
248
|
+
copy.configuration = configuration
|
|
249
|
+
|
|
250
|
+
copy = copy.configure { view in
|
|
251
|
+
if view.configuration != configuration {
|
|
252
|
+
view.configuration = configuration
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return copy
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/// Returns a copy of this view with its `LottieLogger` updated to the given value.
|
|
260
|
+
/// - The underlying `LottieAnimationView`'s `LottieLogger` is immutable after configured,
|
|
261
|
+
/// so this value is only used when initializing the `LottieAnimationView` for the first time.
|
|
262
|
+
public func logger(_ logger: LottieLogger) -> Self {
|
|
263
|
+
var copy = self
|
|
264
|
+
copy.logger = logger
|
|
265
|
+
return copy
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/// Returns a copy of this view with its image provider updated to the given value.
|
|
269
|
+
/// The image provider must be `Equatable` to avoid unnecessary state updates / re-renders.
|
|
270
|
+
public func imageProvider<ImageProvider: AnimationImageProvider & Equatable>(_ imageProvider: ImageProvider) -> Self {
|
|
271
|
+
var copy = self
|
|
272
|
+
copy.imageProvider = imageProvider
|
|
273
|
+
|
|
274
|
+
copy = copy.configure { view in
|
|
275
|
+
if (view.imageProvider as? ImageProvider) != imageProvider {
|
|
276
|
+
view.imageProvider = imageProvider
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return copy
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/// Returns a copy of this view with its text provider updated to the given value.
|
|
284
|
+
/// The image provider must be `Equatable` to avoid unnecessary state updates / re-renders.
|
|
285
|
+
public func textProvider<TextProvider: AnimationKeypathTextProvider & Equatable>(_ textProvider: TextProvider) -> Self {
|
|
286
|
+
var copy = self
|
|
287
|
+
copy.textProvider = textProvider
|
|
288
|
+
|
|
289
|
+
copy = copy.configure { view in
|
|
290
|
+
if (view.textProvider as? TextProvider) != textProvider {
|
|
291
|
+
view.textProvider = textProvider
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return copy
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/// Returns a copy of this view with its image provider updated to the given value.
|
|
299
|
+
/// The image provider must be `Equatable` to avoid unnecessary state updates / re-renders.
|
|
300
|
+
public func fontProvider<FontProvider: AnimationFontProvider & Equatable>(_ fontProvider: FontProvider) -> Self {
|
|
301
|
+
var copy = self
|
|
302
|
+
copy.fontProvider = fontProvider
|
|
303
|
+
|
|
304
|
+
copy = configure { view in
|
|
305
|
+
if (view.fontProvider as? FontProvider) != fontProvider {
|
|
306
|
+
view.fontProvider = fontProvider
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return copy
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/// Returns a copy of this view using the given value provider for the given keypath.
|
|
314
|
+
/// The value provider must be `Equatable` to avoid unnecessary state updates / re-renders.
|
|
315
|
+
public func valueProvider<ValueProvider: AnyValueProvider & Equatable>(
|
|
316
|
+
_ valueProvider: ValueProvider,
|
|
317
|
+
for keypath: AnimationKeypath)
|
|
318
|
+
-> Self
|
|
319
|
+
{
|
|
320
|
+
configure { view in
|
|
321
|
+
if (view.valueProviders[keypath] as? ValueProvider) != valueProvider {
|
|
322
|
+
view.setValueProvider(valueProvider, keypath: keypath)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/// Returns a copy of this view updated to display the given `AnimationProgressTime`.
|
|
328
|
+
/// - If the `currentProgress` value is provided, the `currentProgress` of the
|
|
329
|
+
/// underlying `LottieAnimationView` is updated. This will pause any existing animations.
|
|
330
|
+
/// - If the `animationProgress` is `nil`, no changes will be made and any existing animations
|
|
331
|
+
/// will continue playing uninterrupted.
|
|
332
|
+
public func currentProgress(_ currentProgress: AnimationProgressTime?) -> Self {
|
|
333
|
+
guard let currentProgress = currentProgress else { return self }
|
|
334
|
+
var copy = self
|
|
335
|
+
copy.playbackMode = .progress(currentProgress)
|
|
336
|
+
return copy
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/// Returns a copy of this view updated to display the given `AnimationFrameTime`.
|
|
340
|
+
/// - If the `currentFrame` value is provided, the `currentFrame` of the
|
|
341
|
+
/// underlying `LottieAnimationView` is updated. This will pause any existing animations.
|
|
342
|
+
/// - If the `currentFrame` is `nil`, no changes will be made and any existing animations
|
|
343
|
+
/// will continue playing uninterrupted.
|
|
344
|
+
public func currentFrame(_ currentFrame: AnimationFrameTime?) -> Self {
|
|
345
|
+
guard let currentFrame = currentFrame else { return self }
|
|
346
|
+
var copy = self
|
|
347
|
+
copy.playbackMode = .frame(currentFrame)
|
|
348
|
+
return copy
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/// Returns a copy of this view updated to display the given time value.
|
|
352
|
+
/// - If the `currentTime` value is provided, the `currentTime` of the
|
|
353
|
+
/// underlying `LottieAnimationView` is updated. This will pause any existing animations.
|
|
354
|
+
/// - If the `currentTime` is `nil`, no changes will be made and any existing animations
|
|
355
|
+
/// will continue playing uninterrupted.
|
|
356
|
+
public func currentTime(_ currentTime: TimeInterval?) -> Self {
|
|
357
|
+
guard let currentTime = currentTime else { return self }
|
|
358
|
+
var copy = self
|
|
359
|
+
copy.playbackMode = .time(currentTime)
|
|
360
|
+
return copy
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/// Returns a new instance of this view, which will invoke the provided `loadAnimation` closure
|
|
364
|
+
/// whenever the `binding` value is updated.
|
|
365
|
+
///
|
|
366
|
+
/// - Note: This function requires a valid `loadAnimation` closure provided during view initialization,
|
|
367
|
+
/// otherwise `reloadAnimationTrigger` will have no effect.
|
|
368
|
+
/// - Parameters:
|
|
369
|
+
/// - binding: The binding that triggers the reloading when its value changes.
|
|
370
|
+
/// - showPlaceholder: When `true`, the current animation will be removed before invoking `loadAnimation`,
|
|
371
|
+
/// displaying the `Placeholder` until the new animation loads.
|
|
372
|
+
/// When `false`, the previous animation remains visible while the new one loads.
|
|
373
|
+
public func reloadAnimationTrigger<Value: Equatable>(_ value: Value, showPlaceholder: Bool = true) -> Self {
|
|
374
|
+
var copy = self
|
|
375
|
+
copy.reloadAnimationTrigger = AnyEquatable(value)
|
|
376
|
+
copy.showPlaceholderWhileReloading = showPlaceholder
|
|
377
|
+
return copy
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/// Returns a view that updates the given binding each frame with the animation's `realtimeAnimationProgress`.
|
|
381
|
+
/// The `LottieView` is wrapped in a `TimelineView` with the `.animation` schedule.
|
|
382
|
+
/// - This is a one-way binding. Its value is updated but never read.
|
|
383
|
+
/// - If provided, the binding will be updated each frame with the `realtimeAnimationProgress`
|
|
384
|
+
/// of the underlying `LottieAnimationView`. This is potentially expensive since it triggers
|
|
385
|
+
/// a state update every frame.
|
|
386
|
+
/// - If the binding is `nil`, the `TimelineView` will be paused and no updates will occur to the binding.
|
|
387
|
+
@available(iOS 15.0, tvOS 15.0, macOS 12.0, *)
|
|
388
|
+
public func getRealtimeAnimationProgress(_ realtimeAnimationProgress: Binding<AnimationProgressTime>?) -> some View {
|
|
389
|
+
TimelineView(.animation(paused: realtimeAnimationProgress == nil)) { _ in
|
|
390
|
+
configure { view in
|
|
391
|
+
if let realtimeAnimationProgress = realtimeAnimationProgress {
|
|
392
|
+
DispatchQueue.main.async {
|
|
393
|
+
realtimeAnimationProgress.wrappedValue = view.realtimeAnimationProgress
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/// Returns a view that updates the given binding each frame with the animation's `realtimeAnimationProgress`.
|
|
401
|
+
/// The `LottieView` is wrapped in a `TimelineView` with the `.animation` schedule.
|
|
402
|
+
/// - This is a one-way binding. Its value is updated but never read.
|
|
403
|
+
/// - If provided, the binding will be updated each frame with the `realtimeAnimationProgress`
|
|
404
|
+
/// of the underlying `LottieAnimationView`. This is potentially expensive since it triggers
|
|
405
|
+
/// a state update every frame.
|
|
406
|
+
/// - If the binding is `nil`, the `TimelineView` will be paused and no updates will occur to the binding.
|
|
407
|
+
@available(iOS 15.0, tvOS 15.0, macOS 12.0, *)
|
|
408
|
+
public func getRealtimeAnimationFrame(_ realtimeAnimationFrame: Binding<AnimationProgressTime>?) -> some View {
|
|
409
|
+
TimelineView(.animation(paused: realtimeAnimationFrame == nil)) { _ in
|
|
410
|
+
configure { view in
|
|
411
|
+
if let realtimeAnimationFrame = realtimeAnimationFrame {
|
|
412
|
+
DispatchQueue.main.async {
|
|
413
|
+
realtimeAnimationFrame.wrappedValue = view.realtimeAnimationFrame
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// MARK: Internal
|
|
421
|
+
|
|
422
|
+
var configurations = [SwiftUIView<LottieAnimationView, Void>.Configuration]()
|
|
423
|
+
|
|
424
|
+
// MARK: Private
|
|
425
|
+
|
|
426
|
+
@State private var animationSource: LottieAnimationSource?
|
|
427
|
+
private var playbackMode: LottiePlaybackMode?
|
|
428
|
+
private var reloadAnimationTrigger: AnyEquatable?
|
|
429
|
+
private var loadAnimation: (() async throws -> LottieAnimationSource?)?
|
|
430
|
+
private var animationDidLoad: ((LottieAnimationSource) -> Void)?
|
|
431
|
+
private var animationCompletionHandler: LottieCompletionBlock?
|
|
432
|
+
private var showPlaceholderWhileReloading = false
|
|
433
|
+
private var imageProvider: AnimationImageProvider?
|
|
434
|
+
private var textProvider: AnimationKeypathTextProvider = DefaultTextProvider()
|
|
435
|
+
private var fontProvider: AnimationFontProvider = DefaultFontProvider()
|
|
436
|
+
private var configuration: LottieConfiguration = .shared
|
|
437
|
+
private var logger: LottieLogger = .shared
|
|
438
|
+
private var sizing = SwiftUIMeasurementContainerStrategy.automatic
|
|
439
|
+
private let placeholder: (() -> Placeholder)?
|
|
440
|
+
|
|
441
|
+
private func loadAnimationIfNecessary() {
|
|
442
|
+
guard let loadAnimation = loadAnimation else { return }
|
|
443
|
+
|
|
444
|
+
Task {
|
|
445
|
+
do {
|
|
446
|
+
animationSource = try await loadAnimation()
|
|
447
|
+
} catch {
|
|
448
|
+
logger.warn("Failed to load asynchronous Lottie animation with error: \(error)")
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
private func reloadAnimationTriggerDidChange() {
|
|
454
|
+
guard loadAnimation != nil else { return }
|
|
455
|
+
|
|
456
|
+
if showPlaceholderWhileReloading {
|
|
457
|
+
animationSource = nil
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
loadAnimationIfNecessary()
|
|
461
|
+
}
|
|
462
|
+
}
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
//
|
|
7
7
|
|
|
8
8
|
import Foundation
|
|
9
|
+
|
|
9
10
|
/// `AnimationCacheProvider` is a protocol that describes an Animation Cache.
|
|
10
11
|
/// Animation Cache is used when loading `LottieAnimation` models. Using an Animation Cache
|
|
11
12
|
/// can increase performance when loading an animation multiple times.
|
|
12
13
|
///
|
|
13
14
|
/// Lottie comes with a prebuilt LRU Animation Cache.
|
|
14
|
-
public protocol AnimationCacheProvider: AnyObject {
|
|
15
|
+
public protocol AnimationCacheProvider: AnyObject, Sendable {
|
|
15
16
|
|
|
16
17
|
func animation(forKey: String) -> LottieAnimation?
|
|
17
18
|
|
|
@@ -13,7 +13,7 @@ import Foundation
|
|
|
13
13
|
/// The default size of the cache is 100.
|
|
14
14
|
///
|
|
15
15
|
/// This cache implementation also responds to memory pressure, as it's backed by `NSCache`.
|
|
16
|
-
public class DefaultAnimationCache: AnimationCacheProvider {
|
|
16
|
+
public class DefaultAnimationCache: AnimationCacheProvider, @unchecked Sendable {
|
|
17
17
|
|
|
18
18
|
// MARK: Lifecycle
|
|
19
19
|
|
|
@@ -27,10 +27,9 @@ public class DefaultAnimationCache: AnimationCacheProvider {
|
|
|
27
27
|
public static let sharedCache = DefaultAnimationCache()
|
|
28
28
|
|
|
29
29
|
/// The size of the cache.
|
|
30
|
-
public var cacheSize
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
30
|
+
public var cacheSize: Int {
|
|
31
|
+
get { cache.countLimit }
|
|
32
|
+
set { cache.countLimit = newValue }
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
/// Clears the Cache.
|
|
@@ -50,5 +49,5 @@ public class DefaultAnimationCache: AnimationCacheProvider {
|
|
|
50
49
|
|
|
51
50
|
private static let defaultCacheCountLimit = 100
|
|
52
51
|
|
|
53
|
-
private
|
|
52
|
+
private let cache = NSCache<NSString, LottieAnimation>()
|
|
54
53
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Created by Cal Stephens on 7/14/23.
|
|
2
|
+
// Copyright © 2023 Airbnb Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
/// How animation files should be decoded
|
|
5
|
+
public enum DecodingStrategy: Hashable {
|
|
6
|
+
/// Use Codable. This is was the default strategy introduced on Lottie 3, but should be rarely
|
|
7
|
+
/// used as it's slower than `dictionaryBased`. Kept here for any possible compatibility issues
|
|
8
|
+
/// that may come up, but consider it soft-deprecated.
|
|
9
|
+
case legacyCodable
|
|
10
|
+
|
|
11
|
+
/// Manually deserialize a dictionary into an Animation.
|
|
12
|
+
/// This should be at least 2-3x faster than using Codable and due to that
|
|
13
|
+
/// it's the default as of Lottie 4.x.
|
|
14
|
+
case dictionaryBased
|
|
15
|
+
}
|