lottie-ios 4.1.2 → 4.2.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 (96) hide show
  1. package/.github/workflows/main.yml +27 -9
  2. package/Lottie.xcodeproj/project.pbxproj +158 -70
  3. package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +2 -2
  4. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/IDEFindNavigatorScopes.plist +5 -0
  5. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  6. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +258 -0
  7. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +13 -2
  8. package/Package.swift +2 -1
  9. package/README.md +3 -3
  10. package/Rakefile +8 -4
  11. package/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift +16 -2
  12. package/Sources/Private/CoreAnimation/Animations/CombinedShapeAnimation.swift +1 -1
  13. package/Sources/Private/CoreAnimation/Animations/CustomPathAnimation.swift +1 -1
  14. package/Sources/Private/CoreAnimation/Animations/EllipseAnimation.swift +1 -1
  15. package/Sources/Private/CoreAnimation/Animations/GradientAnimations.swift +6 -6
  16. package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +76 -7
  17. package/Sources/Private/CoreAnimation/Animations/OpacityAnimation.swift +1 -1
  18. package/Sources/Private/CoreAnimation/Animations/RectangleAnimation.swift +1 -1
  19. package/Sources/Private/CoreAnimation/Animations/ShapeAnimation.swift +66 -102
  20. package/Sources/Private/CoreAnimation/Animations/StarAnimation.swift +2 -2
  21. package/Sources/Private/CoreAnimation/Animations/StrokeAnimation.swift +3 -3
  22. package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +66 -17
  23. package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +55 -32
  24. package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +16 -12
  25. package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +3 -3
  26. package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +24 -11
  27. package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +2 -2
  28. package/Sources/Private/CoreAnimation/Layers/PreCompLayer.swift +1 -1
  29. package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +13 -2
  30. package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +9 -1
  31. package/Sources/Private/CoreAnimation/ValueProviderStore.swift +22 -11
  32. package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +1 -1
  33. package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +13 -2
  34. package/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.swift +16 -7
  35. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +1 -1
  36. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +2 -2
  37. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/RectNode.swift +1 -1
  38. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/StarNode.swift +2 -2
  39. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +20 -8
  40. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift +1 -1
  41. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +1 -1
  42. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +1 -1
  43. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +1 -1
  44. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +28 -9
  45. package/Sources/Private/Model/Assets/ImageAsset.swift +4 -3
  46. package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +2 -8
  47. package/Sources/Private/Model/DotLottie/DotLottieManifest.swift +3 -14
  48. package/Sources/Private/Model/DotLottie/DotLottieUtils.swift +11 -1
  49. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+BackingConfiguration.swift +147 -0
  50. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+Helpers.swift +351 -0
  51. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+MemoryFile.swift +183 -0
  52. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+Progress.swift +66 -0
  53. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+Reading.swift +144 -0
  54. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+ReadingDeprecated.swift +49 -0
  55. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+Writing.swift +385 -0
  56. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+WritingDeprecated.swift +91 -0
  57. package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+ZIP64.swift +170 -0
  58. package/Sources/Private/Model/DotLottie/{Zip/ZipArchive.swift → ZipFoundation/Archive.swift} +150 -227
  59. package/Sources/Private/Model/DotLottie/ZipFoundation/Data+Compression.swift +403 -0
  60. package/Sources/Private/Model/DotLottie/ZipFoundation/Data+CompressionDeprecated.swift +44 -0
  61. package/Sources/Private/Model/DotLottie/{Zip → ZipFoundation}/Data+Serialization.swift +62 -0
  62. package/Sources/Private/Model/DotLottie/{Zip/ZipEntry+Serialization.swift → ZipFoundation/Entry+Serialization.swift} +7 -7
  63. package/Sources/Private/Model/DotLottie/{Zip/ZipEntry+ZIP64.swift → ZipFoundation/Entry+ZIP64.swift} +13 -19
  64. package/Sources/Private/Model/DotLottie/{Zip/ZipEntry.swift → ZipFoundation/Entry.swift} +141 -10
  65. package/Sources/Private/Model/DotLottie/ZipFoundation/FileManager+ZIP.swift +368 -0
  66. package/Sources/Private/Model/DotLottie/ZipFoundation/README.md +24 -0
  67. package/Sources/Private/Model/DotLottie/ZipFoundation/URL+ZIP.swift +32 -0
  68. package/Sources/Private/Model/Extensions/Bundle.swift +5 -14
  69. package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +31 -8
  70. package/Sources/Private/Model/Objects/Transform.swift +58 -17
  71. package/Sources/Private/Model/ShapeItems/Repeater.swift +41 -7
  72. package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +61 -7
  73. package/Sources/Private/Model/Text/TextAnimator.swift +37 -5
  74. package/Sources/Private/RootAnimationLayer.swift +3 -1
  75. package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +12 -4
  76. package/Sources/Private/Utility/Extensions/DataExtension.swift +14 -4
  77. package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +11 -0
  78. package/Sources/Private/Utility/Primitives/ColorExtension.swift +10 -13
  79. package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +28 -6
  80. package/Sources/Public/Animation/LottieAnimationHelpers.swift +12 -10
  81. package/Sources/Public/Animation/LottieAnimationView.swift +213 -186
  82. package/Sources/Public/DotLottie/DotLottieFile.swift +11 -34
  83. package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +101 -74
  84. package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +90 -0
  85. package/Sources/Public/iOS/LottieAnimationViewBase.swift +1 -1
  86. package/Sources/Public/macOS/LottieAnimationViewBase.macOS.swift +1 -1
  87. package/lottie-ios.podspec +1 -1
  88. package/package.json +1 -1
  89. package/LottieAnimation/LottieAnimation.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  90. package/LottieAnimation/LottieAnimation.xcodeproj/project.xcworkspace/xcuserdata/valentinperignon.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  91. package/LottieAnimation/LottieAnimation.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  92. package/LottieAnimation/LottieAnimation.xcodeproj/xcuserdata/valentinperignon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +0 -6
  93. package/LottieAnimation/LottieAnimation.xcodeproj/xcuserdata/valentinperignon.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  94. package/Sources/Private/Model/DotLottie/Zip/Data+Compression.swift +0 -134
  95. package/Sources/Private/Model/DotLottie/Zip/FileManager+ZIP.swift +0 -130
  96. package/Sources/Private/Utility/Interpolatable/KeyframeGroup+Extensions.swift +0 -59
@@ -246,6 +246,10 @@ extension CGSize {
246
246
 
247
247
  extension CATransform3D {
248
248
 
249
+ enum Axis {
250
+ case x, y, z
251
+ }
252
+
249
253
  static func makeSkew(skew: CGFloat, skewAxis: CGFloat) -> CATransform3D {
250
254
  let mCos = cos(skewAxis.toRadians())
251
255
  let mSin = sin(skewAxis.toRadians())
@@ -311,20 +315,37 @@ extension CATransform3D {
311
315
  anchor: CGPoint,
312
316
  position: CGPoint,
313
317
  scale: CGSize,
314
- rotation: CGFloat,
318
+ rotationX: CGFloat,
319
+ rotationY: CGFloat,
320
+ rotationZ: CGFloat,
315
321
  skew: CGFloat?,
316
322
  skewAxis: CGFloat?)
317
323
  -> CATransform3D
318
324
  {
319
325
  if let skew = skew, let skewAxis = skewAxis {
320
- return CATransform3DMakeTranslation(position.x, position.y, 0).rotated(rotation).skewed(skew: -skew, skewAxis: skewAxis)
321
- .scaled(scale * 0.01).translated(anchor * -1)
326
+ return CATransform3DMakeTranslation(position.x, position.y, 0)
327
+ .rotated(rotationX, axis: .x)
328
+ .rotated(rotationY, axis: .y)
329
+ .rotated(rotationZ, axis: .z)
330
+ .skewed(skew: -skew, skewAxis: skewAxis)
331
+ .scaled(scale * 0.01)
332
+ .translated(anchor * -1)
322
333
  }
323
- return CATransform3DMakeTranslation(position.x, position.y, 0).rotated(rotation).scaled(scale * 0.01).translated(anchor * -1)
334
+ return CATransform3DMakeTranslation(position.x, position.y, 0)
335
+ .rotated(rotationX, axis: .x)
336
+ .rotated(rotationY, axis: .y)
337
+ .rotated(rotationZ, axis: .z)
338
+ .scaled(scale * 0.01)
339
+ .translated(anchor * -1)
324
340
  }
325
341
 
326
- func rotated(_ degrees: CGFloat) -> CATransform3D {
327
- CATransform3DRotate(self, degrees.toRadians(), 0, 0, 1)
342
+ func rotated(_ degrees: CGFloat, axis: Axis) -> CATransform3D {
343
+ CATransform3DRotate(
344
+ self,
345
+ degrees.toRadians(),
346
+ axis == .x ? 1 : 0,
347
+ axis == .y ? 1 : 0,
348
+ axis == .z ? 1 : 0)
328
349
  }
329
350
 
330
351
  func translated(_ translation: CGPoint) -> CATransform3D {
@@ -338,4 +359,5 @@ extension CATransform3D {
338
359
  func skewed(skew: CGFloat, skewAxis: CGFloat) -> CATransform3D {
339
360
  CATransform3DConcat(CATransform3D.makeSkew(skew: skew, skewAxis: skewAxis), self)
340
361
  }
362
+
341
363
  }
@@ -59,9 +59,7 @@ extension LottieAnimation {
59
59
 
60
60
  do {
61
61
  /// Decode animation.
62
- guard let json = try bundle.getAnimationData(name, subdirectory: subdirectory) else {
63
- return nil
64
- }
62
+ let json = try bundle.getAnimationData(name, subdirectory: subdirectory)
65
63
  let animation = try LottieAnimation.from(data: json)
66
64
  animationCache?.setAnimation(animation, forKey: cacheKey)
67
65
  return animation
@@ -97,7 +95,10 @@ extension LottieAnimation {
97
95
  animationCache?.setAnimation(animation, forKey: filepath)
98
96
  return animation
99
97
  } catch {
100
- /// Decoding Error.
98
+ LottieLogger.shared.warn("""
99
+ Failed to load animation from filepath \(filepath)
100
+ with underlying error: \(error.localizedDescription)
101
+ """)
101
102
  return nil
102
103
  }
103
104
  }
@@ -125,18 +126,19 @@ extension LottieAnimation {
125
126
  return animation
126
127
  }
127
128
 
128
- /// Load jsonData from Asset
129
- guard let json = Data.jsonData(from: name, in: bundle) else {
130
- return nil
131
- }
132
-
133
129
  do {
130
+ /// Load jsonData from Asset
131
+ let json = try Data(assetName: name, in: bundle)
134
132
  /// Decode animation.
135
133
  let animation = try LottieAnimation.from(data: json)
136
134
  animationCache?.setAnimation(animation, forKey: cacheKey)
137
135
  return animation
138
136
  } catch {
139
- /// Decoding error.
137
+ LottieLogger.shared.warn("""
138
+ Failed to load animation with asset name \(name)
139
+ in \(bundle.bundlePath)
140
+ with underlying error: \(error.localizedDescription)
141
+ """)
140
142
  return nil
141
143
  }
142
144
  }
@@ -88,7 +88,7 @@ extension LottieLoopMode: Equatable {
88
88
  // MARK: - LottieAnimationView
89
89
 
90
90
  @IBDesignable
91
- final public class LottieAnimationView: LottieAnimationViewBase {
91
+ open class LottieAnimationView: LottieAnimationViewBase {
92
92
 
93
93
  // MARK: Lifecycle
94
94
 
@@ -178,6 +178,166 @@ final public class LottieAnimationView: LottieAnimationViewBase {
178
178
  commonInit()
179
179
  }
180
180
 
181
+ // MARK: Open
182
+
183
+ /// Plays the animation from its current state to the end.
184
+ ///
185
+ /// - Parameter completion: An optional completion closure to be called when the animation completes playing.
186
+ 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)
198
+ }
199
+
200
+ /// Plays the animation from a progress (0-1) to a progress (0-1).
201
+ ///
202
+ /// - Parameter fromProgress: The start progress of the animation. If `nil` the animation will start at the current progress.
203
+ /// - Parameter toProgress: The end progress of the animation.
204
+ /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used.
205
+ /// - Parameter completion: An optional completion closure to be called when the animation stops.
206
+ open func play(
207
+ fromProgress: AnimationProgressTime? = nil,
208
+ toProgress: AnimationProgressTime,
209
+ loopMode: LottieLoopMode? = nil,
210
+ completion: LottieCompletionBlock? = nil)
211
+ {
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)
226
+ }
227
+
228
+ /// Plays the animation from a start frame to an end frame in the animation's framerate.
229
+ ///
230
+ /// - Parameter fromFrame: The start frame of the animation. If `nil` the animation will start at the current frame.
231
+ /// - Parameter toFrame: The end frame of the animation.
232
+ /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used.
233
+ /// - Parameter completion: An optional completion closure to be called when the animation stops.
234
+ open func play(
235
+ fromFrame: AnimationFrameTime? = nil,
236
+ toFrame: AnimationFrameTime,
237
+ loopMode: LottieLoopMode? = nil,
238
+ completion: LottieCompletionBlock? = nil)
239
+ {
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)
251
+ }
252
+
253
+ /// Plays the animation from a named marker to another marker.
254
+ ///
255
+ /// Markers are point in time that are encoded into the Animation data and assigned
256
+ /// a name.
257
+ ///
258
+ /// NOTE: If markers are not found the play command will exit.
259
+ ///
260
+ /// - Parameter fromMarker: The start marker for the animation playback. If `nil` the
261
+ /// animation will start at the current progress.
262
+ /// - Parameter toMarker: The end marker for the animation playback.
263
+ /// - Parameter playEndMarkerFrame: A flag to determine whether or not to play the frame of the end marker. If the
264
+ /// end marker represents the end of the section to play, it should be to true. If the provided end marker
265
+ /// represents the beginning of the next section, it should be false.
266
+ /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used.
267
+ /// - Parameter completion: An optional completion closure to be called when the animation stops.
268
+ open func play(
269
+ fromMarker: String? = nil,
270
+ toMarker: String,
271
+ playEndMarkerFrame: Bool = true,
272
+ loopMode: LottieLoopMode? = nil,
273
+ completion: LottieCompletionBlock? = nil)
274
+ {
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)
298
+ }
299
+
300
+ /// Plays the animation from a named marker to the end of the marker's duration.
301
+ ///
302
+ /// A marker is a point in time with an associated duration that is encoded into the
303
+ /// animation data and assigned a name.
304
+ ///
305
+ /// NOTE: If marker is not found the play command will exit.
306
+ ///
307
+ /// - Parameter marker: The start marker for the animation playback.
308
+ /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used.
309
+ /// - Parameter completion: An optional completion closure to be called when the animation stops.
310
+ open func play(
311
+ marker: String,
312
+ loopMode: LottieLoopMode? = nil,
313
+ completion: LottieCompletionBlock? = nil)
314
+ {
315
+ guard let from = animation?.markerMap?[marker] else {
316
+ return
317
+ }
318
+
319
+ play(
320
+ fromFrame: from.frameTime,
321
+ toFrame: from.frameTime + from.durationFrameTime,
322
+ loopMode: loopMode,
323
+ completion: completion)
324
+ }
325
+
326
+ /// Stops the animation and resets the view to its start frame.
327
+ ///
328
+ /// The completion closure will be called with `false`
329
+ open func stop() {
330
+ removeCurrentAnimation()
331
+ currentFrame = 0
332
+ }
333
+
334
+ /// Pauses the animation in its current state.
335
+ ///
336
+ /// The completion closure will be called with `false`
337
+ open func pause() {
338
+ removeCurrentAnimation()
339
+ }
340
+
181
341
  // MARK: Public
182
342
 
183
343
  /// The configuration that this `LottieAnimationView` uses when playing its animation
@@ -294,6 +454,13 @@ final public class LottieAnimationView: LottieAnimationViewBase {
294
454
  }
295
455
  }
296
456
 
457
+ /// 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
+ }
462
+ }
463
+
297
464
  /// Returns `true` if the animation is currently playing.
298
465
  public var isAnimationPlaying: Bool {
299
466
  guard let animationLayer = animationLayer else {
@@ -492,164 +659,6 @@ final public class LottieAnimationView: LottieAnimationViewBase {
492
659
  animation = dotLottieAnimation.animation
493
660
  }
494
661
 
495
- /// Plays the animation from its current state to the end.
496
- ///
497
- /// - Parameter completion: An optional completion closure to be called when the animation completes playing.
498
- public func play(completion: LottieCompletionBlock? = nil) {
499
- guard let animation = animation else {
500
- return
501
- }
502
-
503
- /// Build a context for the animation.
504
- let context = AnimationContext(
505
- playFrom: CGFloat(animation.startFrame),
506
- playTo: CGFloat(animation.endFrame),
507
- closure: completion)
508
- removeCurrentAnimationIfNecessary()
509
- addNewAnimationForContext(context)
510
- }
511
-
512
- /// Plays the animation from a progress (0-1) to a progress (0-1).
513
- ///
514
- /// - Parameter fromProgress: The start progress of the animation. If `nil` the animation will start at the current progress.
515
- /// - Parameter toProgress: The end progress of the animation.
516
- /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used.
517
- /// - Parameter completion: An optional completion closure to be called when the animation stops.
518
- public func play(
519
- fromProgress: AnimationProgressTime? = nil,
520
- toProgress: AnimationProgressTime,
521
- loopMode: LottieLoopMode? = nil,
522
- completion: LottieCompletionBlock? = nil)
523
- {
524
- guard let animation = animation else {
525
- return
526
- }
527
-
528
- removeCurrentAnimationIfNecessary()
529
- if let loopMode = loopMode {
530
- /// Set the loop mode, if one was supplied
531
- self.loopMode = loopMode
532
- }
533
- let context = AnimationContext(
534
- playFrom: animation.frameTime(forProgress: fromProgress ?? currentProgress),
535
- playTo: animation.frameTime(forProgress: toProgress),
536
- closure: completion)
537
- addNewAnimationForContext(context)
538
- }
539
-
540
- /// Plays the animation from a start frame to an end frame in the animation's framerate.
541
- ///
542
- /// - Parameter fromFrame: The start frame of the animation. If `nil` the animation will start at the current frame.
543
- /// - Parameter toFrame: The end frame of the animation.
544
- /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used.
545
- /// - Parameter completion: An optional completion closure to be called when the animation stops.
546
- public func play(
547
- fromFrame: AnimationFrameTime? = nil,
548
- toFrame: AnimationFrameTime,
549
- loopMode: LottieLoopMode? = nil,
550
- completion: LottieCompletionBlock? = nil)
551
- {
552
- removeCurrentAnimationIfNecessary()
553
- if let loopMode = loopMode {
554
- /// Set the loop mode, if one was supplied
555
- self.loopMode = loopMode
556
- }
557
-
558
- let context = AnimationContext(
559
- playFrom: fromFrame ?? currentFrame,
560
- playTo: toFrame,
561
- closure: completion)
562
- addNewAnimationForContext(context)
563
- }
564
-
565
- /// Plays the animation from a named marker to another marker.
566
- ///
567
- /// Markers are point in time that are encoded into the Animation data and assigned
568
- /// a name.
569
- ///
570
- /// NOTE: If markers are not found the play command will exit.
571
- ///
572
- /// - Parameter fromMarker: The start marker for the animation playback. If `nil` the
573
- /// animation will start at the current progress.
574
- /// - Parameter toMarker: The end marker for the animation playback.
575
- /// - Parameter playEndMarkerFrame: A flag to determine whether or not to play the frame of the end marker. If the
576
- /// end marker represents the end of the section to play, it should be to true. If the provided end marker
577
- /// represents the beginning of the next section, it should be false.
578
- /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used.
579
- /// - Parameter completion: An optional completion closure to be called when the animation stops.
580
- public func play(
581
- fromMarker: String? = nil,
582
- toMarker: String,
583
- playEndMarkerFrame: Bool = true,
584
- loopMode: LottieLoopMode? = nil,
585
- completion: LottieCompletionBlock? = nil)
586
- {
587
- guard let animation = animation, let markers = animation.markerMap, let to = markers[toMarker] else {
588
- return
589
- }
590
-
591
- removeCurrentAnimationIfNecessary()
592
- if let loopMode = loopMode {
593
- /// Set the loop mode, if one was supplied
594
- self.loopMode = loopMode
595
- }
596
-
597
- let fromTime: CGFloat
598
- if let fromName = fromMarker, let from = markers[fromName] {
599
- fromTime = CGFloat(from.frameTime)
600
- } else {
601
- fromTime = currentFrame
602
- }
603
-
604
- let playTo = playEndMarkerFrame ? CGFloat(to.frameTime) : CGFloat(to.frameTime) - 1
605
- let context = AnimationContext(
606
- playFrom: fromTime,
607
- playTo: playTo,
608
- closure: completion)
609
- addNewAnimationForContext(context)
610
- }
611
-
612
- /// Plays the animation from a named marker to the end of the marker's duration.
613
- ///
614
- /// A marker is a point in time with an associated duration that is encoded into the
615
- /// animation data and assigned a name.
616
- ///
617
- /// NOTE: If marker is not found the play command will exit.
618
- ///
619
- /// - Parameter marker: The start marker for the animation playback.
620
- /// - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used.
621
- /// - Parameter completion: An optional completion closure to be called when the animation stops.
622
- public func play(
623
- marker: String,
624
- loopMode: LottieLoopMode? = nil,
625
- completion: LottieCompletionBlock? = nil)
626
- {
627
- guard let from = animation?.markerMap?[marker] else {
628
- return
629
- }
630
-
631
- play(
632
- fromFrame: from.frameTime,
633
- toFrame: from.frameTime + from.durationFrameTime,
634
- loopMode: loopMode,
635
- completion: completion)
636
- }
637
-
638
- /// Stops the animation and resets the view to its start frame.
639
- ///
640
- /// The completion closure will be called with `false`
641
- public func stop() {
642
- removeCurrentAnimation()
643
- currentFrame = 0
644
- }
645
-
646
- /// Pauses the animation in its current state.
647
- ///
648
- /// The completion closure will be called with `false`
649
- public func pause() {
650
- removeCurrentAnimation()
651
- }
652
-
653
662
  /// Reloads the images supplied to the animation from the `imageProvider`
654
663
  public func reloadImages() {
655
664
  animationLayer?.reloadImages()
@@ -709,10 +718,17 @@ final public class LottieAnimationView: LottieAnimationViewBase {
709
718
  }
710
719
 
711
720
  /// Logs all child keypaths.
721
+ /// Logs the result of `allHierarchyKeypaths()` to the `LottieLogger`.
712
722
  public func logHierarchyKeypaths() {
713
723
  animationLayer?.logHierarchyKeypaths()
714
724
  }
715
725
 
726
+ /// Computes and returns a list of all child keypaths in the current animation.
727
+ /// The returned list is the same as the log output of `logHierarchyKeypaths()`
728
+ public func allHierarchyKeypaths() -> [String] {
729
+ animationLayer?.allHierarchyKeypaths() ?? []
730
+ }
731
+
716
732
  /// Searches for the nearest child layer to the first Keypath and adds the subview
717
733
  /// to that layer. The subview will move and animate with the child layer.
718
734
  /// Furthermore the subview will be in the child layers coordinate space.
@@ -1060,6 +1076,33 @@ final public class LottieAnimationView: LottieAnimationViewBase {
1060
1076
  }
1061
1077
  }
1062
1078
 
1079
+ /// Updates an in flight animation.
1080
+ 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
1102
+
1103
+ addNewAnimationForContext(newContext)
1104
+ }
1105
+
1063
1106
  // MARK: Fileprivate
1064
1107
 
1065
1108
  /// Context describing the animation that is currently playing in this `LottieAnimationView`
@@ -1087,6 +1130,7 @@ final public class LottieAnimationView: LottieAnimationViewBase {
1087
1130
 
1088
1131
  if let oldAnimation = animationLayer {
1089
1132
  oldAnimation.removeFromSuperlayer()
1133
+ animationLayer = nil
1090
1134
  }
1091
1135
 
1092
1136
  invalidateIntrinsicContentSize()
@@ -1109,6 +1153,7 @@ final public class LottieAnimationView: LottieAnimationViewBase {
1109
1153
  return
1110
1154
  }
1111
1155
 
1156
+ animationLayer.animationView = self
1112
1157
  animationLayer.renderScale = screenScale
1113
1158
 
1114
1159
  viewLayer?.addSublayer(animationLayer)
@@ -1125,6 +1170,7 @@ final public class LottieAnimationView: LottieAnimationViewBase {
1125
1170
  imageProvider: imageProvider.cachedImageProvider,
1126
1171
  textProvider: textProvider,
1127
1172
  fontProvider: fontProvider,
1173
+ maskAnimationToBounds: maskAnimationToBounds,
1128
1174
  logger: logger)
1129
1175
  }
1130
1176
 
@@ -1135,6 +1181,7 @@ final public class LottieAnimationView: LottieAnimationViewBase {
1135
1181
  imageProvider: imageProvider.cachedImageProvider,
1136
1182
  textProvider: textProvider,
1137
1183
  fontProvider: fontProvider,
1184
+ maskAnimationToBounds: maskAnimationToBounds,
1138
1185
  compatibilityTrackerMode: .track,
1139
1186
  logger: logger)
1140
1187
 
@@ -1170,6 +1217,7 @@ final public class LottieAnimationView: LottieAnimationViewBase {
1170
1217
  imageProvider: imageProvider.cachedImageProvider,
1171
1218
  textProvider: textProvider,
1172
1219
  fontProvider: fontProvider,
1220
+ maskAnimationToBounds: maskAnimationToBounds,
1173
1221
  compatibilityTrackerMode: .abort,
1174
1222
  logger: logger)
1175
1223
 
@@ -1214,6 +1262,12 @@ final public class LottieAnimationView: LottieAnimationViewBase {
1214
1262
  let animationContext = animationContext
1215
1263
  let currentFrame = currentFrame
1216
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
+
1217
1271
  makeAnimationLayer(usingEngine: .mainThread)
1218
1272
 
1219
1273
  // Set up the Main Thread animation layer using the same configuration that
@@ -1295,33 +1349,6 @@ final public class LottieAnimationView: LottieAnimationViewBase {
1295
1349
  animationContext = nil
1296
1350
  }
1297
1351
 
1298
- /// Updates an in flight animation.
1299
- fileprivate func updateInFlightAnimation() {
1300
- guard let animationContext = animationContext else { return }
1301
-
1302
- guard animationContext.closure.animationState != .complete else {
1303
- // Tried to re-add an already completed animation. Cancel.
1304
- self.animationContext = nil
1305
- return
1306
- }
1307
-
1308
- /// Tell existing context to ignore its closure
1309
- animationContext.closure.ignoreDelegate = true
1310
-
1311
- /// Make a new context, stealing the completion block from the previous.
1312
- let newContext = AnimationContext(
1313
- playFrom: animationContext.playFrom,
1314
- playTo: animationContext.playTo,
1315
- closure: animationContext.closure.completionBlock)
1316
-
1317
- /// Remove current animation, and freeze the current frame.
1318
- let pauseFrame = realtimeAnimationFrame
1319
- animationLayer?.removeAnimation(forKey: activeAnimationName)
1320
- animationLayer?.currentFrame = pauseFrame
1321
-
1322
- addNewAnimationForContext(newContext)
1323
- }
1324
-
1325
1352
  /// Adds animation to animation layer and sets the delegate. If animation layer or animation are nil, exits.
1326
1353
  fileprivate func addNewAnimationForContext(_ animationContext: AnimationContext) {
1327
1354
  guard let animationlayer = animationLayer, let animation = animation else {
@@ -39,34 +39,17 @@ public final class DotLottieFile {
39
39
  /// Image provider for animations
40
40
  private(set) var imageProvider: AnimationImageProvider?
41
41
 
42
- /// Manifest.json file loading
43
- lazy var manifest: DotLottieManifest? = {
44
- let path = fileUrl.appendingPathComponent(DotLottieFile.manifestFileName)
45
- return try? DotLottieManifest.load(from: path)
46
- }()
47
-
48
- /// Animation url for main animation
49
- lazy var animationUrl: URL? = {
50
- guard let animationId = manifest?.animations.first?.id else { return nil }
51
- let dotLottieJson = "\(DotLottieFile.animationsFolderName)/\(animationId).json"
52
- return fileUrl.appendingPathComponent(dotLottieJson)
53
- }()
54
-
55
42
  /// Animations folder url
56
43
  lazy var animationsUrl: URL = fileUrl.appendingPathComponent("\(DotLottieFile.animationsFolderName)")
57
44
 
58
45
  /// All files in animations folder
59
- lazy var animationUrls: [URL] = {
60
- FileManager.default.urls(for: animationsUrl) ?? []
61
- }()
46
+ lazy var animationUrls: [URL] = FileManager.default.urls(for: animationsUrl) ?? []
62
47
 
63
48
  /// Images folder url
64
49
  lazy var imagesUrl: URL = fileUrl.appendingPathComponent("\(DotLottieFile.imagesFolderName)")
65
50
 
66
51
  /// All images in images folder
67
- lazy var imageUrls: [URL] = {
68
- FileManager.default.urls(for: imagesUrl) ?? []
69
- }()
52
+ lazy var imageUrls: [URL] = FileManager.default.urls(for: imagesUrl) ?? []
70
53
 
71
54
  /// The `LottieAnimation` and `DotLottieConfiguration` for the given animation ID in this file
72
55
  func animation(for id: String? = nil) -> DotLottieFile.Animation? {
@@ -85,14 +68,6 @@ public final class DotLottieFile {
85
68
 
86
69
  private let fileUrl: URL
87
70
 
88
- private var dotLottieAnimations: [DotLottieAnimation] {
89
- manifest?.animations.map {
90
- var animation = $0
91
- animation.animationUrl = animationsUrl.appendingPathComponent("\($0.id).json")
92
- return animation
93
- } ?? []
94
- }
95
-
96
71
  /// Decompresses .lottie file from `URL` and saves to local temp folder
97
72
  ///
98
73
  /// - Parameters:
@@ -102,7 +77,7 @@ public final class DotLottieFile {
102
77
  try? FileManager.default.removeItem(at: destinationURL)
103
78
  try FileManager.default.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
104
79
  try FileManager.default.unzipItem(at: url, to: destinationURL)
105
- loadContent()
80
+ try loadContent()
106
81
  try? FileManager.default.removeItem(at: destinationURL)
107
82
  try? FileManager.default.removeItem(at: url)
108
83
  }
@@ -120,14 +95,11 @@ public final class DotLottieFile {
120
95
  }
121
96
 
122
97
  /// Loads file content to memory
123
- private func loadContent() {
98
+ private func loadContent() throws {
124
99
  imageProvider = DotLottieImageProvider(filepath: imagesUrl)
125
100
 
126
- animations = dotLottieAnimations.compactMap { dotLottieAnimation -> DotLottieFile.Animation? in
127
- guard let animation = try? dotLottieAnimation.animation() else {
128
- return nil
129
- }
130
-
101
+ animations = try loadManifest().animations.map { dotLottieAnimation in
102
+ let animation = try dotLottieAnimation.animation(url: animationsUrl)
131
103
  let configuration = DotLottieConfiguration(
132
104
  id: dotLottieAnimation.id,
133
105
  imageProvider: imageProvider,
@@ -139,6 +111,11 @@ public final class DotLottieFile {
139
111
  configuration: configuration)
140
112
  }
141
113
  }
114
+
115
+ private func loadManifest() throws -> DotLottieManifest {
116
+ let path = fileUrl.appendingPathComponent(DotLottieFile.manifestFileName)
117
+ return try DotLottieManifest.load(from: path)
118
+ }
142
119
  }
143
120
 
144
121
  extension String {