lottie-ios 4.2.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/main.yml +117 -11
- package/Gemfile +1 -0
- package/Gemfile.lock +5 -0
- package/Lottie.xcodeproj/project.pbxproj +1542 -194
- package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (iOS).xcscheme +1 -1
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (macOS).xcscheme +1 -1
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (tvOS).xcscheme +4 -5
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (visionOS).xcscheme +66 -0
- package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +18 -0
- package/Lottie.xcworkspace/xcshareddata/swiftpm/Package.resolved +2 -11
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +346 -102
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +16 -5
- package/Package.resolved +2 -2
- package/Package.swift +16 -9
- package/README.md +2 -2
- package/Rakefile +122 -25
- package/Sources/Private/CoreAnimation/Animations/DropShadowAnimation.swift +160 -0
- package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +83 -10
- package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +85 -79
- package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +7 -7
- package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +67 -6
- package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +2 -2
- package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +9 -0
- package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +1 -0
- package/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift +1 -1
- package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +2 -0
- package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +11 -13
- package/Sources/Private/CoreAnimation/Layers/SolidLayer.swift +20 -5
- package/Sources/Private/CoreAnimation/Layers/TextLayer.swift +13 -3
- package/Sources/Private/CoreAnimation/ValueProviderStore.swift +6 -4
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Collection+Diff.swift +263 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Diffable.swift +18 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/DiffableSection.swift +16 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/IndexChangeset.swift +187 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/SectionedChangeset.swift +32 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Logging/EpoxyLogger.swift +99 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/CallbackContextEpoxyModeled.swift +8 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelArrayBuilder.swift +48 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelProperty.swift +158 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelStorage.swift +88 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModeled.swift +54 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/AnyEpoxyModelProperty.swift +29 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/ClassReference.swift +39 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/AnimatedProviding.swift +10 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DataIDProviding.swift +57 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidDisplayProviding.swift +41 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidEndDisplayingProviding.swift +41 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidSelectProviding.swift +36 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ErasedContentProviding.swift +49 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/MakeViewProviding.swift +60 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetBehaviorsProviding.swift +38 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetContentProviding.swift +38 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/StyleIDProviding.swift +37 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/TraitCollectionProviding.swift +14 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewDifferentiatorProviding.swift +34 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewProviding.swift +13 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/WillDisplayProviding.swift +41 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/ViewEpoxyModeled.swift +10 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/README.md +31 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingController.swift +46 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingView.swift +391 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIIntrinsicContentSizeInvalidator.swift +44 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUILayoutMargins.swift +51 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxyableView+SwiftUIView.swift +172 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/MeasuringViewRepresentable.swift +128 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift +452 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/SwiftUIView.swift +148 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIView+SwiftUIView.swift +40 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIViewConfiguringSwiftUIView.swift +43 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/BehaviorsConfigurableView.swift +45 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ContentConfigurableView.swift +36 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/EpoxyableView.swift +5 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/StyledView.swift +42 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ViewType.swift +51 -0
- package/Sources/Private/EmbeddedLibraries/README.md +27 -0
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+BackingConfiguration.swift +4 -4
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+MemoryFile.swift +2 -2
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Writing.swift +4 -4
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive.swift +8 -7
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Compression.swift +5 -5
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/FileManager+ZIP.swift +4 -4
- package/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.swift +6 -0
- package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +3 -1
- package/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift +18 -5
- package/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift +31 -3
- package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +33 -8
- package/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift +8 -1
- package/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift +13 -4
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift +2 -2
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.swift +1 -0
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift +4 -4
- package/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +3 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +2 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift +2 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +2 -2
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +6 -2
- package/Sources/Private/Model/Assets/Asset.swift +8 -0
- package/Sources/Private/Model/Assets/AssetLibrary.swift +2 -2
- package/Sources/Private/Model/Assets/ImageAsset.swift +3 -3
- package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +16 -2
- package/Sources/Private/Model/DotLottie/DotLottieImageProvider.swift +20 -7
- package/Sources/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +26 -0
- package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +8 -5
- package/Sources/Private/Model/LayerEffects/DropShadowEffect.swift +45 -0
- package/Sources/Private/Model/LayerEffects/EffectValues/ColorEffectValue.swift +38 -0
- package/Sources/Private/Model/LayerEffects/EffectValues/EffectValue.swift +98 -0
- package/Sources/Private/Model/LayerEffects/EffectValues/Vector1DEffectValue.swift +38 -0
- package/Sources/Private/Model/LayerEffects/LayerEffect.swift +103 -0
- package/Sources/Private/Model/LayerStyles/DropShadowStyle.swift +72 -0
- package/Sources/Private/Model/LayerStyles/LayerStyle.swift +85 -0
- package/Sources/Private/Model/Layers/LayerModel.swift +27 -0
- package/Sources/Private/Model/Objects/Marker.swift +1 -1
- package/Sources/Private/Model/Objects/Transform.swift +1 -2
- package/Sources/Private/Model/ShapeItems/GradientFill.swift +1 -1
- package/Sources/Private/Model/ShapeItems/GradientStroke.swift +2 -2
- package/Sources/Private/Model/ShapeItems/Merge.swift +1 -1
- package/Sources/Private/Model/ShapeItems/ShapeItem.swift +31 -26
- package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +0 -9
- package/Sources/Private/Model/ShapeItems/Star.swift +1 -1
- package/Sources/Private/Model/Text/Font.swift +2 -2
- package/Sources/Private/Model/Text/Glyph.swift +1 -1
- package/Sources/Private/RootAnimationLayer.swift +2 -2
- package/Sources/Private/Utility/Debugging/LayerDebugging.swift +3 -1
- package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +32 -2
- package/Sources/Private/Utility/Helpers/AnimationContext.swift +4 -2
- package/Sources/Private/Utility/Helpers/AnyEquatable.swift +24 -0
- package/Sources/Private/Utility/Helpers/Binding+Map.swift +18 -0
- package/Sources/Private/Utility/Helpers/View+ValueChanged.swift +20 -0
- package/Sources/Private/Utility/LottieAnimationSource.swift +41 -0
- package/Sources/Private/Utility/Primitives/BezierPath.swift +2 -2
- package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +2 -2
- package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +1 -1
- package/Sources/Public/Animation/LottieAnimation.swift +21 -2
- package/Sources/Public/Animation/LottieAnimationLayer.swift +1464 -0
- package/Sources/Public/Animation/LottieAnimationView.swift +258 -735
- package/Sources/Public/Animation/LottiePlaybackMode.swift +117 -0
- package/Sources/Public/Animation/LottieView.swift +462 -0
- package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +2 -1
- package/Sources/Public/AnimationCache/DefaultAnimationCache.swift +5 -6
- package/Sources/Public/Configuration/DecodingStrategy.swift +15 -0
- package/Sources/Public/Configuration/LottieConfiguration.swift +47 -0
- package/Sources/Public/Configuration/ReducedMotionOption.swift +114 -0
- package/Sources/Public/{LottieConfiguration.swift → Configuration/RenderingEngineOption.swift} +2 -57
- package/Sources/Public/{iOS → Controls}/AnimatedButton.swift +56 -13
- package/Sources/Public/{iOS → Controls}/AnimatedControl.swift +80 -8
- package/Sources/Public/{iOS → Controls}/AnimatedSwitch.swift +71 -31
- package/Sources/Public/Controls/LottieButton.swift +122 -0
- package/Sources/Public/Controls/LottieSwitch.swift +144 -0
- package/Sources/Public/Controls/LottieViewType.swift +79 -0
- package/Sources/Public/DotLottie/DotLottieConfiguration.swift +24 -0
- package/Sources/Public/DotLottie/DotLottieFile.swift +21 -6
- package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +79 -0
- package/Sources/Public/DynamicProperties/AnimationKeypath.swift +11 -1
- package/Sources/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +14 -0
- package/Sources/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +13 -0
- package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +22 -5
- package/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +14 -0
- package/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +13 -0
- package/Sources/Public/FontProvider/AnimationFontProvider.swift +8 -0
- package/Sources/Public/ImageProvider/AnimationImageProvider.swift +24 -0
- package/Sources/Public/Keyframes/Keyframe.swift +6 -0
- package/Sources/Public/Primitives/Vectors.swift +2 -2
- package/Sources/Public/TextProvider/AnimationTextProvider.swift +79 -6
- package/Sources/Public/iOS/AnimationSubview.swift +1 -1
- package/Sources/Public/iOS/BundleImageProvider.swift +16 -2
- package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +2 -2
- package/Sources/Public/iOS/FilepathImageProvider.swift +22 -3
- package/Sources/Public/iOS/LottieAnimationViewBase.swift +7 -1
- package/Sources/Public/macOS/BundleImageProvider.macOS.swift +15 -1
- package/Sources/Public/macOS/FilepathImageProvider.macOS.swift +21 -2
- package/lottie-ios.podspec +2 -1
- package/package.json +1 -1
- package/Sources/Private/Model/DotLottie/DotLottieConfiguration.swift +0 -15
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Helpers.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Progress.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Reading.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ReadingDeprecated.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+WritingDeprecated.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ZIP64.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+CompressionDeprecated.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Serialization.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+Serialization.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+ZIP64.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/README.md +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/URL+ZIP.swift +0 -0
|
@@ -6,7 +6,7 @@ import QuartzCore
|
|
|
6
6
|
// MARK: - TransformModel
|
|
7
7
|
|
|
8
8
|
/// This protocol mirrors the interface of `Transform`,
|
|
9
|
-
/// but
|
|
9
|
+
/// but is also implemented by `ShapeTransform` to allow
|
|
10
10
|
/// both transform types to share the same animation implementation.
|
|
11
11
|
protocol TransformModel {
|
|
12
12
|
/// The anchor point of the transform.
|
|
@@ -32,6 +32,12 @@ protocol TransformModel {
|
|
|
32
32
|
|
|
33
33
|
/// The rotation of the transform on Z axis.
|
|
34
34
|
var rotationZ: KeyframeGroup<LottieVector1D> { get }
|
|
35
|
+
|
|
36
|
+
/// The skew of the transform (only present on `ShapeTransform`s)
|
|
37
|
+
var _skew: KeyframeGroup<LottieVector1D>? { get }
|
|
38
|
+
|
|
39
|
+
/// The skew axis of the transform (only present on `ShapeTransform`s)
|
|
40
|
+
var _skewAxis: KeyframeGroup<LottieVector1D>? { get }
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
// MARK: - Transform + TransformModel
|
|
@@ -40,6 +46,8 @@ extension Transform: TransformModel {
|
|
|
40
46
|
var _position: KeyframeGroup<LottieVector3D>? { position }
|
|
41
47
|
var _positionX: KeyframeGroup<LottieVector1D>? { positionX }
|
|
42
48
|
var _positionY: KeyframeGroup<LottieVector1D>? { positionY }
|
|
49
|
+
var _skew: KeyframeGroup<LottieVector1D>? { nil }
|
|
50
|
+
var _skewAxis: KeyframeGroup<LottieVector1D>? { nil }
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
// MARK: - ShapeTransform + TransformModel
|
|
@@ -49,6 +57,8 @@ extension ShapeTransform: TransformModel {
|
|
|
49
57
|
var _position: KeyframeGroup<LottieVector3D>? { position }
|
|
50
58
|
var _positionX: KeyframeGroup<LottieVector1D>? { nil }
|
|
51
59
|
var _positionY: KeyframeGroup<LottieVector1D>? { nil }
|
|
60
|
+
var _skew: KeyframeGroup<LottieVector1D>? { skew }
|
|
61
|
+
var _skewAxis: KeyframeGroup<LottieVector1D>? { skewAxis }
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
// MARK: - CALayer + TransformModel
|
|
@@ -66,15 +76,18 @@ extension CALayer {
|
|
|
66
76
|
context: LayerAnimationContext)
|
|
67
77
|
throws
|
|
68
78
|
{
|
|
69
|
-
// CALayers don't support animating skew with its own set of keyframes.
|
|
70
|
-
// If the transform includes a skew, we have to combine all of the transform
|
|
71
|
-
// components into a single set of keyframes.
|
|
72
|
-
// Only `ShapeTransform` supports skews.
|
|
73
79
|
if
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
// CALayers don't support animating skew with its own set of keyframes.
|
|
81
|
+
// If the transform includes a skew, we have to combine all of the transform
|
|
82
|
+
// components into a single set of keyframes.
|
|
83
|
+
transformModel.hasSkew
|
|
84
|
+
// Negative `scale.x` values aren't applied correctly by Core Animation when animating
|
|
85
|
+
// `transform.scale.x` and `transform.scale.y` using separate `CAKeyframeAnimation`s
|
|
86
|
+
// (https://openradar.appspot.com/FB9862872). If the transform includes negative `scale.x`
|
|
87
|
+
// values, we have to combine all of the transform components into a single set of keyframes.
|
|
88
|
+
|| transformModel.hasNegativeXScaleValues
|
|
76
89
|
{
|
|
77
|
-
try addCombinedTransformAnimation(for:
|
|
90
|
+
try addCombinedTransformAnimation(for: transformModel, context: context)
|
|
78
91
|
}
|
|
79
92
|
|
|
80
93
|
else {
|
|
@@ -159,65 +172,10 @@ extension CALayer {
|
|
|
159
172
|
// Lottie animation files express scale as a numerical percentage value
|
|
160
173
|
// (e.g. 50%, 100%, 200%) so we divide by 100 to get the decimal values
|
|
161
174
|
// expected by Core Animation (e.g. 0.5, 1.0, 2.0).
|
|
162
|
-
|
|
163
|
-
// This appears to be because we animate `transform.scale.x` and `transform.scale.y`
|
|
164
|
-
// as separate `CAKeyframeAnimation`s instead of using a single animation of `transform` itself.
|
|
165
|
-
// https://openradar.appspot.com/FB9862872
|
|
166
|
-
// - To work around this, we set up a `rotationY` animation below
|
|
167
|
-
// to flip the view horizontally, which gives us the desired effect.
|
|
168
|
-
abs(CGFloat(scale.x) / 100)
|
|
175
|
+
CGFloat(scale.x) / 100
|
|
169
176
|
},
|
|
170
177
|
context: context)
|
|
171
178
|
|
|
172
|
-
/// iOS 14 and earlier doesn't properly support rendering transforms with
|
|
173
|
-
/// negative `scale.x` values: https://github.com/airbnb/lottie-ios/issues/1882
|
|
174
|
-
let osSupportsNegativeScaleValues: Bool = {
|
|
175
|
-
#if os(iOS) || os(tvOS)
|
|
176
|
-
if #available(iOS 15.0, tvOS 15.0, *) {
|
|
177
|
-
return true
|
|
178
|
-
} else {
|
|
179
|
-
return false
|
|
180
|
-
}
|
|
181
|
-
#else
|
|
182
|
-
// We'll assume this works correctly on macOS until told otherwise
|
|
183
|
-
return true
|
|
184
|
-
#endif
|
|
185
|
-
}()
|
|
186
|
-
|
|
187
|
-
lazy var hasNegativeXScaleValues = transformModel.scale.keyframes.contains(where: { $0.value.x < 0 })
|
|
188
|
-
|
|
189
|
-
// When `scale.x` is negative, we have to rotate the view
|
|
190
|
-
// half way around the y axis to flip it horizontally.
|
|
191
|
-
// - We don't do this in snapshot tests because it breaks the tests
|
|
192
|
-
// in surprising ways that don't happen at runtime. Definitely not ideal.
|
|
193
|
-
// - This isn't supported on iOS 14 and earlier either, so we have to
|
|
194
|
-
// log a compatibility error on devices running older OSs.
|
|
195
|
-
if TestHelpers.snapshotTestsAreRunning {
|
|
196
|
-
if hasNegativeXScaleValues {
|
|
197
|
-
context.logger.warn("""
|
|
198
|
-
Negative `scale.x` values are not displayed correctly in snapshot tests
|
|
199
|
-
""")
|
|
200
|
-
}
|
|
201
|
-
} else {
|
|
202
|
-
if !osSupportsNegativeScaleValues, hasNegativeXScaleValues {
|
|
203
|
-
try context.logCompatibilityIssue("""
|
|
204
|
-
iOS 14 and earlier does not support rendering negative `scale.x` values
|
|
205
|
-
""")
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
try addAnimation(
|
|
209
|
-
for: .rotationY,
|
|
210
|
-
keyframes: transformModel.scale,
|
|
211
|
-
value: { scale in
|
|
212
|
-
if scale.x < 0 {
|
|
213
|
-
return .pi
|
|
214
|
-
} else {
|
|
215
|
-
return 0
|
|
216
|
-
}
|
|
217
|
-
},
|
|
218
|
-
context: context)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
179
|
try addAnimation(
|
|
222
180
|
for: .scaleY,
|
|
223
181
|
keyframes: transformModel.scale,
|
|
@@ -225,9 +183,6 @@ extension CALayer {
|
|
|
225
183
|
// Lottie animation files express scale as a numerical percentage value
|
|
226
184
|
// (e.g. 50%, 100%, 200%) so we divide by 100 to get the decimal values
|
|
227
185
|
// expected by Core Animation (e.g. 0.5, 1.0, 2.0).
|
|
228
|
-
// - Negative `scaleY` values are correctly applied (they flip the view
|
|
229
|
-
// vertically), so we don't have to apply an additional rotation animation
|
|
230
|
-
// like we do for `scaleX`.
|
|
231
186
|
CGFloat(scale.y) / 100
|
|
232
187
|
},
|
|
233
188
|
context: context)
|
|
@@ -258,7 +213,7 @@ extension CALayer {
|
|
|
258
213
|
}
|
|
259
214
|
|
|
260
215
|
// Lottie animation files express rotation in degrees
|
|
261
|
-
// (e.g. 90º, 180º, 360º) so we
|
|
216
|
+
// (e.g. 90º, 180º, 360º) so we convert to radians to get the
|
|
262
217
|
// values expected by Core Animation (e.g. π/2, π, 2π)
|
|
263
218
|
|
|
264
219
|
try addAnimation(
|
|
@@ -282,7 +237,7 @@ extension CALayer {
|
|
|
282
237
|
keyframes: transformModel.rotationZ,
|
|
283
238
|
value: { rotationDegrees in
|
|
284
239
|
// Lottie animation files express rotation in degrees
|
|
285
|
-
// (e.g. 90º, 180º, 360º) so we
|
|
240
|
+
// (e.g. 90º, 180º, 360º) so we convert to radians to get the
|
|
286
241
|
// values expected by Core Animation (e.g. π/2, π, 2π)
|
|
287
242
|
rotationDegrees.cgFloatValue * .pi / 180
|
|
288
243
|
},
|
|
@@ -291,26 +246,44 @@ extension CALayer {
|
|
|
291
246
|
|
|
292
247
|
/// Adds an animation for the entire `transform` key by combining all of the
|
|
293
248
|
/// position / size / rotation / skew animations into a single set of keyframes.
|
|
294
|
-
/// This is
|
|
295
|
-
///
|
|
249
|
+
/// This is more expensive that animating each component separately, since
|
|
250
|
+
/// it may require manually interpolating the keyframes at each frame.
|
|
296
251
|
private func addCombinedTransformAnimation(
|
|
297
|
-
for transformModel:
|
|
252
|
+
for transformModel: TransformModel,
|
|
298
253
|
context: LayerAnimationContext)
|
|
299
254
|
throws
|
|
300
255
|
{
|
|
256
|
+
// Core Animation doesn't animate skew changes properly. If the skew value
|
|
257
|
+
// changes over the course of the animation then we have to manually
|
|
258
|
+
// compute the `CATransform3D` for each frame individually.
|
|
259
|
+
let requiresManualInterpolation = transformModel.hasSkewAnimation
|
|
260
|
+
|
|
301
261
|
let combinedTransformKeyframes = Keyframes.combined(
|
|
302
|
-
transformModel.
|
|
303
|
-
transformModel.
|
|
262
|
+
transformModel.anchorPoint,
|
|
263
|
+
transformModel._position ?? KeyframeGroup(LottieVector3D(x: 0.0, y: 0.0, z: 0.0)),
|
|
264
|
+
transformModel._positionX ?? KeyframeGroup(LottieVector1D(0)),
|
|
265
|
+
transformModel._positionY ?? KeyframeGroup(LottieVector1D(0)),
|
|
304
266
|
transformModel.scale,
|
|
305
267
|
transformModel.rotationX,
|
|
306
268
|
transformModel.rotationY,
|
|
307
269
|
transformModel.rotationZ,
|
|
308
|
-
transformModel.
|
|
309
|
-
transformModel.
|
|
310
|
-
|
|
311
|
-
|
|
270
|
+
transformModel._skew ?? KeyframeGroup(LottieVector1D(0)),
|
|
271
|
+
transformModel._skewAxis ?? KeyframeGroup(LottieVector1D(0)),
|
|
272
|
+
requiresManualInterpolation: requiresManualInterpolation,
|
|
273
|
+
makeCombinedResult: {
|
|
274
|
+
anchor, position, positionX, positionY, scale, rotationX, rotationY, rotationZ, skew, skewAxis
|
|
275
|
+
-> CATransform3D in
|
|
276
|
+
|
|
277
|
+
let transformPosition: CGPoint
|
|
278
|
+
if transformModel._positionX != nil, transformModel._positionY != nil {
|
|
279
|
+
transformPosition = CGPoint(x: positionX.cgFloatValue, y: positionY.cgFloatValue)
|
|
280
|
+
} else {
|
|
281
|
+
transformPosition = position.pointValue
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return CATransform3D.makeTransform(
|
|
312
285
|
anchor: anchor.pointValue,
|
|
313
|
-
position:
|
|
286
|
+
position: transformPosition,
|
|
314
287
|
scale: scale.sizeValue,
|
|
315
288
|
rotationX: rotationX.cgFloatValue,
|
|
316
289
|
rotationY: rotationY.cgFloatValue,
|
|
@@ -327,3 +300,36 @@ extension CALayer {
|
|
|
327
300
|
}
|
|
328
301
|
|
|
329
302
|
}
|
|
303
|
+
|
|
304
|
+
extension TransformModel {
|
|
305
|
+
/// Whether or not this transform has a non-zero skew value
|
|
306
|
+
var hasSkew: Bool {
|
|
307
|
+
guard
|
|
308
|
+
let _skew = _skew,
|
|
309
|
+
let _skewAxis = _skewAxis,
|
|
310
|
+
!_skew.keyframes.isEmpty,
|
|
311
|
+
!_skewAxis.keyframes.isEmpty
|
|
312
|
+
else {
|
|
313
|
+
return false
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return _skew.keyframes.contains(where: { $0.value.cgFloatValue != 0 })
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/// Whether or not this transform has a non-zero skew value which animates
|
|
320
|
+
var hasSkewAnimation: Bool {
|
|
321
|
+
guard
|
|
322
|
+
hasSkew,
|
|
323
|
+
let _skew = _skew,
|
|
324
|
+
let _skewAxis = _skewAxis
|
|
325
|
+
else { return false }
|
|
326
|
+
|
|
327
|
+
return _skew.keyframes.count > 1
|
|
328
|
+
|| _skewAxis.keyframes.count > 1
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/// Whether or not this `TransformModel` has any negative X scale values
|
|
332
|
+
var hasNegativeXScaleValues: Bool {
|
|
333
|
+
scale.keyframes.contains(where: { $0.value.x < 0 })
|
|
334
|
+
}
|
|
335
|
+
}
|
|
@@ -17,7 +17,7 @@ final class CoreAnimationLayer: BaseAnimationLayer {
|
|
|
17
17
|
init(
|
|
18
18
|
animation: LottieAnimation,
|
|
19
19
|
imageProvider: AnimationImageProvider,
|
|
20
|
-
textProvider:
|
|
20
|
+
textProvider: AnimationKeypathTextProvider,
|
|
21
21
|
fontProvider: AnimationFontProvider,
|
|
22
22
|
maskAnimationToBounds: Bool,
|
|
23
23
|
compatibilityTrackerMode: CompatibilityTracker.Mode,
|
|
@@ -94,8 +94,8 @@ final class CoreAnimationLayer: BaseAnimationLayer {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
/// The parent `
|
|
98
|
-
weak var
|
|
97
|
+
/// The parent `LottieAnimationLayer` that manages this layer
|
|
98
|
+
weak var lottieAnimationLayer: LottieAnimationLayer?
|
|
99
99
|
|
|
100
100
|
/// A closure that is called after this layer sets up its animation.
|
|
101
101
|
/// If the animation setup was unsuccessful and encountered compatibility issues,
|
|
@@ -108,9 +108,9 @@ final class CoreAnimationLayer: BaseAnimationLayer {
|
|
|
108
108
|
didSet { reloadImages() }
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
/// The `
|
|
111
|
+
/// The `AnimationKeypathTextProvider` that `TextLayer`'s use to retrieve texts,
|
|
112
112
|
/// that they should use to render their text context
|
|
113
|
-
var textProvider:
|
|
113
|
+
var textProvider: AnimationKeypathTextProvider {
|
|
114
114
|
didSet {
|
|
115
115
|
// We need to rebuild the current animation after updating the text provider,
|
|
116
116
|
// since this is used in `TextLayer.setupAnimations(context:)`
|
|
@@ -316,7 +316,7 @@ final class CoreAnimationLayer: BaseAnimationLayer {
|
|
|
316
316
|
else { return }
|
|
317
317
|
|
|
318
318
|
if isAnimationPlaying == true {
|
|
319
|
-
|
|
319
|
+
lottieAnimationLayer?.updateInFlightAnimation()
|
|
320
320
|
} else {
|
|
321
321
|
let currentFrame = currentFrame
|
|
322
322
|
removeAnimations()
|
|
@@ -449,7 +449,7 @@ extension CoreAnimationLayer: RootAnimationLayer {
|
|
|
449
449
|
}
|
|
450
450
|
|
|
451
451
|
func forceDisplayUpdate() {
|
|
452
|
-
|
|
452
|
+
display()
|
|
453
453
|
}
|
|
454
454
|
|
|
455
455
|
func logHierarchyKeypaths() {
|
|
@@ -10,12 +10,18 @@ enum Keyframes {
|
|
|
10
10
|
/// Combines the given keyframe groups of `Keyframe<T>`s into a single keyframe group of of `Keyframe<[T]>`s
|
|
11
11
|
/// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged
|
|
12
12
|
/// - Otherwise, the keyframes are manually interpolated at each frame in the animation
|
|
13
|
-
static func combined<T>(
|
|
13
|
+
static func combined<T>(
|
|
14
|
+
_ allGroups: [KeyframeGroup<T>],
|
|
15
|
+
requiresManualInterpolation: Bool = false)
|
|
16
|
+
-> KeyframeGroup<[T]>
|
|
14
17
|
where T: AnyInterpolatable
|
|
15
18
|
{
|
|
16
|
-
Keyframes.combined(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
Keyframes.combined(
|
|
20
|
+
allGroups,
|
|
21
|
+
requiresManualInterpolation: requiresManualInterpolation,
|
|
22
|
+
makeCombinedResult: { untypedValues in
|
|
23
|
+
untypedValues.compactMap { $0 as? T }
|
|
24
|
+
})
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
/// Combines the given keyframe groups of `Keyframe<T>`s into a single keyframe group of of `Keyframe<[T]>`s
|
|
@@ -24,6 +30,7 @@ enum Keyframes {
|
|
|
24
30
|
static func combined<T1, T2, CombinedResult>(
|
|
25
31
|
_ k1: KeyframeGroup<T1>,
|
|
26
32
|
_ k2: KeyframeGroup<T2>,
|
|
33
|
+
requiresManualInterpolation: Bool = false,
|
|
27
34
|
makeCombinedResult: (T1, T2) throws -> CombinedResult)
|
|
28
35
|
rethrows
|
|
29
36
|
-> KeyframeGroup<CombinedResult>
|
|
@@ -31,6 +38,7 @@ enum Keyframes {
|
|
|
31
38
|
{
|
|
32
39
|
try Keyframes.combined(
|
|
33
40
|
[k1, k2],
|
|
41
|
+
requiresManualInterpolation: requiresManualInterpolation,
|
|
34
42
|
makeCombinedResult: { untypedValues in
|
|
35
43
|
guard
|
|
36
44
|
let t1 = untypedValues[0] as? T1,
|
|
@@ -48,12 +56,14 @@ enum Keyframes {
|
|
|
48
56
|
_ k1: KeyframeGroup<T1>,
|
|
49
57
|
_ k2: KeyframeGroup<T2>,
|
|
50
58
|
_ k3: KeyframeGroup<T3>,
|
|
59
|
+
requiresManualInterpolation: Bool = false,
|
|
51
60
|
makeCombinedResult: (T1, T2, T3) -> CombinedResult)
|
|
52
61
|
-> KeyframeGroup<CombinedResult>
|
|
53
62
|
where T1: AnyInterpolatable, T2: AnyInterpolatable, T3: AnyInterpolatable
|
|
54
63
|
{
|
|
55
64
|
Keyframes.combined(
|
|
56
65
|
[k1, k2, k3],
|
|
66
|
+
requiresManualInterpolation: requiresManualInterpolation,
|
|
57
67
|
makeCombinedResult: { untypedValues in
|
|
58
68
|
guard
|
|
59
69
|
let t1 = untypedValues[0] as? T1,
|
|
@@ -76,6 +86,7 @@ enum Keyframes {
|
|
|
76
86
|
_ k5: KeyframeGroup<T5>,
|
|
77
87
|
_ k6: KeyframeGroup<T6>,
|
|
78
88
|
_ k7: KeyframeGroup<T7>,
|
|
89
|
+
requiresManualInterpolation: Bool = false,
|
|
79
90
|
makeCombinedResult: (T1, T2, T3, T4, T5, T6, T7) -> CombinedResult)
|
|
80
91
|
-> KeyframeGroup<CombinedResult>
|
|
81
92
|
where T1: AnyInterpolatable, T2: AnyInterpolatable, T3: AnyInterpolatable, T4: AnyInterpolatable,
|
|
@@ -83,6 +94,7 @@ enum Keyframes {
|
|
|
83
94
|
{
|
|
84
95
|
Keyframes.combined(
|
|
85
96
|
[k1, k2, k3, k4, k5, k6, k7],
|
|
97
|
+
requiresManualInterpolation: requiresManualInterpolation,
|
|
86
98
|
makeCombinedResult: { untypedValues in
|
|
87
99
|
guard
|
|
88
100
|
let t1 = untypedValues[0] as? T1,
|
|
@@ -110,6 +122,7 @@ enum Keyframes {
|
|
|
110
122
|
_ k6: KeyframeGroup<T6>,
|
|
111
123
|
_ k7: KeyframeGroup<T7>,
|
|
112
124
|
_ k8: KeyframeGroup<T8>,
|
|
125
|
+
requiresManualInterpolation: Bool = false,
|
|
113
126
|
makeCombinedResult: (T1, T2, T3, T4, T5, T6, T7, T8) -> CombinedResult)
|
|
114
127
|
-> KeyframeGroup<CombinedResult>
|
|
115
128
|
where T1: AnyInterpolatable, T2: AnyInterpolatable, T3: AnyInterpolatable, T4: AnyInterpolatable,
|
|
@@ -117,6 +130,7 @@ enum Keyframes {
|
|
|
117
130
|
{
|
|
118
131
|
Keyframes.combined(
|
|
119
132
|
[k1, k2, k3, k4, k5, k6, k7, k8],
|
|
133
|
+
requiresManualInterpolation: requiresManualInterpolation,
|
|
120
134
|
makeCombinedResult: { untypedValues in
|
|
121
135
|
guard
|
|
122
136
|
let t1 = untypedValues[0] as? T1,
|
|
@@ -133,6 +147,48 @@ enum Keyframes {
|
|
|
133
147
|
})
|
|
134
148
|
}
|
|
135
149
|
|
|
150
|
+
/// Combines the given keyframe groups of `Keyframe<T>`s into a single keyframe group of of `Keyframe<[T]>`s
|
|
151
|
+
/// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged
|
|
152
|
+
/// - Otherwise, the keyframes are manually interpolated at each frame in the animation
|
|
153
|
+
static func combined<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, CombinedResult>(
|
|
154
|
+
_ k1: KeyframeGroup<T1>,
|
|
155
|
+
_ k2: KeyframeGroup<T2>,
|
|
156
|
+
_ k3: KeyframeGroup<T3>,
|
|
157
|
+
_ k4: KeyframeGroup<T4>,
|
|
158
|
+
_ k5: KeyframeGroup<T5>,
|
|
159
|
+
_ k6: KeyframeGroup<T6>,
|
|
160
|
+
_ k7: KeyframeGroup<T7>,
|
|
161
|
+
_ k8: KeyframeGroup<T8>,
|
|
162
|
+
_ k9: KeyframeGroup<T9>,
|
|
163
|
+
_ k10: KeyframeGroup<T10>,
|
|
164
|
+
requiresManualInterpolation: Bool = false,
|
|
165
|
+
makeCombinedResult: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) -> CombinedResult)
|
|
166
|
+
-> KeyframeGroup<CombinedResult>
|
|
167
|
+
where T1: AnyInterpolatable, T2: AnyInterpolatable, T3: AnyInterpolatable, T4: AnyInterpolatable,
|
|
168
|
+
T5: AnyInterpolatable, T6: AnyInterpolatable, T7: AnyInterpolatable, T8: AnyInterpolatable,
|
|
169
|
+
T9: AnyInterpolatable, T10: AnyInterpolatable
|
|
170
|
+
{
|
|
171
|
+
Keyframes.combined(
|
|
172
|
+
[k1, k2, k3, k4, k5, k6, k7, k8, k9, k10],
|
|
173
|
+
requiresManualInterpolation: requiresManualInterpolation,
|
|
174
|
+
makeCombinedResult: { untypedValues in
|
|
175
|
+
guard
|
|
176
|
+
let t1 = untypedValues[0] as? T1,
|
|
177
|
+
let t2 = untypedValues[1] as? T2,
|
|
178
|
+
let t3 = untypedValues[2] as? T3,
|
|
179
|
+
let t4 = untypedValues[3] as? T4,
|
|
180
|
+
let t5 = untypedValues[4] as? T5,
|
|
181
|
+
let t6 = untypedValues[5] as? T6,
|
|
182
|
+
let t7 = untypedValues[6] as? T7,
|
|
183
|
+
let t8 = untypedValues[7] as? T8,
|
|
184
|
+
let t9 = untypedValues[8] as? T9,
|
|
185
|
+
let t10 = untypedValues[9] as? T10
|
|
186
|
+
else { return nil }
|
|
187
|
+
|
|
188
|
+
return makeCombinedResult(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10)
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
136
192
|
// MARK: Private
|
|
137
193
|
|
|
138
194
|
/// Combines the given `[KeyframeGroup]` of `Keyframe<T>`s into a single `KeyframeGroup` of `Keyframe<CombinedResult>`s
|
|
@@ -141,8 +197,12 @@ enum Keyframes {
|
|
|
141
197
|
///
|
|
142
198
|
/// `makeCombinedResult` is a closure that takes an array of keyframe values (with the exact same length as `AnyKeyframeGroup`),
|
|
143
199
|
/// casts them to the expected type, and combined them into the final resulting keyframe.
|
|
200
|
+
///
|
|
201
|
+
/// `requiresManualInterpolation` determines whether the keyframes must be computed using `Keyframes.manuallyInterpolated`,
|
|
202
|
+
/// which interpolates the value at each frame, or if the keyframes can simply be combined.
|
|
144
203
|
private static func combined<CombinedResult>(
|
|
145
204
|
_ allGroups: [AnyKeyframeGroup],
|
|
205
|
+
requiresManualInterpolation: Bool,
|
|
146
206
|
makeCombinedResult: ([Any]) throws -> CombinedResult?)
|
|
147
207
|
rethrows
|
|
148
208
|
-> KeyframeGroup<CombinedResult>
|
|
@@ -155,6 +215,7 @@ enum Keyframes {
|
|
|
155
215
|
let animatingKeyframes = untypedGroups.filter { $0.keyframes.count > 1 }
|
|
156
216
|
|
|
157
217
|
guard
|
|
218
|
+
!requiresManualInterpolation,
|
|
158
219
|
!allGroups.isEmpty,
|
|
159
220
|
animatingKeyframes.allSatisfy({ $0.hasSameTimingParameters(as: animatingKeyframes[0]) })
|
|
160
221
|
else {
|
|
@@ -222,7 +283,7 @@ enum Keyframes {
|
|
|
222
283
|
extension KeyframeGroup {
|
|
223
284
|
/// Whether or not all of the keyframes in this `KeyframeGroup` have the same
|
|
224
285
|
/// timing parameters as the corresponding keyframe in the other given `KeyframeGroup`
|
|
225
|
-
func hasSameTimingParameters<
|
|
286
|
+
func hasSameTimingParameters<U>(as other: KeyframeGroup<U>) -> Bool {
|
|
226
287
|
guard keyframes.count == other.keyframes.count else {
|
|
227
288
|
return false
|
|
228
289
|
}
|
|
@@ -236,7 +297,7 @@ extension KeyframeGroup {
|
|
|
236
297
|
extension Keyframe {
|
|
237
298
|
/// Whether or not this keyframe has the same timing parameters as the given keyframe,
|
|
238
299
|
/// excluding `spatialInTangent` and `spatialOutTangent`.
|
|
239
|
-
fileprivate func hasSameTimingParameters<
|
|
300
|
+
fileprivate func hasSameTimingParameters<U>(as other: Keyframe<U>) -> Bool {
|
|
240
301
|
time == other.time
|
|
241
302
|
&& isHold == other.isHold
|
|
242
303
|
&& inTangent == other.inTangent
|
|
@@ -41,8 +41,8 @@ struct LayerAnimationContext {
|
|
|
41
41
|
/// The AnimationKeypath represented by the current layer
|
|
42
42
|
var currentKeypath: AnimationKeypath
|
|
43
43
|
|
|
44
|
-
/// The `
|
|
45
|
-
var textProvider:
|
|
44
|
+
/// The `AnimationKeypathTextProvider`
|
|
45
|
+
var textProvider: AnimationKeypathTextProvider
|
|
46
46
|
|
|
47
47
|
/// Records the given animation keypath so it can be logged or collected into a list
|
|
48
48
|
/// - Used for `CoreAnimationLayer.logHierarchyKeypaths()` and `allHierarchyKeypaths()`
|
|
@@ -67,6 +67,15 @@ class BaseCompositionLayer: BaseAnimationLayer {
|
|
|
67
67
|
inFrame: CGFloat(baseLayerModel.inFrame),
|
|
68
68
|
outFrame: CGFloat(baseLayerModel.outFrame),
|
|
69
69
|
context: context)
|
|
70
|
+
|
|
71
|
+
// There are two different drop shadow schemas, either using `DropShadowEffect` or `DropShadowStyle`.
|
|
72
|
+
// If both happen to be present, prefer the `DropShadowEffect` (which is the drop shadow schema
|
|
73
|
+
// supported on other platforms).
|
|
74
|
+
let dropShadowEffect = baseLayerModel.effects.first(where: { $0 is DropShadowEffect }) as? DropShadowModel
|
|
75
|
+
let dropShadowStyle = baseLayerModel.styles.first(where: { $0 is DropShadowStyle }) as? DropShadowModel
|
|
76
|
+
if let dropShadowModel = dropShadowEffect ?? dropShadowStyle {
|
|
77
|
+
try contentsLayer.addDropShadowAnimations(for: dropShadowModel, context: context)
|
|
78
|
+
}
|
|
70
79
|
}
|
|
71
80
|
}
|
|
72
81
|
|
|
@@ -9,7 +9,7 @@ import QuartzCore
|
|
|
9
9
|
struct LayerContext {
|
|
10
10
|
let animation: LottieAnimation
|
|
11
11
|
let imageProvider: AnimationImageProvider
|
|
12
|
-
let textProvider:
|
|
12
|
+
let textProvider: AnimationKeypathTextProvider
|
|
13
13
|
let fontProvider: AnimationFontProvider
|
|
14
14
|
let compatibilityTracker: CompatibilityTracker
|
|
15
15
|
var layerName: String
|
|
@@ -93,4 +93,6 @@ extension RepeaterTransform: TransformModel {
|
|
|
93
93
|
var _position: KeyframeGroup<LottieVector3D>? { position }
|
|
94
94
|
var _positionX: KeyframeGroup<LottieVector1D>? { nil }
|
|
95
95
|
var _positionY: KeyframeGroup<LottieVector1D>? { nil }
|
|
96
|
+
var _skew: KeyframeGroup<LottieVector1D>? { nil }
|
|
97
|
+
var _skewAxis: KeyframeGroup<LottieVector1D>? { nil }
|
|
96
98
|
}
|
|
@@ -41,7 +41,8 @@ final class ShapeLayer: BaseCompositionLayer {
|
|
|
41
41
|
if let repeater = shapeLayer.items.first(where: { $0 is Repeater }) as? Repeater {
|
|
42
42
|
try setUpRepeater(repeater, context: context)
|
|
43
43
|
} else {
|
|
44
|
-
|
|
44
|
+
let shapeItems = shapeLayer.items.map { ShapeItemLayer.Item(item: $0, groupPath: []) }
|
|
45
|
+
try setupGroups(from: shapeItems, parentGroup: nil, parentGroupPath: [], context: context)
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -50,7 +51,8 @@ final class ShapeLayer: BaseCompositionLayer {
|
|
|
50
51
|
let copyCount = Int(try repeater.copies.exactlyOneKeyframe(context: context, description: "repeater copies").value)
|
|
51
52
|
|
|
52
53
|
for index in 0..<copyCount {
|
|
53
|
-
|
|
54
|
+
let shapeItems = items.map { ShapeItemLayer.Item(item: $0, groupPath: []) }
|
|
55
|
+
for groupLayer in try makeGroupLayers(from: shapeItems, parentGroup: nil, parentGroupPath: [], context: context) {
|
|
54
56
|
let repeatedLayer = RepeaterLayer(repeater: repeater, childLayer: groupLayer, index: index)
|
|
55
57
|
addSublayer(repeatedLayer)
|
|
56
58
|
}
|
|
@@ -127,7 +129,7 @@ final class GroupLayer: BaseAnimationLayer {
|
|
|
127
129
|
private func setupLayerHierarchy(context: LayerContext) throws {
|
|
128
130
|
// Groups can contain other groups, so we may have to continue
|
|
129
131
|
// recursively creating more `GroupLayer`s
|
|
130
|
-
try setupGroups(from:
|
|
132
|
+
try setupGroups(from: items, parentGroup: group, parentGroupPath: groupPath, context: context)
|
|
131
133
|
|
|
132
134
|
// Create `ShapeItemLayer`s for each subgroup of shapes that should be rendered as a single unit
|
|
133
135
|
// - These groups are listed from front-to-back, so we have to add the sublayers in reverse order
|
|
@@ -183,7 +185,7 @@ extension CALayer {
|
|
|
183
185
|
/// - Each `Group` item becomes its own `GroupLayer` sublayer.
|
|
184
186
|
/// - Other `ShapeItem` are applied to all sublayers
|
|
185
187
|
fileprivate func setupGroups(
|
|
186
|
-
from items: [
|
|
188
|
+
from items: [ShapeItemLayer.Item],
|
|
187
189
|
parentGroup: Group?,
|
|
188
190
|
parentGroupPath: [String],
|
|
189
191
|
context: LayerContext)
|
|
@@ -204,29 +206,27 @@ extension CALayer {
|
|
|
204
206
|
/// - Each `Group` item becomes its own `GroupLayer` sublayer.
|
|
205
207
|
/// - Other `ShapeItem` are applied to all sublayers
|
|
206
208
|
fileprivate func makeGroupLayers(
|
|
207
|
-
from items: [
|
|
209
|
+
from items: [ShapeItemLayer.Item],
|
|
208
210
|
parentGroup: Group?,
|
|
209
211
|
parentGroupPath: [String],
|
|
210
212
|
context: LayerContext)
|
|
211
213
|
throws -> [GroupLayer]
|
|
212
214
|
{
|
|
213
|
-
var
|
|
214
|
-
|
|
215
|
-
.grouped(by: { $0 is Group })
|
|
215
|
+
var groupItems = items.compactMap { $0.item as? Group }.filter { !$0.hidden }
|
|
216
|
+
var otherItems = items.filter { !($0.item is Group) && !$0.item.hidden }
|
|
216
217
|
|
|
217
218
|
// Handle the top-level `shapeLayer.items` array. This is typically just a single `Group`,
|
|
218
219
|
// but in practice can be any combination of items. The implementation expects all path-drawing
|
|
219
220
|
// shape items to be managed by a `GroupLayer`, so if there's a top-level path item we
|
|
220
221
|
// have to create a placeholder group.
|
|
221
|
-
if parentGroup == nil, otherItems.contains(where: { $0.drawsCGPath }) {
|
|
222
|
-
groupItems = [Group(items: items, name: "")]
|
|
222
|
+
if parentGroup == nil, otherItems.contains(where: { $0.item.drawsCGPath }) {
|
|
223
|
+
groupItems = [Group(items: items.map { $0.item }, name: "")]
|
|
223
224
|
otherItems = []
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
// Any child items that wouldn't be included in a valid shape render group
|
|
227
228
|
// need to be applied to child groups (otherwise they'd be silently ignored).
|
|
228
229
|
let inheritedItemsForChildGroups = otherItems
|
|
229
|
-
.map { ShapeItemLayer.Item(item: $0, groupPath: parentGroupPath) }
|
|
230
230
|
.shapeRenderGroups(groupHasChildGroupsToInheritUnusedItems: !groupItems.isEmpty)
|
|
231
231
|
.unusedItems
|
|
232
232
|
|
|
@@ -235,8 +235,6 @@ extension CALayer {
|
|
|
235
235
|
let groupsInZAxisOrder = groupItems.reversed()
|
|
236
236
|
|
|
237
237
|
return try groupsInZAxisOrder.compactMap { group in
|
|
238
|
-
guard let group = group as? Group else { return nil }
|
|
239
|
-
|
|
240
238
|
var pathForChildren = parentGroupPath
|
|
241
239
|
if !group.name.isEmpty {
|
|
242
240
|
pathForChildren.append(group.name)
|
|
@@ -30,16 +30,31 @@ final class SolidLayer: BaseCompositionLayer {
|
|
|
30
30
|
super.init(layer: typedLayer)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// MARK: Internal
|
|
34
|
+
|
|
35
|
+
override func setupAnimations(context: LayerAnimationContext) throws {
|
|
36
|
+
try super.setupAnimations(context: context)
|
|
37
|
+
|
|
38
|
+
// Even though the Lottie json schema provides a fixed `solidLayer.colorHex` value,
|
|
39
|
+
// we still need to create a set of keyframes and go through the standard `CAAnimation`
|
|
40
|
+
// codepath so that this value can be customized using the custom `ValueProvider`s API.
|
|
41
|
+
try shapeLayer.addAnimation(
|
|
42
|
+
for: .fillColor,
|
|
43
|
+
keyframes: KeyframeGroup(solidLayer.colorHex.cgColor),
|
|
44
|
+
value: { $0 },
|
|
45
|
+
context: context)
|
|
46
|
+
}
|
|
47
|
+
|
|
33
48
|
// MARK: Private
|
|
34
49
|
|
|
35
50
|
private let solidLayer: SolidLayerModel
|
|
36
51
|
|
|
52
|
+
/// Render the fill color in a child `CAShapeLayer`
|
|
53
|
+
/// - Using a `CAShapeLayer` specifically, instead of a `CALayer` with a `backgroundColor`,
|
|
54
|
+
/// allows the size of the fill shape to be different from `contentsLayer.size`.
|
|
55
|
+
private let shapeLayer = CAShapeLayer()
|
|
56
|
+
|
|
37
57
|
private func setupContentLayer() {
|
|
38
|
-
// Render the fill color in a child `CAShapeLayer`
|
|
39
|
-
// - Using a `CAShapeLayer` specifically, instead of a `CALayer` with a `backgroundColor`,
|
|
40
|
-
// allows the size of the fill shape to be different from `contentsLayer.size`.
|
|
41
|
-
let shapeLayer = CAShapeLayer()
|
|
42
|
-
shapeLayer.fillColor = solidLayer.colorHex.cgColor
|
|
43
58
|
shapeLayer.path = CGPath(rect: .init(x: 0, y: 0, width: solidLayer.width, height: solidLayer.height), transform: nil)
|
|
44
59
|
addSublayer(shapeLayer)
|
|
45
60
|
}
|
|
@@ -44,9 +44,19 @@ final class TextLayer: BaseCompositionLayer {
|
|
|
44
44
|
context: textAnimationContext,
|
|
45
45
|
description: "text layer text")
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
// Prior to Lottie 4.3.0 the Core Animation rendering engine always just used `LegacyAnimationTextProvider`
|
|
48
|
+
// but incorrectly called it with the full keypath string, unlike the Main Thread rendering engine
|
|
49
|
+
// which only used the last component of the keypath. Starting in Lottie 4.3.0 we use `AnimationKeypathTextProvider`
|
|
50
|
+
// instead if implemented.
|
|
51
|
+
if let keypathTextValue = context.textProvider.text(for: textAnimationContext.currentKeypath, sourceText: sourceText.text) {
|
|
52
|
+
renderLayer.text = keypathTextValue
|
|
53
|
+
} else if let legacyTextProvider = context.textProvider as? LegacyAnimationTextProvider {
|
|
54
|
+
renderLayer.text = legacyTextProvider.textFor(
|
|
55
|
+
keypathName: textAnimationContext.currentKeypath.fullPath,
|
|
56
|
+
sourceText: sourceText.text)
|
|
57
|
+
} else {
|
|
58
|
+
renderLayer.text = sourceText.text
|
|
59
|
+
}
|
|
50
60
|
|
|
51
61
|
renderLayer.sizeToFit()
|
|
52
62
|
}
|