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.
Files changed (192) hide show
  1. package/.github/workflows/main.yml +117 -11
  2. package/Gemfile +1 -0
  3. package/Gemfile.lock +5 -0
  4. package/Lottie.xcodeproj/project.pbxproj +1542 -194
  5. package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  6. package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (iOS).xcscheme +1 -1
  7. package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (macOS).xcscheme +1 -1
  8. package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (tvOS).xcscheme +4 -5
  9. package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (visionOS).xcscheme +66 -0
  10. package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +18 -0
  11. package/Lottie.xcworkspace/xcshareddata/swiftpm/Package.resolved +2 -11
  12. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  13. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +346 -102
  14. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +16 -5
  15. package/Package.resolved +2 -2
  16. package/Package.swift +16 -9
  17. package/README.md +2 -2
  18. package/Rakefile +122 -25
  19. package/Sources/Private/CoreAnimation/Animations/DropShadowAnimation.swift +160 -0
  20. package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +83 -10
  21. package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +85 -79
  22. package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +7 -7
  23. package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +67 -6
  24. package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +2 -2
  25. package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +9 -0
  26. package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +1 -0
  27. package/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift +1 -1
  28. package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +2 -0
  29. package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +11 -13
  30. package/Sources/Private/CoreAnimation/Layers/SolidLayer.swift +20 -5
  31. package/Sources/Private/CoreAnimation/Layers/TextLayer.swift +13 -3
  32. package/Sources/Private/CoreAnimation/ValueProviderStore.swift +6 -4
  33. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Collection+Diff.swift +263 -0
  34. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Diffable.swift +18 -0
  35. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/DiffableSection.swift +16 -0
  36. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/IndexChangeset.swift +187 -0
  37. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/SectionedChangeset.swift +32 -0
  38. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Logging/EpoxyLogger.swift +99 -0
  39. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/CallbackContextEpoxyModeled.swift +8 -0
  40. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelArrayBuilder.swift +48 -0
  41. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelProperty.swift +158 -0
  42. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelStorage.swift +88 -0
  43. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModeled.swift +54 -0
  44. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/AnyEpoxyModelProperty.swift +29 -0
  45. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/ClassReference.swift +39 -0
  46. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/AnimatedProviding.swift +10 -0
  47. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DataIDProviding.swift +57 -0
  48. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidDisplayProviding.swift +41 -0
  49. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidEndDisplayingProviding.swift +41 -0
  50. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidSelectProviding.swift +36 -0
  51. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ErasedContentProviding.swift +49 -0
  52. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/MakeViewProviding.swift +60 -0
  53. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetBehaviorsProviding.swift +38 -0
  54. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetContentProviding.swift +38 -0
  55. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/StyleIDProviding.swift +37 -0
  56. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/TraitCollectionProviding.swift +14 -0
  57. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewDifferentiatorProviding.swift +34 -0
  58. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewProviding.swift +13 -0
  59. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/WillDisplayProviding.swift +41 -0
  60. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/ViewEpoxyModeled.swift +10 -0
  61. package/Sources/Private/EmbeddedLibraries/EpoxyCore/README.md +31 -0
  62. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingController.swift +46 -0
  63. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingView.swift +391 -0
  64. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIIntrinsicContentSizeInvalidator.swift +44 -0
  65. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUILayoutMargins.swift +51 -0
  66. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxyableView+SwiftUIView.swift +172 -0
  67. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/MeasuringViewRepresentable.swift +128 -0
  68. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift +452 -0
  69. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/SwiftUIView.swift +148 -0
  70. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIView+SwiftUIView.swift +40 -0
  71. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIViewConfiguringSwiftUIView.swift +43 -0
  72. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/BehaviorsConfigurableView.swift +45 -0
  73. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ContentConfigurableView.swift +36 -0
  74. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/EpoxyableView.swift +5 -0
  75. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/StyledView.swift +42 -0
  76. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ViewType.swift +51 -0
  77. package/Sources/Private/EmbeddedLibraries/README.md +27 -0
  78. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+BackingConfiguration.swift +4 -4
  79. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+MemoryFile.swift +2 -2
  80. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Writing.swift +4 -4
  81. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive.swift +8 -7
  82. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Compression.swift +5 -5
  83. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/FileManager+ZIP.swift +4 -4
  84. package/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.swift +6 -0
  85. package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +3 -1
  86. package/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift +18 -5
  87. package/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift +31 -3
  88. package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +33 -8
  89. package/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift +8 -1
  90. package/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift +13 -4
  91. package/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift +2 -2
  92. package/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.swift +1 -0
  93. package/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift +4 -4
  94. package/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +1 -1
  95. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +3 -1
  96. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +2 -1
  97. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift +2 -1
  98. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +1 -1
  99. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +2 -2
  100. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +1 -1
  101. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +1 -1
  102. package/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +6 -2
  103. package/Sources/Private/Model/Assets/Asset.swift +8 -0
  104. package/Sources/Private/Model/Assets/AssetLibrary.swift +2 -2
  105. package/Sources/Private/Model/Assets/ImageAsset.swift +3 -3
  106. package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +16 -2
  107. package/Sources/Private/Model/DotLottie/DotLottieImageProvider.swift +20 -7
  108. package/Sources/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +26 -0
  109. package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +8 -5
  110. package/Sources/Private/Model/LayerEffects/DropShadowEffect.swift +45 -0
  111. package/Sources/Private/Model/LayerEffects/EffectValues/ColorEffectValue.swift +38 -0
  112. package/Sources/Private/Model/LayerEffects/EffectValues/EffectValue.swift +98 -0
  113. package/Sources/Private/Model/LayerEffects/EffectValues/Vector1DEffectValue.swift +38 -0
  114. package/Sources/Private/Model/LayerEffects/LayerEffect.swift +103 -0
  115. package/Sources/Private/Model/LayerStyles/DropShadowStyle.swift +72 -0
  116. package/Sources/Private/Model/LayerStyles/LayerStyle.swift +85 -0
  117. package/Sources/Private/Model/Layers/LayerModel.swift +27 -0
  118. package/Sources/Private/Model/Objects/Marker.swift +1 -1
  119. package/Sources/Private/Model/Objects/Transform.swift +1 -2
  120. package/Sources/Private/Model/ShapeItems/GradientFill.swift +1 -1
  121. package/Sources/Private/Model/ShapeItems/GradientStroke.swift +2 -2
  122. package/Sources/Private/Model/ShapeItems/Merge.swift +1 -1
  123. package/Sources/Private/Model/ShapeItems/ShapeItem.swift +31 -26
  124. package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +0 -9
  125. package/Sources/Private/Model/ShapeItems/Star.swift +1 -1
  126. package/Sources/Private/Model/Text/Font.swift +2 -2
  127. package/Sources/Private/Model/Text/Glyph.swift +1 -1
  128. package/Sources/Private/RootAnimationLayer.swift +2 -2
  129. package/Sources/Private/Utility/Debugging/LayerDebugging.swift +3 -1
  130. package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +32 -2
  131. package/Sources/Private/Utility/Helpers/AnimationContext.swift +4 -2
  132. package/Sources/Private/Utility/Helpers/AnyEquatable.swift +24 -0
  133. package/Sources/Private/Utility/Helpers/Binding+Map.swift +18 -0
  134. package/Sources/Private/Utility/Helpers/View+ValueChanged.swift +20 -0
  135. package/Sources/Private/Utility/LottieAnimationSource.swift +41 -0
  136. package/Sources/Private/Utility/Primitives/BezierPath.swift +2 -2
  137. package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +2 -2
  138. package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +1 -1
  139. package/Sources/Public/Animation/LottieAnimation.swift +21 -2
  140. package/Sources/Public/Animation/LottieAnimationLayer.swift +1464 -0
  141. package/Sources/Public/Animation/LottieAnimationView.swift +258 -735
  142. package/Sources/Public/Animation/LottiePlaybackMode.swift +117 -0
  143. package/Sources/Public/Animation/LottieView.swift +462 -0
  144. package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +2 -1
  145. package/Sources/Public/AnimationCache/DefaultAnimationCache.swift +5 -6
  146. package/Sources/Public/Configuration/DecodingStrategy.swift +15 -0
  147. package/Sources/Public/Configuration/LottieConfiguration.swift +47 -0
  148. package/Sources/Public/Configuration/ReducedMotionOption.swift +114 -0
  149. package/Sources/Public/{LottieConfiguration.swift → Configuration/RenderingEngineOption.swift} +2 -57
  150. package/Sources/Public/{iOS → Controls}/AnimatedButton.swift +56 -13
  151. package/Sources/Public/{iOS → Controls}/AnimatedControl.swift +80 -8
  152. package/Sources/Public/{iOS → Controls}/AnimatedSwitch.swift +71 -31
  153. package/Sources/Public/Controls/LottieButton.swift +122 -0
  154. package/Sources/Public/Controls/LottieSwitch.swift +144 -0
  155. package/Sources/Public/Controls/LottieViewType.swift +79 -0
  156. package/Sources/Public/DotLottie/DotLottieConfiguration.swift +24 -0
  157. package/Sources/Public/DotLottie/DotLottieFile.swift +21 -6
  158. package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +79 -0
  159. package/Sources/Public/DynamicProperties/AnimationKeypath.swift +11 -1
  160. package/Sources/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +14 -0
  161. package/Sources/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +13 -0
  162. package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +22 -5
  163. package/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +14 -0
  164. package/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +13 -0
  165. package/Sources/Public/FontProvider/AnimationFontProvider.swift +8 -0
  166. package/Sources/Public/ImageProvider/AnimationImageProvider.swift +24 -0
  167. package/Sources/Public/Keyframes/Keyframe.swift +6 -0
  168. package/Sources/Public/Primitives/Vectors.swift +2 -2
  169. package/Sources/Public/TextProvider/AnimationTextProvider.swift +79 -6
  170. package/Sources/Public/iOS/AnimationSubview.swift +1 -1
  171. package/Sources/Public/iOS/BundleImageProvider.swift +16 -2
  172. package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +2 -2
  173. package/Sources/Public/iOS/FilepathImageProvider.swift +22 -3
  174. package/Sources/Public/iOS/LottieAnimationViewBase.swift +7 -1
  175. package/Sources/Public/macOS/BundleImageProvider.macOS.swift +15 -1
  176. package/Sources/Public/macOS/FilepathImageProvider.macOS.swift +21 -2
  177. package/lottie-ios.podspec +2 -1
  178. package/package.json +1 -1
  179. package/Sources/Private/Model/DotLottie/DotLottieConfiguration.swift +0 -15
  180. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Helpers.swift +0 -0
  181. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Progress.swift +0 -0
  182. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Reading.swift +0 -0
  183. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ReadingDeprecated.swift +0 -0
  184. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+WritingDeprecated.swift +0 -0
  185. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ZIP64.swift +0 -0
  186. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+CompressionDeprecated.swift +0 -0
  187. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Serialization.swift +0 -0
  188. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+Serialization.swift +0 -0
  189. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+ZIP64.swift +0 -0
  190. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry.swift +0 -0
  191. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/README.md +0 -0
  192. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/URL+ZIP.swift +0 -0
@@ -54,7 +54,7 @@ public enum LottieBackgroundBehavior {
54
54
  // MARK: - LottieLoopMode
55
55
 
56
56
  /// Defines animation loop behavior
57
- public enum LottieLoopMode {
57
+ public enum LottieLoopMode: Hashable {
58
58
  /// Animation is played once then stops.
59
59
  case playOnce
60
60
  /// Animation will loop from beginning to end until stopped.
@@ -87,6 +87,8 @@ extension LottieLoopMode: Equatable {
87
87
 
88
88
  // MARK: - LottieAnimationView
89
89
 
90
+ /// A UIView subclass for rendering Lottie animations.
91
+ /// - Also available as a SwiftUI view (`LottieView`) and a CALayer subclass (`LottieAnimationLayer`)
90
92
  @IBDesignable
91
93
  open class LottieAnimationView: LottieAnimationViewBase {
92
94
 
@@ -98,20 +100,21 @@ open class LottieAnimationView: LottieAnimationViewBase {
98
100
  public init(
99
101
  animation: LottieAnimation?,
100
102
  imageProvider: AnimationImageProvider? = nil,
101
- textProvider: AnimationTextProvider = DefaultTextProvider(),
103
+ textProvider: AnimationKeypathTextProvider = DefaultTextProvider(),
102
104
  fontProvider: AnimationFontProvider = DefaultFontProvider(),
103
105
  configuration: LottieConfiguration = .shared,
104
106
  logger: LottieLogger = .shared)
105
107
  {
106
- self.animation = animation
107
- self.imageProvider = imageProvider ?? BundleImageProvider(bundle: Bundle.main, searchPath: nil)
108
- self.textProvider = textProvider
109
- self.fontProvider = fontProvider
110
- self.configuration = configuration
108
+ lottieAnimationLayer = LottieAnimationLayer(
109
+ animation: animation,
110
+ imageProvider: imageProvider,
111
+ textProvider: textProvider,
112
+ fontProvider: fontProvider,
113
+ configuration: configuration,
114
+ logger: logger)
111
115
  self.logger = logger
112
116
  super.init(frame: .zero)
113
117
  commonInit()
114
- makeAnimationLayer(usingEngine: configuration.renderingEngine)
115
118
  if let animation = animation {
116
119
  frame = animation.bounds
117
120
  }
@@ -121,23 +124,21 @@ open class LottieAnimationView: LottieAnimationViewBase {
121
124
  public init(
122
125
  dotLottie: DotLottieFile?,
123
126
  animationId: String? = nil,
124
- textProvider: AnimationTextProvider = DefaultTextProvider(),
127
+ textProvider: AnimationKeypathTextProvider = DefaultTextProvider(),
125
128
  fontProvider: AnimationFontProvider = DefaultFontProvider(),
126
129
  configuration: LottieConfiguration = .shared,
127
130
  logger: LottieLogger = .shared)
128
131
  {
129
- let dotLottieAnimation = dotLottie?.animation(for: animationId)
130
- animation = dotLottieAnimation?.animation
131
- imageProvider = dotLottie?.imageProvider ?? BundleImageProvider(bundle: Bundle.main, searchPath: nil)
132
- self.textProvider = textProvider
133
- self.fontProvider = fontProvider
134
- self.configuration = configuration
132
+ lottieAnimationLayer = LottieAnimationLayer(
133
+ dotLottie: dotLottie,
134
+ animationId: animationId,
135
+ textProvider: textProvider,
136
+ fontProvider: fontProvider,
137
+ configuration: configuration,
138
+ logger: logger)
135
139
  self.logger = logger
136
140
  super.init(frame: .zero)
137
141
  commonInit()
138
- loopMode = dotLottieAnimation?.configuration.loopMode ?? .playOnce
139
- animationSpeed = CGFloat(dotLottieAnimation?.configuration.speed ?? 1)
140
- makeAnimationLayer(usingEngine: configuration.renderingEngine)
141
142
  if let animation = animation {
142
143
  frame = animation.bounds
143
144
  }
@@ -147,54 +148,82 @@ open class LottieAnimationView: LottieAnimationViewBase {
147
148
  configuration: LottieConfiguration = .shared,
148
149
  logger: LottieLogger = .shared)
149
150
  {
150
- animation = nil
151
- imageProvider = BundleImageProvider(bundle: Bundle.main, searchPath: nil)
152
- textProvider = DefaultTextProvider()
153
- fontProvider = DefaultFontProvider()
154
- self.configuration = configuration
151
+ lottieAnimationLayer = LottieAnimationLayer(configuration: configuration, logger: logger)
155
152
  self.logger = logger
156
153
  super.init(frame: .zero)
157
154
  commonInit()
158
155
  }
159
156
 
160
157
  public override init(frame: CGRect) {
161
- animation = nil
162
- imageProvider = BundleImageProvider(bundle: Bundle.main, searchPath: nil)
163
- textProvider = DefaultTextProvider()
164
- fontProvider = DefaultFontProvider()
165
- configuration = .shared
158
+ lottieAnimationLayer = LottieAnimationLayer(
159
+ animation: nil,
160
+ imageProvider: BundleImageProvider(bundle: Bundle.main, searchPath: nil),
161
+ textProvider: DefaultTextProvider(),
162
+ fontProvider: DefaultFontProvider(),
163
+ configuration: .shared,
164
+ logger: .shared)
166
165
  logger = .shared
167
166
  super.init(frame: frame)
168
167
  commonInit()
169
168
  }
170
169
 
171
170
  required public init?(coder aDecoder: NSCoder) {
172
- imageProvider = BundleImageProvider(bundle: Bundle.main, searchPath: nil)
173
- textProvider = DefaultTextProvider()
174
- fontProvider = DefaultFontProvider()
175
- configuration = .shared
171
+ lottieAnimationLayer = LottieAnimationLayer(
172
+ animation: nil,
173
+ imageProvider: BundleImageProvider(bundle: Bundle.main, searchPath: nil),
174
+ textProvider: DefaultTextProvider(),
175
+ fontProvider: DefaultFontProvider(),
176
+ configuration: .shared,
177
+ logger: .shared)
176
178
  logger = .shared
177
179
  super.init(coder: aDecoder)
178
180
  commonInit()
179
181
  }
180
182
 
183
+ convenience init(
184
+ animationSource: LottieAnimationSource?,
185
+ imageProvider: AnimationImageProvider? = nil,
186
+ textProvider: AnimationKeypathTextProvider = DefaultTextProvider(),
187
+ fontProvider: AnimationFontProvider = DefaultFontProvider(),
188
+ configuration: LottieConfiguration = .shared,
189
+ logger: LottieLogger = .shared)
190
+ {
191
+ switch animationSource {
192
+ case .lottieAnimation(let animation):
193
+ self.init(
194
+ animation: animation,
195
+ imageProvider: imageProvider,
196
+ textProvider: textProvider,
197
+ fontProvider: fontProvider,
198
+ configuration: configuration,
199
+ logger: logger)
200
+
201
+ case .dotLottieFile(let dotLottieFile):
202
+ self.init(
203
+ dotLottie: dotLottieFile,
204
+ textProvider: textProvider,
205
+ fontProvider: fontProvider,
206
+ configuration: configuration,
207
+ logger: logger)
208
+
209
+ case nil:
210
+ self.init(
211
+ animation: nil,
212
+ imageProvider: imageProvider,
213
+ textProvider: textProvider,
214
+ fontProvider: fontProvider,
215
+ configuration: configuration,
216
+ logger: logger)
217
+ }
218
+ }
219
+
181
220
  // MARK: Open
182
221
 
183
222
  /// Plays the animation from its current state to the end.
184
223
  ///
185
224
  /// - Parameter completion: An optional completion closure to be called when the animation completes playing.
186
225
  open func play(completion: LottieCompletionBlock? = nil) {
187
- guard let animation = animation else {
188
- return
189
- }
190
-
191
- /// Build a context for the animation.
192
- let context = AnimationContext(
193
- playFrom: CGFloat(animation.startFrame),
194
- playTo: CGFloat(animation.endFrame),
195
- closure: completion)
196
- removeCurrentAnimationIfNecessary()
197
- addNewAnimationForContext(context)
226
+ lottieAnimationLayer.play(completion: completion)
198
227
  }
199
228
 
200
229
  /// Plays the animation from a progress (0-1) to a progress (0-1).
@@ -209,20 +238,7 @@ open class LottieAnimationView: LottieAnimationViewBase {
209
238
  loopMode: LottieLoopMode? = nil,
210
239
  completion: LottieCompletionBlock? = nil)
211
240
  {
212
- guard let animation = animation else {
213
- return
214
- }
215
-
216
- removeCurrentAnimationIfNecessary()
217
- if let loopMode = loopMode {
218
- /// Set the loop mode, if one was supplied
219
- self.loopMode = loopMode
220
- }
221
- let context = AnimationContext(
222
- playFrom: animation.frameTime(forProgress: fromProgress ?? currentProgress),
223
- playTo: animation.frameTime(forProgress: toProgress),
224
- closure: completion)
225
- addNewAnimationForContext(context)
241
+ lottieAnimationLayer.play(fromProgress: fromProgress, toProgress: toProgress, loopMode: loopMode, completion: completion)
226
242
  }
227
243
 
228
244
  /// Plays the animation from a start frame to an end frame in the animation's framerate.
@@ -237,17 +253,7 @@ open class LottieAnimationView: LottieAnimationViewBase {
237
253
  loopMode: LottieLoopMode? = nil,
238
254
  completion: LottieCompletionBlock? = nil)
239
255
  {
240
- removeCurrentAnimationIfNecessary()
241
- if let loopMode = loopMode {
242
- /// Set the loop mode, if one was supplied
243
- self.loopMode = loopMode
244
- }
245
-
246
- let context = AnimationContext(
247
- playFrom: fromFrame ?? currentFrame,
248
- playTo: toFrame,
249
- closure: completion)
250
- addNewAnimationForContext(context)
256
+ lottieAnimationLayer.play(fromFrame: fromFrame, toFrame: toFrame, loopMode: loopMode, completion: completion)
251
257
  }
252
258
 
253
259
  /// Plays the animation from a named marker to another marker.
@@ -272,29 +278,12 @@ open class LottieAnimationView: LottieAnimationViewBase {
272
278
  loopMode: LottieLoopMode? = nil,
273
279
  completion: LottieCompletionBlock? = nil)
274
280
  {
275
- guard let animation = animation, let markers = animation.markerMap, let to = markers[toMarker] else {
276
- return
277
- }
278
-
279
- removeCurrentAnimationIfNecessary()
280
- if let loopMode = loopMode {
281
- /// Set the loop mode, if one was supplied
282
- self.loopMode = loopMode
283
- }
284
-
285
- let fromTime: CGFloat
286
- if let fromName = fromMarker, let from = markers[fromName] {
287
- fromTime = CGFloat(from.frameTime)
288
- } else {
289
- fromTime = currentFrame
290
- }
291
-
292
- let playTo = playEndMarkerFrame ? CGFloat(to.frameTime) : CGFloat(to.frameTime) - 1
293
- let context = AnimationContext(
294
- playFrom: fromTime,
295
- playTo: playTo,
296
- closure: completion)
297
- addNewAnimationForContext(context)
281
+ lottieAnimationLayer.play(
282
+ fromMarker: fromMarker,
283
+ toMarker: toMarker,
284
+ playEndMarkerFrame: playEndMarkerFrame,
285
+ loopMode: loopMode,
286
+ completion: completion)
298
287
  }
299
288
 
300
289
  /// Plays the animation from a named marker to the end of the marker's duration.
@@ -312,39 +301,70 @@ open class LottieAnimationView: LottieAnimationViewBase {
312
301
  loopMode: LottieLoopMode? = nil,
313
302
  completion: LottieCompletionBlock? = nil)
314
303
  {
315
- guard let from = animation?.markerMap?[marker] else {
316
- return
317
- }
304
+ lottieAnimationLayer.play(marker: marker, loopMode: loopMode, completion: completion)
305
+ }
318
306
 
319
- play(
320
- fromFrame: from.frameTime,
321
- toFrame: from.frameTime + from.durationFrameTime,
322
- loopMode: loopMode,
323
- completion: completion)
307
+ /// Plays the given markers sequentially in order.
308
+ ///
309
+ /// A marker is a point in time with an associated duration that is encoded into the
310
+ /// animation data and assigned a name. Multiple markers can be played sequentially
311
+ /// to create programmable animations.
312
+ ///
313
+ /// If a marker is not found, it will be skipped.
314
+ ///
315
+ /// If a marker doesn't have a duration value, it will play with a duration of 0
316
+ /// (effectively being skipped).
317
+ ///
318
+ /// If another animation is played (by calling any `play` method) while this
319
+ /// marker sequence is playing, the marker sequence will be cancelled.
320
+ ///
321
+ /// - Parameter markers: The list of markers to play sequentially.
322
+ /// - Parameter completion: An optional completion closure to be called when the animation stops.
323
+ open func play(
324
+ markers: [String],
325
+ completion: LottieCompletionBlock? = nil)
326
+ {
327
+ lottieAnimationLayer.play(markers: markers, completion: completion)
324
328
  }
325
329
 
326
330
  /// Stops the animation and resets the view to its start frame.
327
331
  ///
328
332
  /// The completion closure will be called with `false`
329
333
  open func stop() {
330
- removeCurrentAnimation()
331
- currentFrame = 0
334
+ lottieAnimationLayer.stop()
332
335
  }
333
336
 
334
337
  /// Pauses the animation in its current state.
335
338
  ///
336
339
  /// The completion closure will be called with `false`
337
340
  open func pause() {
338
- removeCurrentAnimation()
341
+ lottieAnimationLayer.pause()
342
+ }
343
+
344
+ /// Applies the given `LottiePlaybackMode` to this layer.
345
+ /// - Parameter animationCompletionHandler: A closure that is called after
346
+ /// an animation triggered by this method completes. This completion
347
+ /// handler is **not called** after setting the playback mode to a
348
+ /// mode that doesn't animate (e.g. `progress`, `frame`, `time`, or `pause`).
349
+ open func play(
350
+ _ playbackMode: LottiePlaybackMode,
351
+ animationCompletionHandler: LottieCompletionBlock? = nil)
352
+ {
353
+ lottieAnimationLayer.play(playbackMode, animationCompletionHandler: animationCompletionHandler)
339
354
  }
340
355
 
341
356
  // MARK: Public
342
357
 
343
358
  /// The configuration that this `LottieAnimationView` uses when playing its animation
344
- public let configuration: LottieConfiguration
359
+ public var configuration: LottieConfiguration {
360
+ get { lottieAnimationLayer.configuration }
361
+ set { lottieAnimationLayer.configuration = newValue }
362
+ }
345
363
 
346
364
  /// Value Providers that have been registered using `setValueProvider(_:keypath:)`
347
- public private(set) var valueProviders = [AnimationKeypath: AnyValueProvider]()
365
+ public var valueProviders: [AnimationKeypath: AnyValueProvider] {
366
+ lottieAnimationLayer.valueProviders
367
+ }
348
368
 
349
369
  /// Describes the behavior of an AnimationView when the app is moved to the background.
350
370
  ///
@@ -357,39 +377,16 @@ open class LottieAnimationView: LottieAnimationViewBase {
357
377
  /// The default for the Core Animation engine is `continuePlaying`,
358
378
  /// since the Core Animation engine does not have any CPU overhead.
359
379
  public var backgroundBehavior: LottieBackgroundBehavior {
360
- get {
361
- let currentBackgroundBehavior = _backgroundBehavior ?? .default(for: currentRenderingEngine ?? .mainThread)
362
-
363
- if
364
- currentRenderingEngine == .mainThread,
365
- _backgroundBehavior == .continuePlaying
366
- {
367
- logger.assertionFailure("""
368
- `LottieBackgroundBehavior.continuePlaying` should not be used with the Main Thread
369
- rendering engine, since this would waste CPU resources on playing an animation
370
- that is not visible. Consider using a different background mode, or switching to
371
- the Core Animation rendering engine (which does not have any CPU overhead).
372
- """)
373
- }
374
-
375
- return currentBackgroundBehavior
376
- }
377
- set {
378
- _backgroundBehavior = newValue
379
- }
380
+ get { lottieAnimationLayer.backgroundBehavior }
381
+ set { lottieAnimationLayer.backgroundBehavior = newValue }
380
382
  }
381
383
 
382
384
  /// Sets the animation backing the animation view. Setting this will clear the
383
385
  /// view's contents, completion blocks and current state. The new animation will
384
386
  /// be loaded up and set to the beginning of its timeline.
385
387
  public var animation: LottieAnimation? {
386
- didSet {
387
- makeAnimationLayer(usingEngine: configuration.renderingEngine)
388
-
389
- if let animation = animation {
390
- animationLoaded?(self, animation)
391
- }
392
- }
388
+ get { lottieAnimationLayer.animation }
389
+ set { lottieAnimationLayer.animation = newValue }
393
390
  }
394
391
 
395
392
  /// A closure that is called when `self.animation` is loaded. When setting this closure,
@@ -432,58 +429,44 @@ open class LottieAnimationView: LottieAnimationViewBase {
432
429
  ///
433
430
  /// Setting this will cause the animation to reload its image contents.
434
431
  public var imageProvider: AnimationImageProvider {
435
- didSet {
436
- animationLayer?.imageProvider = imageProvider.cachedImageProvider
437
- reloadImages()
438
- }
432
+ get { lottieAnimationLayer.imageProvider }
433
+ set { lottieAnimationLayer.imageProvider = newValue }
439
434
  }
440
435
 
441
436
  /// Sets the text provider for animation view. A text provider provides the
442
437
  /// animation with values for text layers
443
- public var textProvider: AnimationTextProvider {
444
- didSet {
445
- animationLayer?.textProvider = textProvider
446
- }
438
+ public var textProvider: AnimationKeypathTextProvider {
439
+ get { lottieAnimationLayer.textProvider }
440
+ set { lottieAnimationLayer.textProvider = newValue }
447
441
  }
448
442
 
449
443
  /// Sets the text provider for animation view. A text provider provides the
450
444
  /// animation with values for text layers
451
445
  public var fontProvider: AnimationFontProvider {
452
- didSet {
453
- animationLayer?.fontProvider = fontProvider
454
- }
446
+ get { lottieAnimationLayer.fontProvider }
447
+ set { lottieAnimationLayer.fontProvider = newValue }
455
448
  }
456
449
 
457
450
  /// Whether or not the animation is masked to the bounds. Defaults to true.
458
- public var maskAnimationToBounds = true {
459
- didSet {
460
- animationLayer?.masksToBounds = maskAnimationToBounds
461
- }
451
+ public var maskAnimationToBounds: Bool {
452
+ get { lottieAnimationLayer.maskAnimationToBounds }
453
+ set { lottieAnimationLayer.maskAnimationToBounds = newValue }
462
454
  }
463
455
 
464
456
  /// Returns `true` if the animation is currently playing.
465
457
  public var isAnimationPlaying: Bool {
466
- guard let animationLayer = animationLayer else {
467
- return false
468
- }
469
-
470
- if let valueFromLayer = animationLayer.isAnimationPlaying {
471
- return valueFromLayer
472
- } else {
473
- return animationLayer.animation(forKey: activeAnimationName) != nil
474
- }
458
+ lottieAnimationLayer.isAnimationPlaying
475
459
  }
476
460
 
477
461
  /// Returns `true` if the animation will start playing when this view is added to a window.
478
462
  public var isAnimationQueued: Bool {
479
- animationContext != nil && waitingToPlayAnimation
463
+ lottieAnimationLayer.hasAnimationContext && waitingToPlayAnimation
480
464
  }
481
465
 
482
466
  /// Sets the loop behavior for `play` calls. Defaults to `playOnce`
483
- public var loopMode: LottieLoopMode = .playOnce {
484
- didSet {
485
- updateInFlightAnimation()
486
- }
467
+ public var loopMode: LottieLoopMode {
468
+ get { lottieAnimationLayer.loopMode }
469
+ set { lottieAnimationLayer.loopMode = newValue }
487
470
  }
488
471
 
489
472
  /// When `true` the animation view will rasterize its contents when not animating.
@@ -492,10 +475,9 @@ open class LottieAnimationView: LottieAnimationViewBase {
492
475
  /// Note: this will not produce crisp results at resolutions above the animations natural resolution.
493
476
  ///
494
477
  /// Defaults to `false`
495
- public var shouldRasterizeWhenIdle = false {
496
- didSet {
497
- updateRasterizationState()
498
- }
478
+ public var shouldRasterizeWhenIdle: Bool {
479
+ get { lottieAnimationLayer.shouldRasterizeWhenIdle }
480
+ set { lottieAnimationLayer.shouldRasterizeWhenIdle = newValue }
499
481
  }
500
482
 
501
483
  /// Sets the current animation time with a Progress Time
@@ -503,20 +485,8 @@ open class LottieAnimationView: LottieAnimationViewBase {
503
485
  /// Note: Setting this will stop the current animation, if any.
504
486
  /// Note 2: If `animation` is nil, setting this will fallback to 0
505
487
  public var currentProgress: AnimationProgressTime {
506
- set {
507
- if let animation = animation {
508
- currentFrame = animation.frameTime(forProgress: newValue)
509
- } else {
510
- currentFrame = 0
511
- }
512
- }
513
- get {
514
- if let animation = animation {
515
- return animation.progressTime(forFrame: currentFrame)
516
- } else {
517
- return 0
518
- }
519
- }
488
+ get { lottieAnimationLayer.currentProgress }
489
+ set { lottieAnimationLayer.currentProgress = newValue }
520
490
  }
521
491
 
522
492
  /// Sets the current animation time with a time in seconds.
@@ -524,53 +494,32 @@ open class LottieAnimationView: LottieAnimationViewBase {
524
494
  /// Note: Setting this will stop the current animation, if any.
525
495
  /// Note 2: If `animation` is nil, setting this will fallback to 0
526
496
  public var currentTime: TimeInterval {
527
- set {
528
- if let animation = animation {
529
- currentFrame = animation.frameTime(forTime: newValue)
530
- } else {
531
- currentFrame = 0
532
- }
533
- }
534
- get {
535
- if let animation = animation {
536
- return animation.time(forFrame: currentFrame)
537
- } else {
538
- return 0
539
- }
540
- }
497
+ get { lottieAnimationLayer.currentTime }
498
+ set { lottieAnimationLayer.currentTime = newValue }
541
499
  }
542
500
 
543
501
  /// Sets the current animation time with a frame in the animations framerate.
544
502
  ///
545
503
  /// Note: Setting this will stop the current animation, if any.
546
504
  public var currentFrame: AnimationFrameTime {
547
- set {
548
- removeCurrentAnimationIfNecessary()
549
- updateAnimationFrame(newValue)
550
- }
551
- get {
552
- animationLayer?.currentFrame ?? 0
553
- }
505
+ get { lottieAnimationLayer.currentFrame }
506
+ set { lottieAnimationLayer.currentFrame = newValue }
554
507
  }
555
508
 
556
509
  /// Returns the current animation frame while an animation is playing.
557
510
  public var realtimeAnimationFrame: AnimationFrameTime {
558
- isAnimationPlaying ? animationLayer?.presentation()?.currentFrame ?? currentFrame : currentFrame
511
+ lottieAnimationLayer.realtimeAnimationFrame
559
512
  }
560
513
 
561
514
  /// Returns the current animation frame while an animation is playing.
562
515
  public var realtimeAnimationProgress: AnimationProgressTime {
563
- if let animation = animation {
564
- return animation.progressTime(forFrame: realtimeAnimationFrame)
565
- }
566
- return 0
516
+ lottieAnimationLayer.realtimeAnimationProgress
567
517
  }
568
518
 
569
519
  /// Sets the speed of the animation playback. Defaults to 1
570
- public var animationSpeed: CGFloat = 1 {
571
- didSet {
572
- updateInFlightAnimation()
573
- }
520
+ public var animationSpeed: CGFloat {
521
+ get { lottieAnimationLayer.animationSpeed }
522
+ set { lottieAnimationLayer.animationSpeed = newValue }
574
523
  }
575
524
 
576
525
  /// When `true` the animation will play back at the framerate encoded in the
@@ -578,10 +527,9 @@ open class LottieAnimationView: LottieAnimationViewBase {
578
527
  /// of the device.
579
528
  ///
580
529
  /// Defaults to false
581
- public var respectAnimationFrameRate = false {
582
- didSet {
583
- animationLayer?.respectAnimationFrameRate = respectAnimationFrameRate
584
- }
530
+ public var respectAnimationFrameRate: Bool {
531
+ get { lottieAnimationLayer.respectAnimationFrameRate }
532
+ set { lottieAnimationLayer.respectAnimationFrameRate = newValue }
585
533
  }
586
534
 
587
535
  /// Controls the cropping of an Animation. Setting this property will crop the animation
@@ -589,7 +537,7 @@ open class LottieAnimationView: LottieAnimationViewBase {
589
537
  /// in the animation's coordinate space.
590
538
  ///
591
539
  /// Animatable.
592
- public var viewportFrame: CGRect? = nil {
540
+ public var viewportFrame: CGRect? {
593
541
  didSet {
594
542
  // This is really ugly, but is needed to trigger a layout pass within an animation block.
595
543
  // Typically this happens automatically, when layout objects are UIView based.
@@ -607,7 +555,7 @@ open class LottieAnimationView: LottieAnimationViewBase {
607
555
  }
608
556
 
609
557
  override public var intrinsicContentSize: CGSize {
610
- if let animation = animation {
558
+ if let animation = lottieAnimationLayer.animation {
611
559
  return animation.bounds.size
612
560
  }
613
561
  return .zero
@@ -617,21 +565,25 @@ open class LottieAnimationView: LottieAnimationViewBase {
617
565
  /// - This will only be `nil` in cases where the configuration is `automatic`
618
566
  /// but a `RootAnimationLayer` hasn't been constructed yet
619
567
  public var currentRenderingEngine: RenderingEngine? {
620
- switch configuration.renderingEngine {
621
- case .specific(let engine):
622
- return engine
568
+ lottieAnimationLayer.currentRenderingEngine
569
+ }
623
570
 
624
- case .automatic:
625
- guard let animationLayer = animationLayer else {
626
- return nil
627
- }
571
+ /// The current `LottiePlaybackMode` that is being used
572
+ public var currentPlaybackMode: LottiePlaybackMode? {
573
+ lottieAnimationLayer.currentPlaybackMode
574
+ }
628
575
 
629
- if animationLayer is CoreAnimationLayer {
630
- return .coreAnimation
631
- } else {
632
- return .mainThread
633
- }
634
- }
576
+ /// Whether or not the Main Thread rendering engine should use `forceDisplayUpdate()`
577
+ /// when rendering each individual frame.
578
+ /// - The main thread rendering engine implements optimizations to decrease the amount
579
+ /// of properties that have to be re-rendered on each frame. There are some cases
580
+ /// where this can result in bugs / incorrect behavior, so we allow it to be disabled.
581
+ /// - Forcing a full render on every frame will decrease performance, and is not recommended
582
+ /// except as a workaround to a bug in the main thread rendering engine.
583
+ /// - Has no effect when using the Core Animation rendering engine.
584
+ public var mainThreadRenderingEngineShouldForceDisplayUpdateOnEachFrame: Bool {
585
+ get { lottieAnimationLayer.mainThreadRenderingEngineShouldForceDisplayUpdateOnEachFrame }
586
+ set { lottieAnimationLayer.mainThreadRenderingEngineShouldForceDisplayUpdateOnEachFrame = newValue }
635
587
  }
636
588
 
637
589
  /// Sets the lottie file backing the animation view. Setting this will clear the
@@ -647,26 +599,33 @@ open class LottieAnimationView: LottieAnimationViewBase {
647
599
  _ animationId: String? = nil,
648
600
  from dotLottieFile: DotLottieFile)
649
601
  {
650
- guard let dotLottieAnimation = dotLottieFile.animation(for: animationId) else { return }
651
-
652
- loopMode = dotLottieAnimation.configuration.loopMode
653
- animationSpeed = CGFloat(dotLottieAnimation.configuration.speed)
654
-
655
- if let imageProvider = dotLottieAnimation.configuration.imageProvider {
656
- self.imageProvider = imageProvider
657
- }
602
+ lottieAnimationLayer.loadAnimation(animationId, from: dotLottieFile)
603
+ }
658
604
 
659
- animation = dotLottieAnimation.animation
605
+ /// Sets the lottie file backing the animation view. Setting this will clear the
606
+ /// view's contents, completion blocks and current state. The new animation will
607
+ /// be loaded up and set to the beginning of its timeline.
608
+ /// The loopMode, animationSpeed and imageProvider will be set according
609
+ /// to lottie file settings
610
+ /// - Parameters:
611
+ /// - atIndex: Internal animation index to play. Optional
612
+ /// Defaults to play first animation in file.
613
+ /// - dotLottieFile: Lottie file to play
614
+ public func loadAnimation(
615
+ atIndex index: Int,
616
+ from dotLottieFile: DotLottieFile)
617
+ {
618
+ lottieAnimationLayer.loadAnimation(atIndex: index, from: dotLottieFile)
660
619
  }
661
620
 
662
621
  /// Reloads the images supplied to the animation from the `imageProvider`
663
622
  public func reloadImages() {
664
- animationLayer?.reloadImages()
623
+ lottieAnimationLayer.reloadImages()
665
624
  }
666
625
 
667
626
  /// Forces the LottieAnimationView to redraw its contents.
668
627
  public func forceDisplayUpdate() {
669
- animationLayer?.forceDisplayUpdate()
628
+ lottieAnimationLayer.forceDisplayUpdate()
670
629
  }
671
630
 
672
631
  /// Sets a ValueProvider for the specified keypath. The value provider will be set
@@ -692,10 +651,7 @@ open class LottieAnimationView: LottieAnimationViewBase {
692
651
  /// animationView.setValueProvider(redValueProvider, keypath: fillKeypath)
693
652
  /// ```
694
653
  public func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) {
695
- guard let animationLayer = animationLayer else { return }
696
-
697
- valueProviders[keypath] = valueProvider
698
- animationLayer.setValueProvider(valueProvider, keypath: keypath)
654
+ lottieAnimationLayer.setValueProvider(valueProvider, keypath: keypath)
699
655
  }
700
656
 
701
657
  /// Reads the value of a property specified by the Keypath.
@@ -704,7 +660,7 @@ open class LottieAnimationView: LottieAnimationViewBase {
704
660
  /// - Parameter for: The keypath used to search for the property.
705
661
  /// - Parameter atFrame: The Frame Time of the value to query. If nil then the current frame is used.
706
662
  public func getValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? {
707
- animationLayer?.getValue(for: keypath, atFrame: atFrame)
663
+ lottieAnimationLayer.getValue(for: keypath, atFrame: atFrame)
708
664
  }
709
665
 
710
666
  /// Reads the original value of a property specified by the Keypath.
@@ -714,19 +670,19 @@ open class LottieAnimationView: LottieAnimationViewBase {
714
670
  /// - Parameter for: The keypath used to search for the property.
715
671
  /// - Parameter atFrame: The Frame Time of the value to query. If nil then the current frame is used.
716
672
  public func getOriginalValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? {
717
- animationLayer?.getOriginalValue(for: keypath, atFrame: atFrame)
673
+ lottieAnimationLayer.getOriginalValue(for: keypath, atFrame: atFrame)
718
674
  }
719
675
 
720
676
  /// Logs all child keypaths.
721
677
  /// Logs the result of `allHierarchyKeypaths()` to the `LottieLogger`.
722
678
  public func logHierarchyKeypaths() {
723
- animationLayer?.logHierarchyKeypaths()
679
+ lottieAnimationLayer.logHierarchyKeypaths()
724
680
  }
725
681
 
726
682
  /// Computes and returns a list of all child keypaths in the current animation.
727
683
  /// The returned list is the same as the log output of `logHierarchyKeypaths()`
728
684
  public func allHierarchyKeypaths() -> [String] {
729
- animationLayer?.allHierarchyKeypaths() ?? []
685
+ lottieAnimationLayer.allHierarchyKeypaths()
730
686
  }
731
687
 
732
688
  /// Searches for the nearest child layer to the first Keypath and adds the subview
@@ -751,12 +707,12 @@ open class LottieAnimationView: LottieAnimationViewBase {
751
707
  /// animationView.addSubview(subview, forLayerAt: layerKeypath)
752
708
  /// ```
753
709
  public func addSubview(_ subview: AnimationSubview, forLayerAt keypath: AnimationKeypath) {
754
- guard let sublayer = animationLayer?.layer(for: keypath) else {
710
+ guard let sublayer = lottieAnimationLayer.rootAnimationLayer?.layer(for: keypath) else {
755
711
  return
756
712
  }
757
713
  setNeedsLayout()
758
714
  layoutIfNeeded()
759
- forceDisplayUpdate()
715
+ lottieAnimationLayer.forceDisplayUpdate()
760
716
  addSubview(subview)
761
717
  if let subViewLayer = subview.viewLayer {
762
718
  sublayer.addSublayer(subViewLayer)
@@ -771,17 +727,10 @@ open class LottieAnimationView: LottieAnimationViewBase {
771
727
  /// - Parameter rect: The CGRect to convert.
772
728
  /// - Parameter toLayerAt: The keypath used to find the layer.
773
729
  public func convert(_ rect: CGRect, toLayerAt keypath: AnimationKeypath?) -> CGRect? {
774
- guard let animationLayer = animationLayer else { return nil }
775
- guard let keypath = keypath else {
776
- return viewLayer?.convert(rect, to: animationLayer)
777
- }
778
- guard let sublayer = animationLayer.layer(for: keypath) else {
779
- return nil
780
- }
730
+ let convertedRect = lottieAnimationLayer.convert(rect, toLayerAt: keypath)
781
731
  setNeedsLayout()
782
732
  layoutIfNeeded()
783
- forceDisplayUpdate()
784
- return animationLayer.convert(rect, to: sublayer)
733
+ return convertedRect
785
734
  }
786
735
 
787
736
  /// Converts a CGPoint from the LottieAnimationView's coordinate space into the
@@ -792,17 +741,10 @@ open class LottieAnimationView: LottieAnimationViewBase {
792
741
  /// - Parameter point: The CGPoint to convert.
793
742
  /// - Parameter toLayerAt: The keypath used to find the layer.
794
743
  public func convert(_ point: CGPoint, toLayerAt keypath: AnimationKeypath?) -> CGPoint? {
795
- guard let animationLayer = animationLayer else { return nil }
796
- guard let keypath = keypath else {
797
- return viewLayer?.convert(point, to: animationLayer)
798
- }
799
- guard let sublayer = animationLayer.layer(for: keypath) else {
800
- return nil
801
- }
744
+ let convertedRect = lottieAnimationLayer.convert(point, toLayerAt: keypath)
802
745
  setNeedsLayout()
803
746
  layoutIfNeeded()
804
- forceDisplayUpdate()
805
- return animationLayer.convert(point, to: sublayer)
747
+ return convertedRect
806
748
  }
807
749
 
808
750
  /// Sets the enabled state of all animator nodes found with the keypath search.
@@ -811,14 +753,7 @@ open class LottieAnimationView: LottieAnimationViewBase {
811
753
  /// - Parameter isEnabled: When true the animator nodes affect the rendering tree. When false the node is removed from the tree.
812
754
  /// - Parameter keypath: The keypath used to find the node(s).
813
755
  public func setNodeIsEnabled(isEnabled: Bool, keypath: AnimationKeypath) {
814
- guard let animationLayer = animationLayer else { return }
815
- let nodes = animationLayer.animatorNodes(for: keypath)
816
- if let nodes = nodes {
817
- for node in nodes {
818
- node.isEnabled = isEnabled
819
- }
820
- forceDisplayUpdate()
821
- }
756
+ lottieAnimationLayer.setNodeIsEnabled(isEnabled: isEnabled, keypath: keypath)
822
757
  }
823
758
 
824
759
  /// Markers are a way to describe a point in time by a key name.
@@ -830,10 +765,7 @@ open class LottieAnimationView: LottieAnimationViewBase {
830
765
  ///
831
766
  /// Returns the Progress Time for the marker named. Returns nil if no marker found.
832
767
  public func progressTime(forMarker named: String) -> AnimationProgressTime? {
833
- guard let animation = animation else {
834
- return nil
835
- }
836
- return animation.progressTime(forMarker: named)
768
+ lottieAnimationLayer.progressTime(forMarker: named)
837
769
  }
838
770
 
839
771
  /// Markers are a way to describe a point in time by a key name.
@@ -845,10 +777,7 @@ open class LottieAnimationView: LottieAnimationViewBase {
845
777
  ///
846
778
  /// Returns the Frame Time for the marker named. Returns nil if no marker found.
847
779
  public func frameTime(forMarker named: String) -> AnimationFrameTime? {
848
- guard let animation = animation else {
849
- return nil
850
- }
851
- return animation.frameTime(forMarker: named)
780
+ lottieAnimationLayer.frameTime(forMarker: named)
852
781
  }
853
782
 
854
783
  /// Markers are a way to describe a point in time and a duration by a key name.
@@ -860,32 +789,51 @@ open class LottieAnimationView: LottieAnimationViewBase {
860
789
  ///
861
790
  /// - Returns: The duration frame time for the marker, or `nil` if no marker found.
862
791
  public func durationFrameTime(forMarker named: String) -> AnimationFrameTime? {
863
- guard let animation = animation else {
864
- return nil
865
- }
866
- return animation.durationFrameTime(forMarker: named)
792
+ lottieAnimationLayer.durationFrameTime(forMarker: named)
867
793
  }
868
794
 
869
795
  // MARK: Internal
870
796
 
871
- var animationLayer: RootAnimationLayer? = nil
797
+ var animationLayer: RootAnimationLayer? {
798
+ lottieAnimationLayer.rootAnimationLayer
799
+ }
872
800
 
873
801
  /// Set animation name from Interface Builder
874
802
  @IBInspectable var animationName: String? {
875
803
  didSet {
876
- self.animation = animationName.flatMap {
877
- LottieAnimation.named($0, animationCache: nil)
804
+ self.lottieAnimationLayer.animation = animationName.flatMap { LottieAnimation.named($0, animationCache: nil)
878
805
  }
879
806
  }
880
807
  }
881
808
 
809
+ override func commonInit() {
810
+ super.commonInit()
811
+ lottieAnimationLayer.screenScale = screenScale
812
+ viewLayer?.addSublayer(lottieAnimationLayer)
813
+
814
+ lottieAnimationLayer.animationLoaded = { [weak self] _, animation in
815
+ guard let self = self else { return }
816
+ self.animationLoaded?(self, animation)
817
+ self.invalidateIntrinsicContentSize()
818
+ self.setNeedsLayout()
819
+ }
820
+
821
+ lottieAnimationLayer.animationLayerDidLoad = { [weak self] _, _ in
822
+ guard let self = self else { return }
823
+ self.invalidateIntrinsicContentSize()
824
+ self.setNeedsLayout()
825
+ }
826
+ }
827
+
882
828
  override func layoutAnimation() {
883
- guard let animation = animation, let animationLayer = animationLayer else { return }
829
+ guard let animation = lottieAnimationLayer.animation, let animationLayer = lottieAnimationLayer.animationLayer else { return }
830
+
884
831
  var position = animation.bounds.center
885
832
  let xform: CATransform3D
886
833
  var shouldForceUpdates = false
887
834
 
888
835
  if let viewportFrame = viewportFrame {
836
+ setNeedsLayout()
889
837
  shouldForceUpdates = contentMode == .redraw
890
838
 
891
839
  let compAspect = viewportFrame.size.width / viewportFrame.size.height
@@ -904,7 +852,7 @@ open class LottieAnimationView: LottieAnimationViewBase {
904
852
  xform = CATransform3DMakeScale(
905
853
  bounds.size.width / animation.size.width,
906
854
  bounds.size.height / animation.size.height,
907
- 1);
855
+ 1)
908
856
  case .scaleAspectFit:
909
857
  position = bounds.center
910
858
  let compAspect = animation.size.width / animation.size.height
@@ -955,7 +903,7 @@ open class LottieAnimationView: LottieAnimationViewBase {
955
903
  position.y = bounds.maxY - animation.bounds.midY
956
904
  xform = CATransform3DIdentity
957
905
 
958
- #if os(iOS) || os(tvOS)
906
+ #if canImport(UIKit)
959
907
  @unknown default:
960
908
  logger.assertionFailure("unsupported contentMode: \(contentMode.rawValue)")
961
909
  xform = CATransform3DIdentity
@@ -969,7 +917,7 @@ open class LottieAnimationView: LottieAnimationViewBase {
969
917
  // If layout is changed without animation, explicitly set animation duration to 0.0
970
918
  // inside CATransaction to avoid unwanted artifacts.
971
919
  /// Check if any animation exist on the view's layer, and match it.
972
- if let key = viewLayer?.animationKeys()?.first, let animation = viewLayer?.animation(forKey: key) {
920
+ if let key = lottieAnimationLayer.animationKeys()?.first, let animation = lottieAnimationLayer.animation(forKey: key) {
973
921
  // The layout is happening within an animation block. Grab the animation data.
974
922
 
975
923
  let positionKey = "LayoutPositionAnimation"
@@ -993,11 +941,7 @@ open class LottieAnimationView: LottieAnimationViewBase {
993
941
 
994
942
  animationLayer.position = position
995
943
  animationLayer.transform = xform
996
- #if os(OSX)
997
- animationLayer.anchorPoint = layer?.anchorPoint ?? CGPoint.zero
998
- #else
999
- animationLayer.anchorPoint = layer.anchorPoint
1000
- #endif
944
+ animationLayer.anchorPoint = lottieAnimationLayer.anchorPoint
1001
945
  animationLayer.add(positionAnimation, forKey: positionKey)
1002
946
  animationLayer.add(xformAnimation, forKey: transformKey)
1003
947
  } else {
@@ -1020,38 +964,21 @@ open class LottieAnimationView: LottieAnimationViewBase {
1020
964
  }
1021
965
 
1022
966
  if shouldForceUpdates {
1023
- animationLayer.forceDisplayUpdate()
967
+ lottieAnimationLayer.forceDisplayUpdate()
1024
968
  }
1025
969
  }
1026
970
 
1027
971
  func updateRasterizationState() {
1028
- if isAnimationPlaying {
1029
- animationLayer?.shouldRasterize = false
972
+ if lottieAnimationLayer.isAnimationPlaying {
973
+ lottieAnimationLayer.animationLayer?.shouldRasterize = false
1030
974
  } else {
1031
- animationLayer?.shouldRasterize = shouldRasterizeWhenIdle
975
+ lottieAnimationLayer.animationLayer?.shouldRasterize = lottieAnimationLayer.shouldRasterizeWhenIdle
1032
976
  }
1033
977
  }
1034
978
 
1035
979
  /// Updates the animation frame. Does not affect any current animations
1036
980
  func updateAnimationFrame(_ newFrame: CGFloat) {
1037
- // In performance tests, we have to wrap the animation view setup
1038
- // in a `CATransaction` in order for the layers to be deallocated at
1039
- // the correct time. The `CATransaction`s in this method interfere
1040
- // with the ones managed by the performance test, and aren't actually
1041
- // necessary in a headless environment, so we disable them.
1042
- if TestHelpers.performanceTestsAreRunning {
1043
- animationLayer?.currentFrame = newFrame
1044
- animationLayer?.forceDisplayUpdate()
1045
- return
1046
- }
1047
-
1048
- CATransaction.begin()
1049
- CATransaction.setCompletionBlock {
1050
- self.animationLayer?.forceDisplayUpdate()
1051
- }
1052
- CATransaction.setDisableActions(true)
1053
- animationLayer?.currentFrame = newFrame
1054
- CATransaction.commit()
981
+ lottieAnimationLayer.updateAnimationFrame(newFrame)
1055
982
  }
1056
983
 
1057
984
  @objc
@@ -1076,438 +1003,34 @@ open class LottieAnimationView: LottieAnimationViewBase {
1076
1003
  }
1077
1004
  }
1078
1005
 
1079
- /// Updates an in flight animation.
1080
1006
  func updateInFlightAnimation() {
1081
- guard let animationContext = animationContext else { return }
1082
-
1083
- guard animationContext.closure.animationState != .complete else {
1084
- // Tried to re-add an already completed animation. Cancel.
1085
- self.animationContext = nil
1086
- return
1087
- }
1088
-
1089
- /// Tell existing context to ignore its closure
1090
- animationContext.closure.ignoreDelegate = true
1091
-
1092
- /// Make a new context, stealing the completion block from the previous.
1093
- let newContext = AnimationContext(
1094
- playFrom: animationContext.playFrom,
1095
- playTo: animationContext.playTo,
1096
- closure: animationContext.closure.completionBlock)
1097
-
1098
- /// Remove current animation, and freeze the current frame.
1099
- let pauseFrame = realtimeAnimationFrame
1100
- animationLayer?.removeAnimation(forKey: activeAnimationName)
1101
- animationLayer?.currentFrame = pauseFrame
1007
+ lottieAnimationLayer.updateInFlightAnimation()
1008
+ }
1102
1009
 
1103
- addNewAnimationForContext(newContext)
1010
+ func loadAnimation(_ animationSource: LottieAnimationSource?) {
1011
+ lottieAnimationLayer.loadAnimation(animationSource)
1104
1012
  }
1105
1013
 
1106
1014
  // MARK: Fileprivate
1107
1015
 
1108
- /// Context describing the animation that is currently playing in this `LottieAnimationView`
1109
- /// - When non-nil, an animation is currently playing in this view. Otherwise,
1110
- /// the view is paused on a specific frame.
1111
- fileprivate var animationContext: AnimationContext?
1112
-
1113
- fileprivate var _activeAnimationName: String = LottieAnimationView.animationName
1114
- fileprivate var animationID = 0
1115
-
1116
1016
  fileprivate var waitingToPlayAnimation = false
1117
1017
 
1118
- fileprivate var activeAnimationName: String {
1119
- switch animationLayer?.primaryAnimationKey {
1120
- case .specific(let animationKey):
1121
- return animationKey
1122
- case .managed, nil:
1123
- return _activeAnimationName
1124
- }
1125
- }
1126
-
1127
- fileprivate func makeAnimationLayer(usingEngine renderingEngine: RenderingEngineOption) {
1128
- /// Remove current animation if any
1129
- removeCurrentAnimation()
1130
-
1131
- if let oldAnimation = animationLayer {
1132
- oldAnimation.removeFromSuperlayer()
1133
- animationLayer = nil
1134
- }
1135
-
1136
- invalidateIntrinsicContentSize()
1137
-
1138
- guard let animation = animation else {
1139
- return
1140
- }
1141
-
1142
- let rootAnimationLayer: RootAnimationLayer?
1143
- switch renderingEngine {
1144
- case .automatic:
1145
- rootAnimationLayer = makeAutomaticEngineLayer(for: animation)
1146
- case .specific(.coreAnimation):
1147
- rootAnimationLayer = makeCoreAnimationLayer(for: animation)
1148
- case .specific(.mainThread):
1149
- rootAnimationLayer = makeMainThreadAnimationLayer(for: animation)
1150
- }
1151
-
1152
- guard let animationLayer = rootAnimationLayer else {
1153
- return
1154
- }
1155
-
1156
- animationLayer.animationView = self
1157
- animationLayer.renderScale = screenScale
1158
-
1159
- viewLayer?.addSublayer(animationLayer)
1160
- self.animationLayer = animationLayer
1161
- reloadImages()
1162
- animationLayer.setNeedsDisplay()
1163
- setNeedsLayout()
1164
- currentFrame = CGFloat(animation.startFrame)
1165
- }
1166
-
1167
- fileprivate func makeMainThreadAnimationLayer(for animation: LottieAnimation) -> MainThreadAnimationLayer {
1168
- MainThreadAnimationLayer(
1169
- animation: animation,
1170
- imageProvider: imageProvider.cachedImageProvider,
1171
- textProvider: textProvider,
1172
- fontProvider: fontProvider,
1173
- maskAnimationToBounds: maskAnimationToBounds,
1174
- logger: logger)
1175
- }
1176
-
1177
- fileprivate func makeCoreAnimationLayer(for animation: LottieAnimation) -> CoreAnimationLayer? {
1178
- do {
1179
- let coreAnimationLayer = try CoreAnimationLayer(
1180
- animation: animation,
1181
- imageProvider: imageProvider.cachedImageProvider,
1182
- textProvider: textProvider,
1183
- fontProvider: fontProvider,
1184
- maskAnimationToBounds: maskAnimationToBounds,
1185
- compatibilityTrackerMode: .track,
1186
- logger: logger)
1187
-
1188
- coreAnimationLayer.didSetUpAnimation = { [logger] compatibilityIssues in
1189
- logger.assert(
1190
- compatibilityIssues.isEmpty,
1191
- "Encountered Core Animation compatibility issues while setting up animation:\n"
1192
- + compatibilityIssues.map { $0.description }.joined(separator: "\n") + "\n\n"
1193
- + """
1194
- This animation cannot be rendered correctly by the Core Animation engine.
1195
- To resolve this issue, you can use `RenderingEngineOption.automatic`, which automatically falls back
1196
- to the Main Thread rendering engine when necessary, or just use `RenderingEngineOption.mainThread`.
1197
-
1198
- """)
1199
- }
1200
-
1201
- return coreAnimationLayer
1202
- } catch {
1203
- // This should never happen, because we initialize the `CoreAnimationLayer` with
1204
- // `CompatibilityTracker.Mode.track` (which reports errors in `didSetUpAnimation`,
1205
- // not by throwing).
1206
- logger.assertionFailure("Encountered unexpected error \(error)")
1207
- return nil
1208
- }
1209
- }
1210
-
1211
- fileprivate func makeAutomaticEngineLayer(for animation: LottieAnimation) -> CoreAnimationLayer? {
1212
- do {
1213
- // Attempt to set up the Core Animation layer. This can either throw immediately in `init`,
1214
- // or throw an error later in `CALayer.display()` that will be reported in `didSetUpAnimation`.
1215
- let coreAnimationLayer = try CoreAnimationLayer(
1216
- animation: animation,
1217
- imageProvider: imageProvider.cachedImageProvider,
1218
- textProvider: textProvider,
1219
- fontProvider: fontProvider,
1220
- maskAnimationToBounds: maskAnimationToBounds,
1221
- compatibilityTrackerMode: .abort,
1222
- logger: logger)
1223
-
1224
- coreAnimationLayer.didSetUpAnimation = { [weak self] issues in
1225
- self?.automaticEngineLayerDidSetUpAnimation(issues)
1226
- }
1227
-
1228
- return coreAnimationLayer
1229
- } catch {
1230
- if case CompatibilityTracker.Error.encounteredCompatibilityIssue(let compatibilityIssue) = error {
1231
- automaticEngineLayerDidSetUpAnimation([compatibilityIssue])
1232
- } else {
1233
- // This should never happen, because we expect `CoreAnimationLayer` to only throw
1234
- // `CompatibilityTracker.Error.encounteredCompatibilityIssue` errors.
1235
- logger.assertionFailure("Encountered unexpected error \(error)")
1236
- automaticEngineLayerDidSetUpAnimation([])
1237
- }
1238
-
1239
- return nil
1240
- }
1241
- }
1242
-
1243
- // Handles any compatibility issues with the Core Animation engine
1244
- // by falling back to the Main Thread engine
1245
- fileprivate func automaticEngineLayerDidSetUpAnimation(_ compatibilityIssues: [CompatibilityIssue]) {
1246
- // If there weren't any compatibility issues, then there's nothing else to do
1247
- if compatibilityIssues.isEmpty {
1248
- return
1249
- }
1250
-
1251
- logger.warn(
1252
- "Encountered Core Animation compatibility issue while setting up animation:\n"
1253
- + compatibilityIssues.map { $0.description }.joined(separator: "\n") + "\n"
1254
- + """
1255
- This animation may have additional compatibility issues, but animation setup was cancelled early to avoid wasted work.
1256
-
1257
- Automatically falling back to Main Thread rendering engine. This fallback comes with some additional performance
1258
- overhead, which can be reduced by manually specifying that this animation should always use the Main Thread engine.
1259
-
1260
- """)
1261
-
1262
- let animationContext = animationContext
1263
- let currentFrame = currentFrame
1264
-
1265
- // Disable the completion handler delegate before tearing down the `CoreAnimationLayer`
1266
- // and building the `MainThreadAnimationLayer`. Otherwise deinitializing the
1267
- // `CoreAnimationLayer` would trigger the animation completion handler even though
1268
- // the animation hasn't even started playing yet.
1269
- animationContext?.closure.ignoreDelegate = true
1270
-
1271
- makeAnimationLayer(usingEngine: .mainThread)
1272
-
1273
- // Set up the Main Thread animation layer using the same configuration that
1274
- // was being used by the previous Core Animation layer
1275
- self.currentFrame = currentFrame
1276
-
1277
- if let animationContext = animationContext {
1278
- // `AnimationContext.closure` (`AnimationCompletionDelegate`) is a reference type
1279
- // that is the animation layer's `CAAnimationDelegate`, and holds a reference to
1280
- // the animation layer. Reusing a single instance across different animation layers
1281
- // can cause the animation setup to fail, so we create a copy of the `animationContext`:
1282
- addNewAnimationForContext(AnimationContext(
1283
- playFrom: animationContext.playFrom,
1284
- playTo: animationContext.playTo,
1285
- closure: animationContext.closure.completionBlock))
1286
- }
1287
- }
1288
-
1289
1018
  fileprivate func updateAnimationForBackgroundState() {
1290
- if let currentContext = animationContext {
1291
- switch backgroundBehavior {
1292
- case .stop:
1293
- removeCurrentAnimation()
1294
- updateAnimationFrame(currentContext.playFrom)
1295
- case .pause:
1296
- removeCurrentAnimation()
1297
- case .pauseAndRestore:
1298
- currentContext.closure.ignoreDelegate = true
1299
- removeCurrentAnimation()
1300
- /// Keep the stale context around for when the app enters the foreground.
1301
- animationContext = currentContext
1302
- case .forceFinish:
1303
- removeCurrentAnimation()
1304
- updateAnimationFrame(currentContext.playTo)
1305
- case .continuePlaying:
1306
- break
1307
- }
1308
- }
1019
+ lottieAnimationLayer.updateAnimationForBackgroundState()
1309
1020
  }
1310
1021
 
1311
1022
  fileprivate func updateAnimationForForegroundState() {
1312
- if let currentContext = animationContext {
1313
- if waitingToPlayAnimation {
1314
- waitingToPlayAnimation = false
1315
- addNewAnimationForContext(currentContext)
1316
- } else if backgroundBehavior == .pauseAndRestore {
1317
- /// Restore animation from saved state
1318
- updateInFlightAnimation()
1319
- }
1023
+ let wasWaitingToPlayAnimation = waitingToPlayAnimation
1024
+ if waitingToPlayAnimation {
1025
+ waitingToPlayAnimation = false
1320
1026
  }
1321
- }
1322
-
1323
- /// Removes the current animation and pauses the animation at the current frame
1324
- /// if necessary before setting up a new animation.
1325
- /// - This is not necessary with the Core Animation engine, and skipping
1326
- /// this step lets us avoid building the animations twice (once paused
1327
- /// and once again playing)
1328
- /// - This method should only be called immediately before setting up another
1329
- /// animation -- otherwise this LottieAnimationView could be put in an inconsistent state.
1330
- fileprivate func removeCurrentAnimationIfNecessary() {
1331
- switch currentRenderingEngine {
1332
- case .mainThread:
1333
- removeCurrentAnimation()
1334
- case .coreAnimation, nil:
1335
- // We still need to remove the `animationContext`, since it should only be present
1336
- // when an animation is actually playing. Without this calling `removeCurrentAnimationIfNecessary()`
1337
- // and then setting the animation to a specific paused frame would put this
1338
- // `LottieAnimationView` in an inconsistent state.
1339
- animationContext = nil
1340
- }
1341
- }
1342
-
1343
- /// Stops the current in flight animation and freezes the animation in its current state.
1344
- fileprivate func removeCurrentAnimation() {
1345
- guard animationContext != nil else { return }
1346
- let pauseFrame = realtimeAnimationFrame
1347
- animationLayer?.removeAnimation(forKey: activeAnimationName)
1348
- updateAnimationFrame(pauseFrame)
1349
- animationContext = nil
1350
- }
1351
-
1352
- /// Adds animation to animation layer and sets the delegate. If animation layer or animation are nil, exits.
1353
- fileprivate func addNewAnimationForContext(_ animationContext: AnimationContext) {
1354
- guard let animationlayer = animationLayer, let animation = animation else {
1355
- return
1356
- }
1357
-
1358
- self.animationContext = animationContext
1359
-
1360
- switch currentRenderingEngine {
1361
- case .mainThread:
1362
- guard window != nil else {
1363
- waitingToPlayAnimation = true
1364
- return
1365
- }
1366
-
1367
- case .coreAnimation, nil:
1368
- // The Core Animation engine automatically batches animation setup to happen
1369
- // in `CALayer.display()`, which won't be called until the layer is on-screen,
1370
- // so we don't need to defer animation setup at this layer.
1371
- break
1372
- }
1373
-
1374
- animationID = animationID + 1
1375
- _activeAnimationName = LottieAnimationView.animationName + String(animationID)
1376
-
1377
- if let coreAnimationLayer = animationlayer as? CoreAnimationLayer {
1378
- var animationContext = animationContext
1379
-
1380
- // Core Animation doesn't natively support negative speed values,
1381
- // so instead we can swap `playFrom` / `playTo`
1382
- if animationSpeed < 0 {
1383
- let temp = animationContext.playFrom
1384
- animationContext.playFrom = animationContext.playTo
1385
- animationContext.playTo = temp
1386
- }
1387
-
1388
- var timingConfiguration = CoreAnimationLayer.CAMediaTimingConfiguration(
1389
- autoreverses: loopMode.caAnimationConfiguration.autoreverses,
1390
- repeatCount: loopMode.caAnimationConfiguration.repeatCount,
1391
- speed: abs(Float(animationSpeed)))
1392
-
1393
- // The animation should start playing from the `currentFrame`,
1394
- // if `currentFrame` is included in the time range being played.
1395
- let lowerBoundTime = min(animationContext.playFrom, animationContext.playTo)
1396
- let upperBoundTime = max(animationContext.playFrom, animationContext.playTo)
1397
- if (lowerBoundTime ..< upperBoundTime).contains(round(currentFrame)) {
1398
- // We have to configure this differently depending on the loop mode:
1399
- switch loopMode {
1400
- // When playing exactly once (and not looping), we can just set the
1401
- // `playFrom` time to be the `currentFrame`. Since the animation duration
1402
- // is based on `playFrom` and `playTo`, this automatically truncates the
1403
- // duration (so the animation stops playing at `playFrom`).
1404
- // - Don't do this if the animation is already at that frame
1405
- // (e.g. playing from 100% to 0% when the animation is already at 0%)
1406
- // since that would cause the animation to not play at all.
1407
- case .playOnce:
1408
- if animationContext.playTo != currentFrame {
1409
- animationContext.playFrom = currentFrame
1410
- }
1411
-
1412
- // When looping, we specifically _don't_ want to affect the duration of the animation,
1413
- // since that would affect the duration of all subsequent loops. We just want to adjust
1414
- // the duration of the _first_ loop. Instead of setting `playFrom`, we just add a `timeOffset`
1415
- // so the first loop begins at `currentTime` but all subsequent loops are the standard duration.
1416
- default:
1417
- if animationSpeed < 0 {
1418
- timingConfiguration.timeOffset = animation.time(forFrame: animationContext.playFrom) - currentTime
1419
- } else {
1420
- timingConfiguration.timeOffset = currentTime - animation.time(forFrame: animationContext.playFrom)
1421
- }
1422
- }
1423
- }
1424
-
1425
- // If attempting to play a zero-duration animation, just pause on that single frame instead
1426
- if animationContext.playFrom == animationContext.playTo {
1427
- currentFrame = animationContext.playTo
1428
- animationContext.closure.completionBlock?(true)
1429
- return
1430
- }
1431
-
1432
- coreAnimationLayer.playAnimation(configuration: .init(
1433
- animationContext: animationContext,
1434
- timingConfiguration: timingConfiguration))
1435
-
1436
- return
1437
- }
1438
-
1439
- /// At this point there is no animation on animationLayer and its state is set.
1440
-
1441
- let framerate = animation.framerate
1442
-
1443
- let playFrom = animationContext.playFrom.clamp(animation.startFrame, animation.endFrame)
1444
- let playTo = animationContext.playTo.clamp(animation.startFrame, animation.endFrame)
1445
-
1446
- let duration = ((max(playFrom, playTo) - min(playFrom, playTo)) / CGFloat(framerate))
1447
-
1448
- let playingForward: Bool =
1449
- (
1450
- (animationSpeed > 0 && playFrom < playTo) ||
1451
- (animationSpeed < 0 && playTo < playFrom))
1452
-
1453
- var startFrame = currentFrame.clamp(min(playFrom, playTo), max(playFrom, playTo))
1454
- if startFrame == playTo {
1455
- startFrame = playFrom
1456
- }
1457
-
1458
- let timeOffset: TimeInterval = playingForward
1459
- ? Double(startFrame - min(playFrom, playTo)) / framerate
1460
- : Double(max(playFrom, playTo) - startFrame) / framerate
1461
-
1462
- let layerAnimation = CABasicAnimation(keyPath: "currentFrame")
1463
- layerAnimation.fromValue = playFrom
1464
- layerAnimation.toValue = playTo
1465
- layerAnimation.speed = Float(animationSpeed)
1466
- layerAnimation.duration = TimeInterval(duration)
1467
- layerAnimation.fillMode = CAMediaTimingFillMode.both
1468
- layerAnimation.repeatCount = loopMode.caAnimationConfiguration.repeatCount
1469
- layerAnimation.autoreverses = loopMode.caAnimationConfiguration.autoreverses
1470
-
1471
- layerAnimation.isRemovedOnCompletion = false
1472
- if timeOffset != 0 {
1473
- let currentLayerTime = viewLayer?.convertTime(CACurrentMediaTime(), from: nil) ?? 0
1474
- layerAnimation.beginTime = currentLayerTime - (timeOffset * 1 / Double(abs(animationSpeed)))
1475
- }
1476
- layerAnimation.delegate = animationContext.closure
1477
- animationContext.closure.animationLayer = animationlayer
1478
- animationContext.closure.animationKey = activeAnimationName
1479
-
1480
- animationlayer.add(layerAnimation, forKey: activeAnimationName)
1481
- updateRasterizationState()
1027
+ lottieAnimationLayer.updateAnimationForForegroundState(wasWaitingToPlayAnimation: wasWaitingToPlayAnimation)
1482
1028
  }
1483
1029
 
1484
1030
  // MARK: Private
1485
1031
 
1486
- static private let animationName = "Lottie"
1032
+ // The backing CALayer for this animation view.
1033
+ private let lottieAnimationLayer: LottieAnimationLayer
1487
1034
 
1488
1035
  private let logger: LottieLogger
1489
-
1490
- /// The `LottieBackgroundBehavior` that was specified manually by setting `self.backgroundBehavior`
1491
- private var _backgroundBehavior: LottieBackgroundBehavior?
1492
-
1493
- }
1494
-
1495
- // MARK: - LottieLoopMode + caAnimationConfiguration
1496
-
1497
- extension LottieLoopMode {
1498
- /// The `CAAnimation` configuration that reflects this mode
1499
- var caAnimationConfiguration: (repeatCount: Float, autoreverses: Bool) {
1500
- switch self {
1501
- case .playOnce:
1502
- return (repeatCount: 1, autoreverses: false)
1503
- case .loop:
1504
- return (repeatCount: .greatestFiniteMagnitude, autoreverses: false)
1505
- case .autoReverse:
1506
- return (repeatCount: .greatestFiniteMagnitude, autoreverses: true)
1507
- case .repeat(let amount):
1508
- return (repeatCount: amount, autoreverses: false)
1509
- case .repeatBackwards(let amount):
1510
- return (repeatCount: amount, autoreverses: true)
1511
- }
1512
- }
1513
1036
  }