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.
- package/.github/workflows/main.yml +27 -9
- package/Lottie.xcodeproj/project.pbxproj +158 -70
- package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +2 -2
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/IDEFindNavigatorScopes.plist +5 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +258 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +13 -2
- package/Package.swift +2 -1
- package/README.md +3 -3
- package/Rakefile +8 -4
- package/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift +16 -2
- package/Sources/Private/CoreAnimation/Animations/CombinedShapeAnimation.swift +1 -1
- package/Sources/Private/CoreAnimation/Animations/CustomPathAnimation.swift +1 -1
- package/Sources/Private/CoreAnimation/Animations/EllipseAnimation.swift +1 -1
- package/Sources/Private/CoreAnimation/Animations/GradientAnimations.swift +6 -6
- package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +76 -7
- package/Sources/Private/CoreAnimation/Animations/OpacityAnimation.swift +1 -1
- package/Sources/Private/CoreAnimation/Animations/RectangleAnimation.swift +1 -1
- package/Sources/Private/CoreAnimation/Animations/ShapeAnimation.swift +66 -102
- package/Sources/Private/CoreAnimation/Animations/StarAnimation.swift +2 -2
- package/Sources/Private/CoreAnimation/Animations/StrokeAnimation.swift +3 -3
- package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +66 -17
- package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +55 -32
- package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +16 -12
- package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +3 -3
- package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +24 -11
- package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +2 -2
- package/Sources/Private/CoreAnimation/Layers/PreCompLayer.swift +1 -1
- package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +13 -2
- package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +9 -1
- package/Sources/Private/CoreAnimation/ValueProviderStore.swift +22 -11
- package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +1 -1
- package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +13 -2
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.swift +16 -7
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +2 -2
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/RectNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/StarNode.swift +2 -2
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +20 -8
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +28 -9
- package/Sources/Private/Model/Assets/ImageAsset.swift +4 -3
- package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +2 -8
- package/Sources/Private/Model/DotLottie/DotLottieManifest.swift +3 -14
- package/Sources/Private/Model/DotLottie/DotLottieUtils.swift +11 -1
- package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+BackingConfiguration.swift +147 -0
- package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+Helpers.swift +351 -0
- package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+MemoryFile.swift +183 -0
- package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+Progress.swift +66 -0
- package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+Reading.swift +144 -0
- package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+ReadingDeprecated.swift +49 -0
- package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+Writing.swift +385 -0
- package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+WritingDeprecated.swift +91 -0
- package/Sources/Private/Model/DotLottie/ZipFoundation/Archive+ZIP64.swift +170 -0
- package/Sources/Private/Model/DotLottie/{Zip/ZipArchive.swift → ZipFoundation/Archive.swift} +150 -227
- package/Sources/Private/Model/DotLottie/ZipFoundation/Data+Compression.swift +403 -0
- package/Sources/Private/Model/DotLottie/ZipFoundation/Data+CompressionDeprecated.swift +44 -0
- package/Sources/Private/Model/DotLottie/{Zip → ZipFoundation}/Data+Serialization.swift +62 -0
- package/Sources/Private/Model/DotLottie/{Zip/ZipEntry+Serialization.swift → ZipFoundation/Entry+Serialization.swift} +7 -7
- package/Sources/Private/Model/DotLottie/{Zip/ZipEntry+ZIP64.swift → ZipFoundation/Entry+ZIP64.swift} +13 -19
- package/Sources/Private/Model/DotLottie/{Zip/ZipEntry.swift → ZipFoundation/Entry.swift} +141 -10
- package/Sources/Private/Model/DotLottie/ZipFoundation/FileManager+ZIP.swift +368 -0
- package/Sources/Private/Model/DotLottie/ZipFoundation/README.md +24 -0
- package/Sources/Private/Model/DotLottie/ZipFoundation/URL+ZIP.swift +32 -0
- package/Sources/Private/Model/Extensions/Bundle.swift +5 -14
- package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +31 -8
- package/Sources/Private/Model/Objects/Transform.swift +58 -17
- package/Sources/Private/Model/ShapeItems/Repeater.swift +41 -7
- package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +61 -7
- package/Sources/Private/Model/Text/TextAnimator.swift +37 -5
- package/Sources/Private/RootAnimationLayer.swift +3 -1
- package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +12 -4
- package/Sources/Private/Utility/Extensions/DataExtension.swift +14 -4
- package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +11 -0
- package/Sources/Private/Utility/Primitives/ColorExtension.swift +10 -13
- package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +28 -6
- package/Sources/Public/Animation/LottieAnimationHelpers.swift +12 -10
- package/Sources/Public/Animation/LottieAnimationView.swift +213 -186
- package/Sources/Public/DotLottie/DotLottieFile.swift +11 -34
- package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +101 -74
- package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +90 -0
- package/Sources/Public/iOS/LottieAnimationViewBase.swift +1 -1
- package/Sources/Public/macOS/LottieAnimationViewBase.macOS.swift +1 -1
- package/lottie-ios.podspec +1 -1
- package/package.json +1 -1
- package/LottieAnimation/LottieAnimation.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/LottieAnimation/LottieAnimation.xcodeproj/project.xcworkspace/xcuserdata/valentinperignon.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/LottieAnimation/LottieAnimation.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- package/LottieAnimation/LottieAnimation.xcodeproj/xcuserdata/valentinperignon.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +0 -6
- package/LottieAnimation/LottieAnimation.xcodeproj/xcuserdata/valentinperignon.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- package/Sources/Private/Model/DotLottie/Zip/Data+Compression.swift +0 -134
- package/Sources/Private/Model/DotLottie/Zip/FileManager+ZIP.swift +0 -130
- package/Sources/Private/Utility/Interpolatable/KeyframeGroup+Extensions.swift +0 -59
|
@@ -19,6 +19,7 @@ final class CoreAnimationLayer: BaseAnimationLayer {
|
|
|
19
19
|
imageProvider: AnimationImageProvider,
|
|
20
20
|
textProvider: AnimationTextProvider,
|
|
21
21
|
fontProvider: AnimationFontProvider,
|
|
22
|
+
maskAnimationToBounds: Bool,
|
|
22
23
|
compatibilityTrackerMode: CompatibilityTracker.Mode,
|
|
23
24
|
logger: LottieLogger)
|
|
24
25
|
throws
|
|
@@ -31,7 +32,7 @@ final class CoreAnimationLayer: BaseAnimationLayer {
|
|
|
31
32
|
compatibilityTracker = CompatibilityTracker(mode: compatibilityTrackerMode, logger: logger)
|
|
32
33
|
valueProviderStore = ValueProviderStore(logger: logger)
|
|
33
34
|
super.init()
|
|
34
|
-
masksToBounds =
|
|
35
|
+
masksToBounds = maskAnimationToBounds
|
|
35
36
|
setup()
|
|
36
37
|
try setupChildLayers()
|
|
37
38
|
}
|
|
@@ -71,7 +72,10 @@ final class CoreAnimationLayer: BaseAnimationLayer {
|
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
enum PlaybackState: Equatable {
|
|
74
|
-
/// The animation is playing
|
|
75
|
+
/// The animation is has started playing, and may still be playing.
|
|
76
|
+
/// - When animating with a finite duration (e.g. `playOnce`), playback
|
|
77
|
+
/// state will still be `playing` when the animation completes.
|
|
78
|
+
/// To check if the animation is currently playing, prefer `isAnimationPlaying`.
|
|
75
79
|
case playing
|
|
76
80
|
/// The animation is statically displaying a specific frame
|
|
77
81
|
case paused(frame: AnimationFrameTime)
|
|
@@ -81,9 +85,18 @@ final class CoreAnimationLayer: BaseAnimationLayer {
|
|
|
81
85
|
struct AnimationConfiguration: Equatable {
|
|
82
86
|
var animationContext: AnimationContext
|
|
83
87
|
var timingConfiguration: CAMediaTimingConfiguration
|
|
84
|
-
var
|
|
88
|
+
var recordHierarchyKeypath: ((String) -> Void)?
|
|
89
|
+
|
|
90
|
+
static func ==(_ lhs: AnimationConfiguration, _ rhs: AnimationConfiguration) -> Bool {
|
|
91
|
+
lhs.animationContext == rhs.animationContext
|
|
92
|
+
&& lhs.timingConfiguration == rhs.timingConfiguration
|
|
93
|
+
&& ((lhs.recordHierarchyKeypath == nil) == (rhs.recordHierarchyKeypath == nil))
|
|
94
|
+
}
|
|
85
95
|
}
|
|
86
96
|
|
|
97
|
+
/// The parent `LottieAnimationView` that manages this layer
|
|
98
|
+
weak var animationView: LottieAnimationView?
|
|
99
|
+
|
|
87
100
|
/// A closure that is called after this layer sets up its animation.
|
|
88
101
|
/// If the animation setup was unsuccessful and encountered compatibility issues,
|
|
89
102
|
/// those issues are included in this call.
|
|
@@ -148,7 +161,9 @@ final class CoreAnimationLayer: BaseAnimationLayer {
|
|
|
148
161
|
// allocate a very large amount of memory (400mb+).
|
|
149
162
|
// - Alternatively this layer could subclass `CATransformLayer`,
|
|
150
163
|
// but this causes Core Animation to emit unnecessary logs.
|
|
151
|
-
if
|
|
164
|
+
if var pendingAnimationConfiguration = pendingAnimationConfiguration {
|
|
165
|
+
pendingAnimationConfigurationModification?(&pendingAnimationConfiguration.animationConfiguration)
|
|
166
|
+
pendingAnimationConfigurationModification = nil
|
|
152
167
|
self.pendingAnimationConfiguration = nil
|
|
153
168
|
|
|
154
169
|
do {
|
|
@@ -180,6 +195,9 @@ final class CoreAnimationLayer: BaseAnimationLayer {
|
|
|
180
195
|
animationConfiguration: AnimationConfiguration,
|
|
181
196
|
playbackState: PlaybackState)?
|
|
182
197
|
|
|
198
|
+
/// A modification that should be applied to the next animation configuration
|
|
199
|
+
private var pendingAnimationConfigurationModification: ((inout AnimationConfiguration) -> Void)?
|
|
200
|
+
|
|
183
201
|
/// Configuration for the animation that is currently setup in this layer
|
|
184
202
|
private var currentAnimationConfiguration: AnimationConfiguration?
|
|
185
203
|
|
|
@@ -249,7 +267,7 @@ final class CoreAnimationLayer: BaseAnimationLayer {
|
|
|
249
267
|
logger: logger,
|
|
250
268
|
currentKeypath: AnimationKeypath(keys: []),
|
|
251
269
|
textProvider: textProvider,
|
|
252
|
-
|
|
270
|
+
recordHierarchyKeypath: configuration.recordHierarchyKeypath)
|
|
253
271
|
|
|
254
272
|
// Perform a layout pass if necessary so all of the sublayers
|
|
255
273
|
// have the most up-to-date sizing information
|
|
@@ -289,32 +307,20 @@ final class CoreAnimationLayer: BaseAnimationLayer {
|
|
|
289
307
|
|
|
290
308
|
// Removes the current `CAAnimation`s, and rebuilds new animations
|
|
291
309
|
// using the same configuration as the previous animations.
|
|
292
|
-
private func rebuildCurrentAnimation(
|
|
310
|
+
private func rebuildCurrentAnimation() {
|
|
293
311
|
guard
|
|
294
|
-
let currentConfiguration = currentAnimationConfiguration,
|
|
295
|
-
let playbackState = playbackState,
|
|
296
312
|
// Don't replace any pending animations that are queued to begin
|
|
297
313
|
// on the next run loop cycle, since an existing pending animation
|
|
298
314
|
// will cause the animation to be rebuilt anyway.
|
|
299
315
|
pendingAnimationConfiguration == nil
|
|
300
|
-
else {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
removeAnimations()
|
|
311
|
-
|
|
312
|
-
switch playbackState {
|
|
313
|
-
case .paused(let frame):
|
|
314
|
-
currentFrame = frame
|
|
315
|
-
|
|
316
|
-
case .playing:
|
|
317
|
-
playAnimation(configuration: newConfiguration ?? currentConfiguration)
|
|
316
|
+
else { return }
|
|
317
|
+
|
|
318
|
+
if isAnimationPlaying == true {
|
|
319
|
+
animationView?.updateInFlightAnimation()
|
|
320
|
+
} else {
|
|
321
|
+
let currentFrame = currentFrame
|
|
322
|
+
removeAnimations()
|
|
323
|
+
self.currentFrame = currentFrame
|
|
318
324
|
}
|
|
319
325
|
}
|
|
320
326
|
|
|
@@ -328,6 +334,9 @@ extension CoreAnimationLayer: RootAnimationLayer {
|
|
|
328
334
|
.specific(#keyPath(animationProgress))
|
|
329
335
|
}
|
|
330
336
|
|
|
337
|
+
/// Whether or not the animation is currently playing.
|
|
338
|
+
/// - Handles case where CAAnimations with a finite duration animation (e.g. `playOnce`)
|
|
339
|
+
/// have finished playing but still present on this layer.
|
|
331
340
|
var isAnimationPlaying: Bool? {
|
|
332
341
|
switch pendingAnimationConfiguration?.playbackState {
|
|
333
342
|
case .playing:
|
|
@@ -344,6 +353,8 @@ extension CoreAnimationLayer: RootAnimationLayer {
|
|
|
344
353
|
}
|
|
345
354
|
}
|
|
346
355
|
|
|
356
|
+
/// The current frame of the animation being displayed,
|
|
357
|
+
/// accounting for the realtime progress of any active CAAnimations.
|
|
347
358
|
var currentFrame: AnimationFrameTime {
|
|
348
359
|
get {
|
|
349
360
|
switch playbackState {
|
|
@@ -442,18 +453,30 @@ extension CoreAnimationLayer: RootAnimationLayer {
|
|
|
442
453
|
}
|
|
443
454
|
|
|
444
455
|
func logHierarchyKeypaths() {
|
|
445
|
-
|
|
456
|
+
for keypath in allHierarchyKeypaths() {
|
|
457
|
+
logger.info(keypath)
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
func allHierarchyKeypaths() -> [String] {
|
|
462
|
+
guard pendingAnimationConfiguration?.animationConfiguration ?? currentAnimationConfiguration != nil else {
|
|
446
463
|
logger.info("Cannot log hierarchy keypaths until animation has been set up at least once")
|
|
447
|
-
return
|
|
464
|
+
return []
|
|
448
465
|
}
|
|
449
466
|
|
|
450
467
|
logger.info("Lottie: Rebuilding animation with hierarchy keypath logging enabled")
|
|
451
468
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
469
|
+
var allAnimationKeypaths = [String]()
|
|
470
|
+
pendingAnimationConfigurationModification = { configuration in
|
|
471
|
+
configuration.recordHierarchyKeypath = { keypath in
|
|
472
|
+
allAnimationKeypaths.append(keypath)
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
rebuildCurrentAnimation()
|
|
456
477
|
displayIfNeeded()
|
|
478
|
+
|
|
479
|
+
return allAnimationKeypaths
|
|
457
480
|
}
|
|
458
481
|
|
|
459
482
|
func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) {
|
|
@@ -68,20 +68,21 @@ enum Keyframes {
|
|
|
68
68
|
/// Combines the given keyframe groups of `Keyframe<T>`s into a single keyframe group of of `Keyframe<[T]>`s
|
|
69
69
|
/// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged
|
|
70
70
|
/// - Otherwise, the keyframes are manually interpolated at each frame in the animation
|
|
71
|
-
static func combined<T1, T2, T3, T4, T5, T6, CombinedResult>(
|
|
71
|
+
static func combined<T1, T2, T3, T4, T5, T6, T7, CombinedResult>(
|
|
72
72
|
_ k1: KeyframeGroup<T1>,
|
|
73
73
|
_ k2: KeyframeGroup<T2>,
|
|
74
74
|
_ k3: KeyframeGroup<T3>,
|
|
75
75
|
_ k4: KeyframeGroup<T4>,
|
|
76
76
|
_ k5: KeyframeGroup<T5>,
|
|
77
77
|
_ k6: KeyframeGroup<T6>,
|
|
78
|
-
|
|
78
|
+
_ k7: KeyframeGroup<T7>,
|
|
79
|
+
makeCombinedResult: (T1, T2, T3, T4, T5, T6, T7) -> CombinedResult)
|
|
79
80
|
-> KeyframeGroup<CombinedResult>
|
|
80
81
|
where T1: AnyInterpolatable, T2: AnyInterpolatable, T3: AnyInterpolatable, T4: AnyInterpolatable,
|
|
81
|
-
T5: AnyInterpolatable, T6: AnyInterpolatable
|
|
82
|
+
T5: AnyInterpolatable, T6: AnyInterpolatable, T7: AnyInterpolatable
|
|
82
83
|
{
|
|
83
84
|
Keyframes.combined(
|
|
84
|
-
[k1, k2, k3, k4, k5, k6],
|
|
85
|
+
[k1, k2, k3, k4, k5, k6, k7],
|
|
85
86
|
makeCombinedResult: { untypedValues in
|
|
86
87
|
guard
|
|
87
88
|
let t1 = untypedValues[0] as? T1,
|
|
@@ -89,17 +90,18 @@ enum Keyframes {
|
|
|
89
90
|
let t3 = untypedValues[2] as? T3,
|
|
90
91
|
let t4 = untypedValues[3] as? T4,
|
|
91
92
|
let t5 = untypedValues[4] as? T5,
|
|
92
|
-
let t6 = untypedValues[5] as? T6
|
|
93
|
+
let t6 = untypedValues[5] as? T6,
|
|
94
|
+
let t7 = untypedValues[6] as? T7
|
|
93
95
|
else { return nil }
|
|
94
96
|
|
|
95
|
-
return makeCombinedResult(t1, t2, t3, t4, t5, t6)
|
|
97
|
+
return makeCombinedResult(t1, t2, t3, t4, t5, t6, t7)
|
|
96
98
|
})
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
/// Combines the given keyframe groups of `Keyframe<T>`s into a single keyframe group of of `Keyframe<[T]>`s
|
|
100
102
|
/// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged
|
|
101
103
|
/// - Otherwise, the keyframes are manually interpolated at each frame in the animation
|
|
102
|
-
static func combined<T1, T2, T3, T4, T5, T6, T7, CombinedResult>(
|
|
104
|
+
static func combined<T1, T2, T3, T4, T5, T6, T7, T8, CombinedResult>(
|
|
103
105
|
_ k1: KeyframeGroup<T1>,
|
|
104
106
|
_ k2: KeyframeGroup<T2>,
|
|
105
107
|
_ k3: KeyframeGroup<T3>,
|
|
@@ -107,13 +109,14 @@ enum Keyframes {
|
|
|
107
109
|
_ k5: KeyframeGroup<T5>,
|
|
108
110
|
_ k6: KeyframeGroup<T6>,
|
|
109
111
|
_ k7: KeyframeGroup<T7>,
|
|
110
|
-
|
|
112
|
+
_ k8: KeyframeGroup<T8>,
|
|
113
|
+
makeCombinedResult: (T1, T2, T3, T4, T5, T6, T7, T8) -> CombinedResult)
|
|
111
114
|
-> KeyframeGroup<CombinedResult>
|
|
112
115
|
where T1: AnyInterpolatable, T2: AnyInterpolatable, T3: AnyInterpolatable, T4: AnyInterpolatable,
|
|
113
|
-
T5: AnyInterpolatable, T6: AnyInterpolatable, T7: AnyInterpolatable
|
|
116
|
+
T5: AnyInterpolatable, T6: AnyInterpolatable, T7: AnyInterpolatable, T8: AnyInterpolatable
|
|
114
117
|
{
|
|
115
118
|
Keyframes.combined(
|
|
116
|
-
[k1, k2, k3, k4, k5, k6, k7],
|
|
119
|
+
[k1, k2, k3, k4, k5, k6, k7, k8],
|
|
117
120
|
makeCombinedResult: { untypedValues in
|
|
118
121
|
guard
|
|
119
122
|
let t1 = untypedValues[0] as? T1,
|
|
@@ -122,10 +125,11 @@ enum Keyframes {
|
|
|
122
125
|
let t4 = untypedValues[3] as? T4,
|
|
123
126
|
let t5 = untypedValues[4] as? T5,
|
|
124
127
|
let t6 = untypedValues[5] as? T6,
|
|
125
|
-
let t7 = untypedValues[6] as? T7
|
|
128
|
+
let t7 = untypedValues[6] as? T7,
|
|
129
|
+
let t8 = untypedValues[7] as? T8
|
|
126
130
|
else { return nil }
|
|
127
131
|
|
|
128
|
-
return makeCombinedResult(t1, t2, t3, t4, t5, t6, t7)
|
|
132
|
+
return makeCombinedResult(t1, t2, t3, t4, t5, t6, t7, t8)
|
|
129
133
|
})
|
|
130
134
|
}
|
|
131
135
|
|
|
@@ -44,9 +44,9 @@ struct LayerAnimationContext {
|
|
|
44
44
|
/// The `AnimationTextProvider`
|
|
45
45
|
var textProvider: AnimationTextProvider
|
|
46
46
|
|
|
47
|
-
///
|
|
48
|
-
/// - Used for `CoreAnimationLayer.logHierarchyKeypaths()`
|
|
49
|
-
var
|
|
47
|
+
/// Records the given animation keypath so it can be logged or collected into a list
|
|
48
|
+
/// - Used for `CoreAnimationLayer.logHierarchyKeypaths()` and `allHierarchyKeypaths()`
|
|
49
|
+
var recordHierarchyKeypath: ((String) -> Void)?
|
|
50
50
|
|
|
51
51
|
/// A closure that remaps the given frame in the child layer's local time to a frame
|
|
52
52
|
/// in the animation's overall global time
|
|
@@ -17,6 +17,7 @@ class BaseCompositionLayer: BaseAnimationLayer {
|
|
|
17
17
|
setupSublayers()
|
|
18
18
|
compositingFilter = layerModel.blendMode.filterName
|
|
19
19
|
name = layerModel.name
|
|
20
|
+
contentsLayer.name = "\(layerModel.name) (Content)"
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
required init?(coder _: NSCoder) {
|
|
@@ -36,6 +37,10 @@ class BaseCompositionLayer: BaseAnimationLayer {
|
|
|
36
37
|
|
|
37
38
|
// MARK: Internal
|
|
38
39
|
|
|
40
|
+
/// The layer that content / sublayers should be rendered in.
|
|
41
|
+
/// This is the layer that transform animations are applied to.
|
|
42
|
+
let contentsLayer = BaseAnimationLayer()
|
|
43
|
+
|
|
39
44
|
/// Whether or not this layer render should render any visible content
|
|
40
45
|
var renderLayerContents: Bool { true }
|
|
41
46
|
|
|
@@ -43,24 +48,22 @@ class BaseCompositionLayer: BaseAnimationLayer {
|
|
|
43
48
|
/// and all child `AnimationLayer`s.
|
|
44
49
|
/// - Can be overridden by subclasses, which much call `super`.
|
|
45
50
|
override func setupAnimations(context: LayerAnimationContext) throws {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
context = context.addingKeypathComponent(baseLayerModel.name)
|
|
49
|
-
}
|
|
51
|
+
let layerContext = context.addingKeypathComponent(baseLayerModel.name)
|
|
52
|
+
let childContext = renderLayerContents ? layerContext : context
|
|
50
53
|
|
|
51
|
-
try setupLayerAnimations(context:
|
|
52
|
-
try setupChildAnimations(context:
|
|
54
|
+
try setupLayerAnimations(context: layerContext)
|
|
55
|
+
try setupChildAnimations(context: childContext)
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
func setupLayerAnimations(context: LayerAnimationContext) throws {
|
|
56
|
-
let
|
|
59
|
+
let transformContext = context.addingKeypathComponent("Transform")
|
|
57
60
|
|
|
58
|
-
try addTransformAnimations(for: baseLayerModel.transform, context:
|
|
61
|
+
try contentsLayer.addTransformAnimations(for: baseLayerModel.transform, context: transformContext)
|
|
59
62
|
|
|
60
63
|
if renderLayerContents {
|
|
61
|
-
try addOpacityAnimation(for: baseLayerModel.transform, context:
|
|
64
|
+
try contentsLayer.addOpacityAnimation(for: baseLayerModel.transform, context: transformContext)
|
|
62
65
|
|
|
63
|
-
addVisibilityAnimation(
|
|
66
|
+
contentsLayer.addVisibilityAnimation(
|
|
64
67
|
inFrame: CGFloat(baseLayerModel.inFrame),
|
|
65
68
|
outFrame: CGFloat(baseLayerModel.outFrame),
|
|
66
69
|
context: context)
|
|
@@ -71,17 +74,27 @@ class BaseCompositionLayer: BaseAnimationLayer {
|
|
|
71
74
|
try super.setupAnimations(context: context)
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
override func addSublayer(_ layer: CALayer) {
|
|
78
|
+
if layer === contentsLayer {
|
|
79
|
+
super.addSublayer(contentsLayer)
|
|
80
|
+
} else {
|
|
81
|
+
contentsLayer.addSublayer(layer)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
74
85
|
// MARK: Private
|
|
75
86
|
|
|
76
87
|
private let baseLayerModel: LayerModel
|
|
77
88
|
|
|
78
89
|
private func setupSublayers() {
|
|
90
|
+
addSublayer(contentsLayer)
|
|
91
|
+
|
|
79
92
|
if
|
|
80
93
|
renderLayerContents,
|
|
81
94
|
let masks = baseLayerModel.masks?.filter({ $0.mode != .none }),
|
|
82
95
|
!masks.isEmpty
|
|
83
96
|
{
|
|
84
|
-
mask = MaskCompositionLayer(masks: masks)
|
|
97
|
+
contentsLayer.mask = MaskCompositionLayer(masks: masks)
|
|
85
98
|
}
|
|
86
99
|
}
|
|
87
100
|
|
|
@@ -42,12 +42,12 @@ final class ImageLayer: BaseCompositionLayer {
|
|
|
42
42
|
let image = context.imageProvider.imageForAsset(asset: imageAsset)
|
|
43
43
|
else {
|
|
44
44
|
self.imageAsset = nil
|
|
45
|
-
contents = nil
|
|
45
|
+
contentsLayer.contents = nil
|
|
46
46
|
return
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
self.imageAsset = imageAsset
|
|
50
|
-
contents = image
|
|
50
|
+
contentsLayer.contents = image
|
|
51
51
|
setNeedsLayout()
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -55,7 +55,15 @@ private struct RepeaterTransform {
|
|
|
55
55
|
anchorPoint = repeater.anchorPoint
|
|
56
56
|
scale = repeater.scale
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
rotationX = repeater.rotationX.map { rotation in
|
|
59
|
+
LottieVector1D(rotation.value * Double(index))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
rotationY = repeater.rotationY.map { rotation in
|
|
63
|
+
LottieVector1D(rotation.value * Double(index))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
rotationZ = repeater.rotationZ.map { rotation in
|
|
59
67
|
LottieVector1D(rotation.value * Double(index))
|
|
60
68
|
}
|
|
61
69
|
|
|
@@ -71,7 +79,10 @@ private struct RepeaterTransform {
|
|
|
71
79
|
|
|
72
80
|
let anchorPoint: KeyframeGroup<LottieVector3D>
|
|
73
81
|
let position: KeyframeGroup<LottieVector3D>
|
|
74
|
-
let
|
|
82
|
+
let rotationX: KeyframeGroup<LottieVector1D>
|
|
83
|
+
let rotationY: KeyframeGroup<LottieVector1D>
|
|
84
|
+
let rotationZ: KeyframeGroup<LottieVector1D>
|
|
85
|
+
|
|
75
86
|
let scale: KeyframeGroup<LottieVector3D>
|
|
76
87
|
|
|
77
88
|
}
|
|
@@ -373,9 +373,17 @@ extension Array where Element == ShapeItemLayer.Item {
|
|
|
373
373
|
|
|
374
374
|
for item in self {
|
|
375
375
|
// `renderGroups` is non-empty, so is guaranteed to have a valid end index
|
|
376
|
-
|
|
376
|
+
var lastIndex: Int {
|
|
377
|
+
renderGroups.indices.last!
|
|
378
|
+
}
|
|
377
379
|
|
|
378
380
|
if item.item.drawsCGPath {
|
|
381
|
+
// Trims should only affect paths that precede them in the group,
|
|
382
|
+
// so if the existing group already has a trim we create a new group for this path item.
|
|
383
|
+
if renderGroups[lastIndex].otherItems.contains(where: { $0.item is Trim }) {
|
|
384
|
+
renderGroups.append(ShapeRenderGroup())
|
|
385
|
+
}
|
|
386
|
+
|
|
379
387
|
renderGroups[lastIndex].pathItems.append(item)
|
|
380
388
|
}
|
|
381
389
|
|
|
@@ -26,10 +26,15 @@ final class ValueProviderStore {
|
|
|
26
26
|
because that would require calling the closure on the main thread once per frame.
|
|
27
27
|
""")
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
let supportedProperties = PropertyName.allCases.map { $0.rawValue }
|
|
30
|
+
let propertyBeingCustomized = keypath.keys.last ?? ""
|
|
31
|
+
|
|
30
32
|
logger.assert(
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
+
supportedProperties.contains(propertyBeingCustomized),
|
|
34
|
+
"""
|
|
35
|
+
The Core Animation rendering engine currently doesn't support customizing "\(propertyBeingCustomized)" \
|
|
36
|
+
properties. Supported properties are: \(supportedProperties.joined(separator: ", ")).
|
|
37
|
+
""")
|
|
33
38
|
|
|
34
39
|
valueProviders.append((keypath: keypath, valueProvider: valueProvider))
|
|
35
40
|
}
|
|
@@ -42,9 +47,7 @@ final class ValueProviderStore {
|
|
|
42
47
|
context: LayerAnimationContext)
|
|
43
48
|
throws -> KeyframeGroup<Value>?
|
|
44
49
|
{
|
|
45
|
-
|
|
46
|
-
context.logger.info(keypath.fullPath)
|
|
47
|
-
}
|
|
50
|
+
context.recordHierarchyKeypath?(keypath.fullPath)
|
|
48
51
|
|
|
49
52
|
guard let anyValueProvider = valueProvider(for: keypath) else {
|
|
50
53
|
return nil
|
|
@@ -125,13 +128,21 @@ extension AnimationKeypath {
|
|
|
125
128
|
+ keypath.keys.joined(separator: "\\.") // match this keypath, escaping "." characters
|
|
126
129
|
+ "$" // match the end of the string
|
|
127
130
|
|
|
128
|
-
// ** wildcards
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
+
// Replace the ** and * wildcards with markers that are guaranteed to be unique
|
|
132
|
+
// and won't conflict with regex syntax (e.g. `.*`).
|
|
133
|
+
let doubleWildcardMarker = UUID().uuidString
|
|
134
|
+
let singleWildcardMarker = UUID().uuidString
|
|
135
|
+
regex = regex.replacingOccurrences(of: "**", with: doubleWildcardMarker)
|
|
136
|
+
regex = regex.replacingOccurrences(of: "*", with: singleWildcardMarker)
|
|
137
|
+
|
|
138
|
+
// "**" wildcards match zero or more path segments separated by "\\."
|
|
139
|
+
// - "**.Color" matches any of "Color", "Layer 1.Color", and "Layer 1.Layer 2.Color"
|
|
140
|
+
regex = regex.replacingOccurrences(of: "\(doubleWildcardMarker)\\.", with: ".*")
|
|
141
|
+
regex = regex.replacingOccurrences(of: doubleWildcardMarker, with: ".*")
|
|
131
142
|
|
|
132
|
-
// * wildcards match
|
|
143
|
+
// "*" wildcards match exactly one path component
|
|
133
144
|
// - "*.Color" matches "Layer 1.Color" but not "Layer 1.Layer 2.Color"
|
|
134
|
-
regex = regex.replacingOccurrences(of:
|
|
145
|
+
regex = regex.replacingOccurrences(of: singleWildcardMarker, with: "[^.]+")
|
|
135
146
|
|
|
136
147
|
return fullPath.range(of: regex, options: .regularExpression) != nil
|
|
137
148
|
}
|
|
@@ -167,7 +167,7 @@ private class MaskNodeProperties: NodePropertyMap {
|
|
|
167
167
|
shape = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.shape.keyframes))
|
|
168
168
|
expansion = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.expansion.keyframes))
|
|
169
169
|
propertyMap = [
|
|
170
|
-
|
|
170
|
+
PropertyName.opacity.rawValue : opacity,
|
|
171
171
|
"Shape" : shape,
|
|
172
172
|
"Expansion" : expansion,
|
|
173
173
|
]
|
|
@@ -23,6 +23,7 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
|
|
|
23
23
|
imageProvider: AnimationImageProvider,
|
|
24
24
|
textProvider: AnimationTextProvider,
|
|
25
25
|
fontProvider: AnimationFontProvider,
|
|
26
|
+
maskAnimationToBounds: Bool,
|
|
26
27
|
logger: LottieLogger)
|
|
27
28
|
{
|
|
28
29
|
layerImageProvider = LayerImageProvider(imageProvider: imageProvider, assets: animation.assetLibrary?.imageAssets)
|
|
@@ -31,7 +32,7 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
|
|
|
31
32
|
animationLayers = []
|
|
32
33
|
self.logger = logger
|
|
33
34
|
super.init()
|
|
34
|
-
masksToBounds =
|
|
35
|
+
masksToBounds = maskAnimationToBounds
|
|
35
36
|
bounds = animation.bounds
|
|
36
37
|
let layers = animation.layers.initializeCompositionLayers(
|
|
37
38
|
assetLibrary: animation.assetLibrary,
|
|
@@ -146,6 +147,9 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
|
|
|
146
147
|
/// The animatable Current Frame Property
|
|
147
148
|
@NSManaged var currentFrame: CGFloat
|
|
148
149
|
|
|
150
|
+
/// The parent `LottieAnimationView` that manages this layer
|
|
151
|
+
weak var animationView: LottieAnimationView?
|
|
152
|
+
|
|
149
153
|
var animationLayers: ContiguousArray<CompositionLayer>
|
|
150
154
|
|
|
151
155
|
var primaryAnimationKey: AnimationKey {
|
|
@@ -200,7 +204,14 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
|
|
|
200
204
|
|
|
201
205
|
func logHierarchyKeypaths() {
|
|
202
206
|
logger.info("Lottie: Logging Animation Keypaths")
|
|
203
|
-
|
|
207
|
+
|
|
208
|
+
for keypath in allHierarchyKeypaths() {
|
|
209
|
+
logger.info(keypath)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
func allHierarchyKeypaths() -> [String] {
|
|
214
|
+
animationLayers.flatMap { $0.allKeypaths() }
|
|
204
215
|
}
|
|
205
216
|
|
|
206
217
|
func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) {
|
|
@@ -18,14 +18,19 @@ final class LayerTransformProperties: NodePropertyMap, KeypathSearchable {
|
|
|
18
18
|
init(transform: Transform) {
|
|
19
19
|
anchor = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.anchorPoint.keyframes))
|
|
20
20
|
scale = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.scale.keyframes))
|
|
21
|
-
|
|
21
|
+
rotationX = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotationX.keyframes))
|
|
22
|
+
rotationY = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotationY.keyframes))
|
|
23
|
+
rotationZ = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotationZ.keyframes))
|
|
22
24
|
opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.opacity.keyframes))
|
|
23
25
|
|
|
24
26
|
var propertyMap: [String: AnyNodeProperty] = [
|
|
25
27
|
"Anchor Point" : anchor,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
28
|
+
PropertyName.scale.rawValue : scale,
|
|
29
|
+
PropertyName.rotation.rawValue: rotationZ,
|
|
30
|
+
"Rotation X" : rotationX,
|
|
31
|
+
"Rotation Y" : rotationY,
|
|
32
|
+
"Rotation Z" : rotationZ,
|
|
33
|
+
PropertyName.opacity.rawValue : opacity,
|
|
29
34
|
]
|
|
30
35
|
|
|
31
36
|
if
|
|
@@ -41,7 +46,7 @@ final class LayerTransformProperties: NodePropertyMap, KeypathSearchable {
|
|
|
41
46
|
position = nil
|
|
42
47
|
} else if let positionKeyframes = transform.position?.keyframes {
|
|
43
48
|
let position: NodeProperty<LottieVector3D> = NodeProperty(provider: KeyframeInterpolator(keyframes: positionKeyframes))
|
|
44
|
-
propertyMap[
|
|
49
|
+
propertyMap[PropertyName.position.rawValue] = position
|
|
45
50
|
self.position = position
|
|
46
51
|
positionX = nil
|
|
47
52
|
positionY = nil
|
|
@@ -64,7 +69,9 @@ final class LayerTransformProperties: NodePropertyMap, KeypathSearchable {
|
|
|
64
69
|
|
|
65
70
|
let anchor: NodeProperty<LottieVector3D>
|
|
66
71
|
let scale: NodeProperty<LottieVector3D>
|
|
67
|
-
let
|
|
72
|
+
let rotationX: NodeProperty<LottieVector1D>
|
|
73
|
+
let rotationY: NodeProperty<LottieVector1D>
|
|
74
|
+
let rotationZ: NodeProperty<LottieVector1D>
|
|
68
75
|
let position: NodeProperty<LottieVector3D>?
|
|
69
76
|
let positionX: NodeProperty<LottieVector1D>?
|
|
70
77
|
let positionY: NodeProperty<LottieVector1D>?
|
|
@@ -130,7 +137,9 @@ class LayerTransformNode: AnimatorNode {
|
|
|
130
137
|
anchor: transformProperties.anchor.value.pointValue,
|
|
131
138
|
position: position,
|
|
132
139
|
scale: transformProperties.scale.value.sizeValue,
|
|
133
|
-
|
|
140
|
+
rotationX: transformProperties.rotationX.value.cgFloatValue,
|
|
141
|
+
rotationY: transformProperties.rotationY.value.cgFloatValue,
|
|
142
|
+
rotationZ: transformProperties.rotationZ.value.cgFloatValue,
|
|
134
143
|
skew: nil,
|
|
135
144
|
skewAxis: nil)
|
|
136
145
|
|
|
@@ -20,7 +20,7 @@ final class EllipseNodeProperties: NodePropertyMap, KeypathSearchable {
|
|
|
20
20
|
position = NodeProperty(provider: KeyframeInterpolator(keyframes: ellipse.position.keyframes))
|
|
21
21
|
size = NodeProperty(provider: KeyframeInterpolator(keyframes: ellipse.size.keyframes))
|
|
22
22
|
keypathProperties = [
|
|
23
|
-
|
|
23
|
+
PropertyName.position.rawValue : position,
|
|
24
24
|
"Size" : size,
|
|
25
25
|
]
|
|
26
26
|
properties = Array(keypathProperties.values)
|
|
@@ -23,10 +23,10 @@ final class PolygonNodeProperties: NodePropertyMap, KeypathSearchable {
|
|
|
23
23
|
rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: star.rotation.keyframes))
|
|
24
24
|
points = NodeProperty(provider: KeyframeInterpolator(keyframes: star.points.keyframes))
|
|
25
25
|
keypathProperties = [
|
|
26
|
-
|
|
26
|
+
PropertyName.position.rawValue : position,
|
|
27
27
|
"Outer Radius" : outerRadius,
|
|
28
28
|
"Outer Roundedness" : outerRoundedness,
|
|
29
|
-
|
|
29
|
+
PropertyName.rotation.rawValue : rotation,
|
|
30
30
|
"Points" : points,
|
|
31
31
|
]
|
|
32
32
|
properties = Array(keypathProperties.values)
|
|
@@ -22,7 +22,7 @@ final class RectNodeProperties: NodePropertyMap, KeypathSearchable {
|
|
|
22
22
|
cornerRadius = NodeProperty(provider: KeyframeInterpolator(keyframes: rectangle.cornerRadius.keyframes))
|
|
23
23
|
|
|
24
24
|
keypathProperties = [
|
|
25
|
-
|
|
25
|
+
PropertyName.position.rawValue : position,
|
|
26
26
|
"Size" : size,
|
|
27
27
|
"Roundness" : cornerRadius,
|
|
28
28
|
]
|
|
@@ -33,12 +33,12 @@ final class StarNodeProperties: NodePropertyMap, KeypathSearchable {
|
|
|
33
33
|
rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: star.rotation.keyframes))
|
|
34
34
|
points = NodeProperty(provider: KeyframeInterpolator(keyframes: star.points.keyframes))
|
|
35
35
|
keypathProperties = [
|
|
36
|
-
|
|
36
|
+
PropertyName.position.rawValue : position,
|
|
37
37
|
"Outer Radius" : outerRadius,
|
|
38
38
|
"Outer Roundedness" : outerRoundedness,
|
|
39
39
|
"Inner Radius" : innerRadius,
|
|
40
40
|
"Inner Roundedness" : innerRoundedness,
|
|
41
|
-
|
|
41
|
+
PropertyName.rotation.rawValue : rotation,
|
|
42
42
|
"Points" : points,
|
|
43
43
|
]
|
|
44
44
|
properties = Array(keypathProperties.values)
|