lottie-ios 3.4.4 → 4.0.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/.gitattributes +1 -0
- package/.github/workflows/main.yml +7 -7
- package/Gemfile.lock +1 -1
- package/Lottie.xcodeproj/project.pbxproj +286 -68
- package/Lottie.xcworkspace/xcshareddata/swiftpm/Package.resolved +18 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +3 -24
- package/Package.swift +2 -2
- package/README.md +10 -3
- package/Rakefile +1 -1
- package/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift +6 -9
- package/Sources/Private/CoreAnimation/Animations/CustomPathAnimation.swift +42 -3
- package/Sources/Private/CoreAnimation/Animations/EllipseAnimation.swift +5 -17
- package/Sources/Private/CoreAnimation/Animations/GradientAnimations.swift +25 -17
- package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +2 -2
- package/Sources/Private/CoreAnimation/Animations/OpacityAnimation.swift +1 -1
- package/Sources/Private/CoreAnimation/Animations/RectangleAnimation.swift +9 -20
- package/Sources/Private/CoreAnimation/Animations/ShapeAnimation.swift +39 -25
- package/Sources/Private/CoreAnimation/Animations/StarAnimation.swift +13 -32
- package/Sources/Private/CoreAnimation/Animations/StrokeAnimation.swift +4 -4
- package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +13 -13
- package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +4 -4
- package/Sources/Private/CoreAnimation/Extensions/KeyframeGroup+exactlyOneKeyframe.swift +4 -12
- package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +222 -0
- package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +1 -1
- package/Sources/Private/CoreAnimation/Layers/CALayer+setupLayerHierarchy.swift +8 -6
- package/Sources/Private/CoreAnimation/Layers/GradientRenderLayer.swift +13 -10
- package/Sources/Private/CoreAnimation/Layers/InfiniteOpaqueAnimationLayer.swift +56 -0
- package/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift +1 -1
- package/Sources/Private/CoreAnimation/Layers/PreCompLayer.swift +2 -3
- package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +9 -9
- package/Sources/Private/CoreAnimation/Layers/ShapeItemLayer.swift +9 -3
- package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +7 -18
- package/Sources/Private/CoreAnimation/ValueProviderStore.swift +2 -3
- package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +5 -5
- package/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift +3 -3
- package/Sources/Private/MainThread/LayerContainers/CompLayers/SolidCompositionLayer.swift +2 -2
- package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +7 -6
- package/Sources/Private/MainThread/LayerContainers/Utility/InvertedMatteLayer.swift +1 -3
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.swift +10 -10
- package/Sources/Private/MainThread/NodeRenderSystem/Extensions/ItemsExtension.swift +7 -0
- package/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift +2 -2
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/RoundedCornersNode.swift +85 -0
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +4 -4
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +11 -12
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift +10 -11
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +2 -2
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +5 -5
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/RectNode.swift +3 -3
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/StarNode.swift +9 -9
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +14 -14
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift +2 -2
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +3 -3
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +10 -10
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +14 -14
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +11 -11
- package/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +1 -1
- package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +52 -0
- package/Sources/Private/Model/DotLottie/DotLottieConfiguration.swift +15 -0
- package/Sources/Private/Model/DotLottie/DotLottieImageProvider.swift +73 -0
- package/Sources/Private/Model/DotLottie/DotLottieManifest.swift +53 -0
- package/Sources/Private/Model/DotLottie/DotLottieUtils.swift +58 -0
- package/Sources/Private/Model/DotLottie/Zip/Data+Compression.swift +134 -0
- package/Sources/Private/Model/DotLottie/Zip/Data+Serialization.swift +87 -0
- package/Sources/Private/Model/DotLottie/Zip/FileManager+ZIP.swift +130 -0
- package/Sources/Private/Model/DotLottie/Zip/ZipArchive.swift +474 -0
- package/Sources/Private/Model/DotLottie/Zip/ZipEntry+Serialization.swift +189 -0
- package/Sources/Private/Model/DotLottie/Zip/ZipEntry+ZIP64.swift +179 -0
- package/Sources/Private/Model/DotLottie/Zip/ZipEntry.swift +227 -0
- package/Sources/Private/Model/Extensions/Bundle.swift +26 -0
- package/Sources/Private/Model/Keyframes/KeyframeData.swift +12 -12
- package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +9 -2
- package/Sources/Private/Model/Layers/LayerModel.swift +1 -1
- package/Sources/Private/Model/Layers/PreCompLayerModel.swift +3 -3
- package/Sources/Private/Model/Layers/TextLayerModel.swift +1 -1
- package/Sources/Private/Model/Objects/DashPattern.swift +2 -2
- package/Sources/Private/Model/Objects/Mask.swift +10 -8
- package/Sources/Private/Model/Objects/Transform.swift +39 -35
- package/Sources/Private/Model/ShapeItems/Ellipse.swift +6 -6
- package/Sources/Private/Model/ShapeItems/Fill.swift +6 -6
- package/Sources/Private/Model/ShapeItems/GradientFill.swift +15 -15
- package/Sources/Private/Model/ShapeItems/GradientStroke.swift +19 -19
- package/Sources/Private/Model/ShapeItems/Rectangle.swift +9 -9
- package/Sources/Private/Model/ShapeItems/Repeater.swift +37 -32
- package/Sources/Private/Model/ShapeItems/RoundedCorners.swift +47 -0
- package/Sources/Private/Model/ShapeItems/ShapeItem.swift +4 -0
- package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +34 -28
- package/Sources/Private/Model/ShapeItems/Star.swift +21 -21
- package/Sources/Private/Model/ShapeItems/Stroke.swift +10 -10
- package/Sources/Private/Model/ShapeItems/Trim.swift +9 -9
- package/Sources/Private/Model/Text/Font.swift +1 -1
- package/Sources/Private/Model/Text/TextAnimator.swift +33 -33
- package/Sources/Private/Model/Text/TextDocument.swift +8 -8
- package/Sources/Private/RootAnimationLayer.swift +4 -4
- package/Sources/Private/Utility/Debugging/LayerDebugging.swift +12 -18
- package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +2 -2
- package/Sources/Private/Utility/Extensions/CGColor+RGB.swift +12 -7
- package/Sources/Private/Utility/Interpolatable/InterpolatableExtensions.swift +1 -1
- package/Sources/Private/Utility/Interpolatable/KeyframeGroup+Extensions.swift +4 -4
- package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +147 -0
- package/Sources/Private/Utility/Primitives/ColorExtension.swift +8 -11
- package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +22 -22
- package/Sources/{Private/Model/Animation.swift → Public/Animation/LottieAnimation.swift} +8 -8
- package/Sources/Public/Animation/{AnimationPublic.swift → LottieAnimationHelpers.swift} +62 -27
- package/Sources/Public/Animation/{AnimationView.swift → LottieAnimationView.swift} +122 -26
- package/Sources/Public/Animation/LottieAnimationViewInitializers.swift +222 -0
- package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +4 -4
- package/Sources/Public/AnimationCache/DefaultAnimationCache.swift +54 -0
- package/Sources/Public/AnimationCache/LRUAnimationCache.swift +4 -52
- package/Sources/Public/AnimationCache/LottieAnimationCache.swift +15 -0
- package/Sources/Public/DotLottie/Cache/DotLottieCache.swift +53 -0
- package/Sources/Public/DotLottie/Cache/DotLottieCacheProvider.swift +23 -0
- package/Sources/Public/DotLottie/DotLottieFile.swift +161 -0
- package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +265 -0
- package/Sources/Public/DynamicProperties/AnimationKeypath.swift +3 -3
- package/Sources/Public/DynamicProperties/AnyValueProvider.swift +2 -2
- package/Sources/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +10 -10
- package/Sources/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +4 -4
- package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +5 -5
- package/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +2 -2
- package/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +2 -2
- package/Sources/Public/FontProvider/AnimationFontProvider.swift +1 -1
- package/Sources/Public/ImageProvider/AnimationImageProvider.swift +2 -2
- package/Sources/Public/Keyframes/Interpolatable.swift +17 -17
- package/Sources/Public/Keyframes/Keyframe.swift +10 -10
- package/Sources/Public/LottieConfiguration.swift +25 -7
- package/Sources/Public/Primitives/{Color.swift → LottieColor.swift} +3 -3
- package/Sources/Public/Primitives/Vectors.swift +4 -4
- package/Sources/Public/TextProvider/AnimationTextProvider.swift +1 -1
- package/Sources/Public/iOS/AnimatedButton.swift +1 -1
- package/Sources/Public/iOS/AnimatedControl.swift +6 -6
- package/Sources/Public/iOS/AnimatedSwitch.swift +1 -1
- package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +8 -8
- package/Sources/Public/iOS/{AnimationViewBase.swift → LottieAnimationViewBase.swift} +4 -4
- package/Sources/Public/iOS/UIColorExtension.swift +2 -2
- package/Sources/Public/macOS/{AnimationViewBase.macOS.swift → LottieAnimationViewBase.macOS.swift} +4 -4
- package/Tests/AnimationCacheProviderTests.swift +40 -0
- package/Tests/AnimationKeypathTests.swift +20 -13
- package/Tests/AnimationViewTests.swift +64 -0
- package/Tests/AutomaticEngineTests.swift +11 -2
- package/Tests/ParsingTests.swift +4 -2
- package/Tests/PerformanceTests.swift +9 -9
- package/Tests/SnapshotConfiguration.swift +7 -7
- package/Tests/SnapshotTests.swift +57 -27
- package/Tests/ValueProvidersTests.swift +9 -6
- package/lottie-ios.podspec +4 -4
- package/package.json +2 -2
- package/script/test-carthage/Cartfile +1 -1
- package/script/test-carthage/CarthageTest/ViewController.swift +1 -1
- package/script/test-carthage/CarthageTest-macOS/ViewController.swift +1 -1
- package/Sources/Private/CoreAnimation/Extensions/Keyframes+combinedIfPossible.swift +0 -154
- package/Sources/Public/Animation/AnimationViewInitializers.swift +0 -109
- package/Tests/Artifacts/LottieTests.xcresult/Data/data.0~4VqMqsI5lOfxRppnud6-VDWcNsU8J7VgFCJfW2dXPwOcAkvU-I8Um5yp9n0Zv6nr3VmcxYggaVMDFfR0U_vjKw== +0 -1
- package/Tests/Artifacts/LottieTests.xcresult/Data/data.0~7IO4FqH8-VUsSfuA0_wMGUgfGdV7MwzpkzjwWbiB50jXZKQRHtU4G0A7ZXTvsR4mDdrWiawpNC_eriOALfZ7Og== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/data.0~ClRW5-6rvrgZHHbrPS1VEREaxelpVcKlERPzSa3f2rtFNXdoqATxFCsPge3a_nO4mhhtpmTmbYvyfI-obAu8Qg== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/data.0~Jr2dFGcJc1188nMoMha82z9wM99lCLm4MKzhjfVVjIxKeCZVGWGZH3HgJFNkTpozdk5p1u5o91dCiY4-cR1Zhg== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/data.0~MBDeCFoahVzzmtSQrVhhy9VMJV6CmcnUkFya_qwe0XsMFscmY2nS8dxrTEYkY16sH68sEH7WVCCzTuW1z1Hocw== +0 -1
- package/Tests/Artifacts/LottieTests.xcresult/Data/data.0~U4plJajEDM_uSZdglhDyj6gQO63mbeKxYP9X94aIaOtL0yErtfQnGz5SgsqQ0ToSZcJj6Ao9Wx-nesZVwBTRuw== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/data.0~UsLY5O-vDMEHEe3bF8XJvkKkYxdsKlIytgJW7zgNuetw53fG3SssfZiiMfbuYnOtvvvYBXTwpbz07V6Czp029Q== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/data.0~cpg4cb7H0TaehHy4a-xINJMFfBM2_AEHal_0tIX8ymNVJPsjlCysi3-Cad0Ca_SvuGwVM7ONF55OBUuC9YKZvA== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/data.0~cuHjdTCuciVumvEpvozxwDj2wAdgWe13bzd1pUAGN45-WOgY0kIid9aUlBX675OnS-xNEc_pyQWo0RO1dKAjcw== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/data.0~f_-bfn-KRHHFg39_MtwgBulEAuWH6F05yqGYydXhil6kqZ51eAoRX6tsiqOr0Oa6eL3dK1tcCBD1bWX5orCZhw== +0 -1
- package/Tests/Artifacts/LottieTests.xcresult/Data/data.0~jdIx7vWS6ej1cqYcbCS4KyZErSMF13ELR95YwClVc98tIXcsglh7KuGvI1gIxEPDtPXQpfC3XijIAGn1quL8Dw== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/data.0~lz3e0YeBa8TvMBSSymToh--gc6zznUIdH2jO0AJ4so5OPNdw6wpYmJebhaENGsRoD3beUXvlyD5f7_WeZrzyNQ== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/data.0~u_5dWbliYJ__YkyUCDFjdxiFds9M8Epr_RvbU1rIRCbBQdhRJ_TUBXXcL_Qq-wVp4umNLTOzud4OpQItSpO6Rg== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/data.0~v-YGzhC2l_icsAsyp1XAbEWYwxNook-rARAWlVZINioEsgT9LGvhg2oh78nYqoeH78m1fihr5HCcGNfF7SQj0g== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/refs.0~4VqMqsI5lOfxRppnud6-VDWcNsU8J7VgFCJfW2dXPwOcAkvU-I8Um5yp9n0Zv6nr3VmcxYggaVMDFfR0U_vjKw== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/refs.0~7IO4FqH8-VUsSfuA0_wMGUgfGdV7MwzpkzjwWbiB50jXZKQRHtU4G0A7ZXTvsR4mDdrWiawpNC_eriOALfZ7Og== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/refs.0~ClRW5-6rvrgZHHbrPS1VEREaxelpVcKlERPzSa3f2rtFNXdoqATxFCsPge3a_nO4mhhtpmTmbYvyfI-obAu8Qg== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/refs.0~Jr2dFGcJc1188nMoMha82z9wM99lCLm4MKzhjfVVjIxKeCZVGWGZH3HgJFNkTpozdk5p1u5o91dCiY4-cR1Zhg== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/refs.0~MBDeCFoahVzzmtSQrVhhy9VMJV6CmcnUkFya_qwe0XsMFscmY2nS8dxrTEYkY16sH68sEH7WVCCzTuW1z1Hocw== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/refs.0~U4plJajEDM_uSZdglhDyj6gQO63mbeKxYP9X94aIaOtL0yErtfQnGz5SgsqQ0ToSZcJj6Ao9Wx-nesZVwBTRuw== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/refs.0~UsLY5O-vDMEHEe3bF8XJvkKkYxdsKlIytgJW7zgNuetw53fG3SssfZiiMfbuYnOtvvvYBXTwpbz07V6Czp029Q== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/refs.0~cpg4cb7H0TaehHy4a-xINJMFfBM2_AEHal_0tIX8ymNVJPsjlCysi3-Cad0Ca_SvuGwVM7ONF55OBUuC9YKZvA== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/refs.0~cuHjdTCuciVumvEpvozxwDj2wAdgWe13bzd1pUAGN45-WOgY0kIid9aUlBX675OnS-xNEc_pyQWo0RO1dKAjcw== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/refs.0~f_-bfn-KRHHFg39_MtwgBulEAuWH6F05yqGYydXhil6kqZ51eAoRX6tsiqOr0Oa6eL3dK1tcCBD1bWX5orCZhw== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/refs.0~jdIx7vWS6ej1cqYcbCS4KyZErSMF13ELR95YwClVc98tIXcsglh7KuGvI1gIxEPDtPXQpfC3XijIAGn1quL8Dw== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/refs.0~lz3e0YeBa8TvMBSSymToh--gc6zznUIdH2jO0AJ4so5OPNdw6wpYmJebhaENGsRoD3beUXvlyD5f7_WeZrzyNQ== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/refs.0~u_5dWbliYJ__YkyUCDFjdxiFds9M8Epr_RvbU1rIRCbBQdhRJ_TUBXXcL_Qq-wVp4umNLTOzud4OpQItSpO6Rg== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Data/refs.0~v-YGzhC2l_icsAsyp1XAbEWYwxNook-rARAWlVZINioEsgT9LGvhg2oh78nYqoeH78m1fihr5HCcGNfF7SQj0g== +0 -0
- package/Tests/Artifacts/LottieTests.xcresult/Info.plist +0 -29
|
@@ -8,8 +8,8 @@ import QuartzCore
|
|
|
8
8
|
|
|
9
9
|
/// A `ShapeItem` that represents a stroke
|
|
10
10
|
protocol StrokeShapeItem: OpacityAnimationModel {
|
|
11
|
-
var strokeColor: KeyframeGroup<
|
|
12
|
-
var width: KeyframeGroup<
|
|
11
|
+
var strokeColor: KeyframeGroup<LottieColor>? { get }
|
|
12
|
+
var width: KeyframeGroup<LottieVector1D> { get }
|
|
13
13
|
var lineCap: LineCap { get }
|
|
14
14
|
var lineJoin: LineJoin { get }
|
|
15
15
|
var miterLimit: Double { get }
|
|
@@ -19,13 +19,13 @@ protocol StrokeShapeItem: OpacityAnimationModel {
|
|
|
19
19
|
// MARK: - Stroke + StrokeShapeItem
|
|
20
20
|
|
|
21
21
|
extension Stroke: StrokeShapeItem {
|
|
22
|
-
var strokeColor: KeyframeGroup<
|
|
22
|
+
var strokeColor: KeyframeGroup<LottieColor>? { color }
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
// MARK: - GradientStroke + StrokeShapeItem
|
|
26
26
|
|
|
27
27
|
extension GradientStroke: StrokeShapeItem {
|
|
28
|
-
var strokeColor: KeyframeGroup<
|
|
28
|
+
var strokeColor: KeyframeGroup<LottieColor>? { nil }
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// MARK: - CAShapeLayer + StrokeShapeItem
|
|
@@ -10,39 +10,39 @@ import QuartzCore
|
|
|
10
10
|
/// both transform types to share the same animation implementation.
|
|
11
11
|
protocol TransformModel {
|
|
12
12
|
/// The anchor point of the transform.
|
|
13
|
-
var anchorPoint: KeyframeGroup<
|
|
13
|
+
var anchorPoint: KeyframeGroup<LottieVector3D> { get }
|
|
14
14
|
|
|
15
15
|
/// The position of the transform. This is nil if the position data was split.
|
|
16
|
-
var _position: KeyframeGroup<
|
|
16
|
+
var _position: KeyframeGroup<LottieVector3D>? { get }
|
|
17
17
|
|
|
18
18
|
/// The positionX of the transform. This is nil if the position property is set.
|
|
19
|
-
var _positionX: KeyframeGroup<
|
|
19
|
+
var _positionX: KeyframeGroup<LottieVector1D>? { get }
|
|
20
20
|
|
|
21
21
|
/// The positionY of the transform. This is nil if the position property is set.
|
|
22
|
-
var _positionY: KeyframeGroup<
|
|
22
|
+
var _positionY: KeyframeGroup<LottieVector1D>? { get }
|
|
23
23
|
|
|
24
24
|
/// The scale of the transform
|
|
25
|
-
var scale: KeyframeGroup<
|
|
25
|
+
var scale: KeyframeGroup<LottieVector3D> { get }
|
|
26
26
|
|
|
27
27
|
/// The rotation of the transform. Note: This is single dimensional rotation.
|
|
28
|
-
var rotation: KeyframeGroup<
|
|
28
|
+
var rotation: KeyframeGroup<LottieVector1D> { get }
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// MARK: - Transform + TransformModel
|
|
32
32
|
|
|
33
33
|
extension Transform: TransformModel {
|
|
34
|
-
var _position: KeyframeGroup<
|
|
35
|
-
var _positionX: KeyframeGroup<
|
|
36
|
-
var _positionY: KeyframeGroup<
|
|
34
|
+
var _position: KeyframeGroup<LottieVector3D>? { position }
|
|
35
|
+
var _positionX: KeyframeGroup<LottieVector1D>? { positionX }
|
|
36
|
+
var _positionY: KeyframeGroup<LottieVector1D>? { positionY }
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// MARK: - ShapeTransform + TransformModel
|
|
40
40
|
|
|
41
41
|
extension ShapeTransform: TransformModel {
|
|
42
|
-
var anchorPoint: KeyframeGroup<
|
|
43
|
-
var _position: KeyframeGroup<
|
|
44
|
-
var _positionX: KeyframeGroup<
|
|
45
|
-
var _positionY: KeyframeGroup<
|
|
42
|
+
var anchorPoint: KeyframeGroup<LottieVector3D> { anchor }
|
|
43
|
+
var _position: KeyframeGroup<LottieVector3D>? { position }
|
|
44
|
+
var _positionX: KeyframeGroup<LottieVector1D>? { nil }
|
|
45
|
+
var _positionY: KeyframeGroup<LottieVector1D>? { nil }
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// MARK: - CALayer + TransformModel
|
|
@@ -15,7 +15,7 @@ final class CoreAnimationLayer: BaseAnimationLayer {
|
|
|
15
15
|
/// - This initializer is throwing, but will only throw when using
|
|
16
16
|
/// `CompatibilityTracker.Mode.abort`.
|
|
17
17
|
init(
|
|
18
|
-
animation:
|
|
18
|
+
animation: LottieAnimation,
|
|
19
19
|
imageProvider: AnimationImageProvider,
|
|
20
20
|
textProvider: AnimationTextProvider,
|
|
21
21
|
fontProvider: AnimationFontProvider,
|
|
@@ -31,7 +31,7 @@ final class CoreAnimationLayer: BaseAnimationLayer {
|
|
|
31
31
|
compatibilityTracker = CompatibilityTracker(mode: compatibilityTrackerMode, logger: logger)
|
|
32
32
|
valueProviderStore = ValueProviderStore(logger: logger)
|
|
33
33
|
super.init()
|
|
34
|
-
|
|
34
|
+
masksToBounds = true
|
|
35
35
|
setup()
|
|
36
36
|
try setupChildLayers()
|
|
37
37
|
}
|
|
@@ -156,7 +156,7 @@ final class CoreAnimationLayer: BaseAnimationLayer {
|
|
|
156
156
|
} catch {
|
|
157
157
|
if case CompatibilityTracker.Error.encounteredCompatibilityIssue(let compatibilityIssue) = error {
|
|
158
158
|
// Even though the animation setup failed, we still update the layer's playback state
|
|
159
|
-
// so it can be read by the parent `
|
|
159
|
+
// so it can be read by the parent `LottieAnimationView` when handling this error
|
|
160
160
|
currentPlaybackState = pendingAnimationConfiguration.playbackState
|
|
161
161
|
|
|
162
162
|
didSetUpAnimation?([compatibilityIssue])
|
|
@@ -187,7 +187,7 @@ final class CoreAnimationLayer: BaseAnimationLayer {
|
|
|
187
187
|
/// which is also the realtime animation progress of this layer's animation
|
|
188
188
|
@objc private var animationProgress: CGFloat = 0
|
|
189
189
|
|
|
190
|
-
private let animation:
|
|
190
|
+
private let animation: LottieAnimation
|
|
191
191
|
private let valueProviderStore: ValueProviderStore
|
|
192
192
|
private let compatibilityTracker: CompatibilityTracker
|
|
193
193
|
private let logger: LottieLogger
|
|
@@ -6,17 +6,9 @@
|
|
|
6
6
|
extension KeyframeGroup {
|
|
7
7
|
/// Retrieves the first `Keyframe` from this group,
|
|
8
8
|
/// and asserts that there are not any extra keyframes that would be ignored
|
|
9
|
-
///
|
|
10
|
-
///
|
|
11
|
-
///
|
|
12
|
-
/// be applied to a single `CALayer` property (for example, the definition for a
|
|
13
|
-
/// `Rectangle` technically lets you animate `size`, `position`, and `cornerRadius`
|
|
14
|
-
/// separately, but these all have to be combined into a single `CAKeyframeAnimation`
|
|
15
|
-
/// on the `CAShapeLayer.path` property.
|
|
16
|
-
///
|
|
17
|
-
/// - In those sorts of cases, we currently choose one one `KeyframeGroup` to provide the
|
|
18
|
-
/// timing information, and disallow simultaneous animations on the other properties.
|
|
19
|
-
///
|
|
9
|
+
/// - This should only be used in cases where it's fundamentally not possible to
|
|
10
|
+
/// support animating a given property (e.g. if Core Animation itself doesn't
|
|
11
|
+
/// support the property).
|
|
20
12
|
func exactlyOneKeyframe(
|
|
21
13
|
context: CompatibilityTrackerProviding,
|
|
22
14
|
description: String,
|
|
@@ -29,7 +21,7 @@ extension KeyframeGroup {
|
|
|
29
21
|
keyframes.count == 1,
|
|
30
22
|
"""
|
|
31
23
|
The Core Animation rendering engine does not support animating multiple keyframes
|
|
32
|
-
for \(description) values
|
|
24
|
+
for \(description) values, due to limitations of Core Animation.
|
|
33
25
|
""")
|
|
34
26
|
|
|
35
27
|
return keyframes[0].value
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// Created by Cal Stephens on 1/28/22.
|
|
2
|
+
// Copyright © 2022 Airbnb Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
// MARK: - Keyframes
|
|
5
|
+
|
|
6
|
+
enum Keyframes {
|
|
7
|
+
|
|
8
|
+
// MARK: Internal
|
|
9
|
+
|
|
10
|
+
/// Combines the given keyframe groups of `Keyframe<T>`s into a single keyframe group of of `Keyframe<[T]>`s
|
|
11
|
+
/// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged
|
|
12
|
+
/// - Otherwise, the keyframes are manually interpolated at each frame in the animation
|
|
13
|
+
static func combined<T>(_ allGroups: [KeyframeGroup<T>]) -> KeyframeGroup<[T]>
|
|
14
|
+
where T: AnyInterpolatable
|
|
15
|
+
{
|
|
16
|
+
Keyframes.combined(allGroups, makeCombinedResult: { untypedValues in
|
|
17
|
+
untypedValues.compactMap { $0 as? T }
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Combines the given keyframe groups of `Keyframe<T>`s into a single keyframe group of of `Keyframe<[T]>`s
|
|
22
|
+
/// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged
|
|
23
|
+
/// - Otherwise, the keyframes are manually interpolated at each frame in the animation
|
|
24
|
+
static func combined<T1, T2, CombinedResult>(
|
|
25
|
+
_ k1: KeyframeGroup<T1>,
|
|
26
|
+
_ k2: KeyframeGroup<T2>,
|
|
27
|
+
makeCombinedResult: (T1, T2) -> CombinedResult)
|
|
28
|
+
-> KeyframeGroup<CombinedResult>
|
|
29
|
+
where T1: AnyInterpolatable, T2: AnyInterpolatable
|
|
30
|
+
{
|
|
31
|
+
Keyframes.combined(
|
|
32
|
+
[k1, k2],
|
|
33
|
+
makeCombinedResult: { untypedValues in
|
|
34
|
+
guard
|
|
35
|
+
let t1 = untypedValues[0] as? T1,
|
|
36
|
+
let t2 = untypedValues[1] as? T2
|
|
37
|
+
else { return nil }
|
|
38
|
+
|
|
39
|
+
return makeCombinedResult(t1, t2)
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Combines the given keyframe groups of `Keyframe<T>`s into a single keyframe group of of `Keyframe<[T]>`s
|
|
44
|
+
/// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged
|
|
45
|
+
/// - Otherwise, the keyframes are manually interpolated at each frame in the animation
|
|
46
|
+
static func combined<T1, T2, T3, CombinedResult>(
|
|
47
|
+
_ k1: KeyframeGroup<T1>,
|
|
48
|
+
_ k2: KeyframeGroup<T2>,
|
|
49
|
+
_ k3: KeyframeGroup<T3>,
|
|
50
|
+
makeCombinedResult: (T1, T2, T3) -> CombinedResult)
|
|
51
|
+
-> KeyframeGroup<CombinedResult>
|
|
52
|
+
where T1: AnyInterpolatable, T2: AnyInterpolatable, T3: AnyInterpolatable
|
|
53
|
+
{
|
|
54
|
+
Keyframes.combined(
|
|
55
|
+
[k1, k2, k3],
|
|
56
|
+
makeCombinedResult: { untypedValues in
|
|
57
|
+
guard
|
|
58
|
+
let t1 = untypedValues[0] as? T1,
|
|
59
|
+
let t2 = untypedValues[1] as? T2,
|
|
60
|
+
let t3 = untypedValues[2] as? T3
|
|
61
|
+
else { return nil }
|
|
62
|
+
|
|
63
|
+
return makeCombinedResult(t1, t2, t3)
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Combines the given keyframe groups of `Keyframe<T>`s into a single keyframe group of of `Keyframe<[T]>`s
|
|
68
|
+
/// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged
|
|
69
|
+
/// - Otherwise, the keyframes are manually interpolated at each frame in the animation
|
|
70
|
+
static func combined<T1, T2, T3, T4, T5, T6, T7, CombinedResult>(
|
|
71
|
+
_ k1: KeyframeGroup<T1>,
|
|
72
|
+
_ k2: KeyframeGroup<T2>,
|
|
73
|
+
_ k3: KeyframeGroup<T3>,
|
|
74
|
+
_ k4: KeyframeGroup<T4>,
|
|
75
|
+
_ k5: KeyframeGroup<T5>,
|
|
76
|
+
_ k6: KeyframeGroup<T6>,
|
|
77
|
+
_ k7: KeyframeGroup<T7>,
|
|
78
|
+
makeCombinedResult: (T1, T2, T3, T4, T5, T6, T7) -> CombinedResult)
|
|
79
|
+
-> KeyframeGroup<CombinedResult>
|
|
80
|
+
where T1: AnyInterpolatable, T2: AnyInterpolatable, T3: AnyInterpolatable, T4: AnyInterpolatable,
|
|
81
|
+
T5: AnyInterpolatable, T6: AnyInterpolatable, T7: AnyInterpolatable
|
|
82
|
+
{
|
|
83
|
+
Keyframes.combined(
|
|
84
|
+
[k1, k2, k3, k4, k5, k6, k7],
|
|
85
|
+
makeCombinedResult: { untypedValues in
|
|
86
|
+
guard
|
|
87
|
+
let t1 = untypedValues[0] as? T1,
|
|
88
|
+
let t2 = untypedValues[1] as? T2,
|
|
89
|
+
let t3 = untypedValues[2] as? T3,
|
|
90
|
+
let t4 = untypedValues[3] as? T4,
|
|
91
|
+
let t5 = untypedValues[4] as? T5,
|
|
92
|
+
let t6 = untypedValues[5] as? T6,
|
|
93
|
+
let t7 = untypedValues[6] as? T7
|
|
94
|
+
else { return nil }
|
|
95
|
+
|
|
96
|
+
return makeCombinedResult(t1, t2, t3, t4, t5, t6, t7)
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// MARK: Private
|
|
101
|
+
|
|
102
|
+
/// Combines the given `[KeyframeGroup]` of `Keyframe<T>`s into a single `KeyframeGroup` of `Keyframe<CombinedResult>`s
|
|
103
|
+
/// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged
|
|
104
|
+
/// - Otherwise, the keyframes are manually interpolated at each frame in the animation
|
|
105
|
+
///
|
|
106
|
+
/// `makeCombinedResult` is a closure that takes an array of keyframe values (with the exact same length as `AnyKeyframeGroup`),
|
|
107
|
+
/// casts them to the expected type, and combined them into the final resulting keyframe.
|
|
108
|
+
private static func combined<CombinedResult>(
|
|
109
|
+
_ allGroups: [AnyKeyframeGroup],
|
|
110
|
+
makeCombinedResult: ([Any]) -> CombinedResult?)
|
|
111
|
+
-> KeyframeGroup<CombinedResult>
|
|
112
|
+
{
|
|
113
|
+
let untypedGroups = allGroups.map { $0.untyped }
|
|
114
|
+
|
|
115
|
+
// Animations with no timing information (e.g. with just a single keyframe)
|
|
116
|
+
// can be trivially combined with any other set of keyframes, so we don't need
|
|
117
|
+
// to check those.
|
|
118
|
+
let animatingKeyframes = untypedGroups.filter { $0.keyframes.count > 1 }
|
|
119
|
+
|
|
120
|
+
guard
|
|
121
|
+
!allGroups.isEmpty,
|
|
122
|
+
animatingKeyframes.allSatisfy({ $0.hasSameTimingParameters(as: animatingKeyframes[0]) })
|
|
123
|
+
else {
|
|
124
|
+
// If the keyframes don't all share the same timing information,
|
|
125
|
+
// we have to interpolate the value at each individual frame
|
|
126
|
+
return Keyframes.manuallyInterpolated(allGroups, makeCombinedResult: makeCombinedResult)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
var combinedKeyframes = ContiguousArray<Keyframe<CombinedResult>>()
|
|
130
|
+
let baseKeyframes = (animatingKeyframes.first ?? untypedGroups[0]).keyframes
|
|
131
|
+
|
|
132
|
+
for index in baseKeyframes.indices {
|
|
133
|
+
let baseKeyframe = baseKeyframes[index]
|
|
134
|
+
let untypedValues = untypedGroups.map { $0.valueForCombinedKeyframes(at: index) }
|
|
135
|
+
|
|
136
|
+
if let combinedValue = makeCombinedResult(untypedValues) {
|
|
137
|
+
combinedKeyframes.append(baseKeyframe.withValue(combinedValue))
|
|
138
|
+
} else {
|
|
139
|
+
LottieLogger.shared.assertionFailure("""
|
|
140
|
+
Failed to cast untyped keyframe values to expected type. This is an internal error.
|
|
141
|
+
""")
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return KeyframeGroup(keyframes: combinedKeyframes)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private static func manuallyInterpolated<CombinedResult>(
|
|
149
|
+
_ allGroups: [AnyKeyframeGroup],
|
|
150
|
+
makeCombinedResult: ([Any]) -> CombinedResult?)
|
|
151
|
+
-> KeyframeGroup<CombinedResult>
|
|
152
|
+
{
|
|
153
|
+
let untypedGroups = allGroups.map { $0.untyped }
|
|
154
|
+
let untypedInterpolators = allGroups.map { $0.interpolator }
|
|
155
|
+
|
|
156
|
+
let times = untypedGroups.flatMap { $0.keyframes.map { $0.time } }
|
|
157
|
+
|
|
158
|
+
let minimumTime = times.min() ?? 0
|
|
159
|
+
let maximumTime = times.max() ?? 0
|
|
160
|
+
let animationLocalTimeRange = Int(minimumTime)...Int(maximumTime)
|
|
161
|
+
|
|
162
|
+
let interpolatedKeyframes = animationLocalTimeRange.compactMap { localTime -> Keyframe<CombinedResult>? in
|
|
163
|
+
let interpolatedValues = untypedInterpolators.map { interpolator in
|
|
164
|
+
interpolator.value(frame: AnimationFrameTime(localTime))
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
guard let combinedResult = makeCombinedResult(interpolatedValues) else {
|
|
168
|
+
LottieLogger.shared.assertionFailure("""
|
|
169
|
+
Failed to cast untyped keyframe values to expected type. This is an internal error.
|
|
170
|
+
""")
|
|
171
|
+
return nil
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return Keyframe(
|
|
175
|
+
value: combinedResult,
|
|
176
|
+
time: AnimationFrameTime(localTime))
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return KeyframeGroup(keyframes: ContiguousArray(interpolatedKeyframes))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
extension KeyframeGroup {
|
|
185
|
+
/// Whether or not all of the keyframes in this `KeyframeGroup` have the same
|
|
186
|
+
/// timing parameters as the corresponding keyframe in the other given `KeyframeGroup`
|
|
187
|
+
func hasSameTimingParameters<T>(as other: KeyframeGroup<T>) -> Bool {
|
|
188
|
+
guard keyframes.count == other.keyframes.count else {
|
|
189
|
+
return false
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return zip(keyframes, other.keyframes).allSatisfy {
|
|
193
|
+
$0.hasSameTimingParameters(as: $1)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
extension Keyframe {
|
|
199
|
+
/// Whether or not this keyframe has the same timing parameters as the given keyframe,
|
|
200
|
+
/// excluding `spatialInTangent` and `spatialOutTangent`.
|
|
201
|
+
fileprivate func hasSameTimingParameters<T>(as other: Keyframe<T>) -> Bool {
|
|
202
|
+
time == other.time
|
|
203
|
+
&& isHold == other.isHold
|
|
204
|
+
&& inTangent == other.inTangent
|
|
205
|
+
&& outTangent == other.outTangent
|
|
206
|
+
// We intentionally don't compare spatial in/out tangents,
|
|
207
|
+
// since those values are only used in very specific cases
|
|
208
|
+
// (animating the x/y position of a layer), which aren't ever
|
|
209
|
+
// combined in this way.
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
extension KeyframeGroup {
|
|
214
|
+
/// The value to use for a combined set of keyframes, for the given index
|
|
215
|
+
fileprivate func valueForCombinedKeyframes(at index: Int) -> T {
|
|
216
|
+
if keyframes.count == 1 {
|
|
217
|
+
return keyframes[0].value
|
|
218
|
+
} else {
|
|
219
|
+
return keyframes[index].value
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -18,7 +18,7 @@ protocol AnimationLayer: CALayer {
|
|
|
18
18
|
// Context describing the timing parameters of the current animation
|
|
19
19
|
struct LayerAnimationContext {
|
|
20
20
|
/// The animation being played
|
|
21
|
-
let animation:
|
|
21
|
+
let animation: LottieAnimation
|
|
22
22
|
|
|
23
23
|
/// The timing configuration that should be applied to `CAAnimation`s
|
|
24
24
|
let timingConfiguration: CoreAnimationLayer.CAMediaTimingConfiguration
|
|
@@ -15,7 +15,7 @@ extension CALayer {
|
|
|
15
15
|
context: LayerContext)
|
|
16
16
|
throws
|
|
17
17
|
{
|
|
18
|
-
//
|
|
18
|
+
// A `LottieAnimation`'s `LayerModel`s are listed from front to back,
|
|
19
19
|
// but `CALayer.sublayers` are listed from back to front.
|
|
20
20
|
// We reverse the layer ordering to match what Core Animation expects.
|
|
21
21
|
// The final view hierarchy must display the layers in this exact order.
|
|
@@ -51,7 +51,7 @@ extension CALayer {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// Create an `AnimationLayer` for each `LayerModel`
|
|
54
|
-
for (layerModel, mask) in try layersInZAxisOrder.pairedLayersAndMasks(
|
|
54
|
+
for (layerModel, mask) in try layersInZAxisOrder.pairedLayersAndMasks() {
|
|
55
55
|
guard let layer = try layerModel.makeAnimationLayer(context: context) else {
|
|
56
56
|
continue
|
|
57
57
|
}
|
|
@@ -106,8 +106,8 @@ extension CALayer {
|
|
|
106
106
|
fileprivate func maskLayer(
|
|
107
107
|
for matteLayerModel: LayerModel,
|
|
108
108
|
type: MatteType,
|
|
109
|
-
context: LayerContext)
|
|
110
|
-
-> CALayer?
|
|
109
|
+
context: LayerContext)
|
|
110
|
+
throws -> CALayer?
|
|
111
111
|
{
|
|
112
112
|
switch type {
|
|
113
113
|
case .add:
|
|
@@ -123,7 +123,9 @@ extension CALayer {
|
|
|
123
123
|
// layer being masked, this creates an inverted mask where only areas _outside_
|
|
124
124
|
// of the mask layer are visible.
|
|
125
125
|
// https://developer.apple.com/documentation/coregraphics/cgblendmode/xor
|
|
126
|
-
|
|
126
|
+
// - The inverted mask is supposed to expand infinitely around the shape,
|
|
127
|
+
// so we use `InfiniteOpaqueAnimationLayer`
|
|
128
|
+
let base = InfiniteOpaqueAnimationLayer()
|
|
127
129
|
base.backgroundColor = .rgb(0, 0, 0)
|
|
128
130
|
base.addSublayer(maskLayer)
|
|
129
131
|
maskLayer.compositingFilter = "xor"
|
|
@@ -141,7 +143,7 @@ extension Collection where Element == LayerModel {
|
|
|
141
143
|
/// a `LayerModel` to use as its mask, if applicable
|
|
142
144
|
/// based on the layer's `MatteType` configuration.
|
|
143
145
|
/// - Assumes the layers are sorted in z-axis order.
|
|
144
|
-
fileprivate func pairedLayersAndMasks(
|
|
146
|
+
fileprivate func pairedLayersAndMasks() throws
|
|
145
147
|
-> [(layer: LayerModel, mask: (model: LayerModel, matteType: MatteType)?)]
|
|
146
148
|
{
|
|
147
149
|
var layersAndMasks = [(layer: LayerModel, mask: (model: LayerModel, matteType: MatteType)?)]()
|
|
@@ -45,8 +45,8 @@ final class GradientRenderLayer: CAGradientLayer {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
let pointInBounds = CGPoint(
|
|
48
|
-
x: referencePoint.x +
|
|
49
|
-
y: referencePoint.y +
|
|
48
|
+
x: referencePoint.x + CALayer.veryLargeLayerPadding,
|
|
49
|
+
y: referencePoint.y + CALayer.veryLargeLayerPadding)
|
|
50
50
|
|
|
51
51
|
return CGPoint(
|
|
52
52
|
x: CGFloat(pointInBounds.x) / bounds.width,
|
|
@@ -55,20 +55,14 @@ final class GradientRenderLayer: CAGradientLayer {
|
|
|
55
55
|
|
|
56
56
|
// MARK: Private
|
|
57
57
|
|
|
58
|
-
/// Extra padding around the `gradientReferenceBounds` where the gradient is also rendered
|
|
59
|
-
/// - This specific value is arbitrary and can be increased if necessary.
|
|
60
|
-
/// Theoretically this should be "infinite", to match the behavior of
|
|
61
|
-
/// `CGContext.drawLinearGradient` with `[.drawsAfterEndLocation, .drawsBeforeStartLocation]`.
|
|
62
|
-
private let gradientPadding: CGFloat = 10_000
|
|
63
|
-
|
|
64
58
|
private func updateLayout() {
|
|
65
59
|
anchorPoint = .zero
|
|
66
60
|
|
|
67
61
|
bounds = CGRect(
|
|
68
62
|
x: gradientReferenceBounds.origin.x,
|
|
69
63
|
y: gradientReferenceBounds.origin.y,
|
|
70
|
-
width:
|
|
71
|
-
height:
|
|
64
|
+
width: CALayer.veryLargeLayerPadding + gradientReferenceBounds.width + CALayer.veryLargeLayerPadding,
|
|
65
|
+
height: CALayer.veryLargeLayerPadding + gradientReferenceBounds.height + CALayer.veryLargeLayerPadding)
|
|
72
66
|
|
|
73
67
|
// Align the center of this layer to be at the center point of its parent layer
|
|
74
68
|
let superlayerSize = superlayer?.frame.size ?? gradientReferenceBounds.size
|
|
@@ -92,3 +86,12 @@ extension GradientRenderLayer: CustomLayoutLayer {
|
|
|
92
86
|
}
|
|
93
87
|
}
|
|
94
88
|
}
|
|
89
|
+
|
|
90
|
+
extension CALayer {
|
|
91
|
+
/// Extra padding to add around layers that should be very large or "infinite" in size.
|
|
92
|
+
/// Examples include `GradientRenderLayer` and `InfiniteOpaqueAnimationLayer`.
|
|
93
|
+
/// - This specific value is arbitrary and can be increased if necessary.
|
|
94
|
+
/// - Theoretically this should be "infinite", to match the behavior of
|
|
95
|
+
/// `CGContext.drawLinearGradient` with `[.drawsAfterEndLocation, .drawsBeforeStartLocation]` etc.
|
|
96
|
+
static let veryLargeLayerPadding: CGFloat = 10_000
|
|
97
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Created by Cal Stephens on 10/10/22.
|
|
2
|
+
// Copyright © 2022 Airbnb Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
import QuartzCore
|
|
5
|
+
|
|
6
|
+
// MARK: - ExpandedAnimationLayer
|
|
7
|
+
|
|
8
|
+
/// A `BaseAnimationLayer` subclass that renders its background color
|
|
9
|
+
/// as if the layer is infinitely large, without affecting its bounds
|
|
10
|
+
/// or the bounds of its sublayers
|
|
11
|
+
final class InfiniteOpaqueAnimationLayer: BaseAnimationLayer {
|
|
12
|
+
|
|
13
|
+
// MARK: Lifecycle
|
|
14
|
+
|
|
15
|
+
override init() {
|
|
16
|
+
super.init()
|
|
17
|
+
addSublayer(additionalPaddingLayer)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
required init?(coder _: NSCoder) {
|
|
21
|
+
fatalError("init(coder:) has not been implemented")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// Called by CoreAnimation to create a shadow copy of this layer
|
|
25
|
+
/// More details: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init
|
|
26
|
+
override init(layer: Any) {
|
|
27
|
+
super.init(layer: layer)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// MARK: Internal
|
|
31
|
+
|
|
32
|
+
override func layoutSublayers() {
|
|
33
|
+
super.layoutSublayers()
|
|
34
|
+
|
|
35
|
+
masksToBounds = false
|
|
36
|
+
additionalPaddingLayer.backgroundColor = backgroundColor
|
|
37
|
+
|
|
38
|
+
// Scale `additionalPaddingLayer` to be larger than this layer
|
|
39
|
+
// by `additionalPadding` at each size, and centered at the center
|
|
40
|
+
// of this layer. Since `additionalPadding` is very large, this has
|
|
41
|
+
// the affect of making `additionalPaddingLayer` appear infinite.
|
|
42
|
+
let scaleRatioX = (bounds.width + (CALayer.veryLargeLayerPadding * 2)) / bounds.width
|
|
43
|
+
let scaleRatioY = (bounds.height + (CALayer.veryLargeLayerPadding * 2)) / bounds.height
|
|
44
|
+
|
|
45
|
+
additionalPaddingLayer.transform = CATransform3DScale(
|
|
46
|
+
CATransform3DMakeTranslation(-CALayer.veryLargeLayerPadding, -CALayer.veryLargeLayerPadding, 0),
|
|
47
|
+
scaleRatioX,
|
|
48
|
+
scaleRatioY,
|
|
49
|
+
1)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// MARK: Private
|
|
53
|
+
|
|
54
|
+
private let additionalPaddingLayer = CALayer()
|
|
55
|
+
|
|
56
|
+
}
|
|
@@ -7,7 +7,7 @@ import QuartzCore
|
|
|
7
7
|
|
|
8
8
|
/// Context available when constructing an `AnimationLayer`
|
|
9
9
|
struct LayerContext {
|
|
10
|
-
let animation:
|
|
10
|
+
let animation: LottieAnimation
|
|
11
11
|
let imageProvider: AnimationImageProvider
|
|
12
12
|
let textProvider: AnimationTextProvider
|
|
13
13
|
let fontProvider: AnimationFontProvider
|
|
@@ -108,10 +108,9 @@ extension PreCompLayer: CustomLayoutLayer {
|
|
|
108
108
|
extension KeyframeInterpolator where ValueType == AnimationFrameTime {
|
|
109
109
|
/// A `KeyframeInterpolator` for the given `timeRemapping` keyframes
|
|
110
110
|
static func timeRemapping(
|
|
111
|
-
keyframes timeRemappingKeyframes: KeyframeGroup<
|
|
111
|
+
keyframes timeRemappingKeyframes: KeyframeGroup<LottieVector1D>,
|
|
112
112
|
context: LayerContext)
|
|
113
|
-
throws
|
|
114
|
-
-> KeyframeInterpolator<AnimationFrameTime>
|
|
113
|
+
throws -> KeyframeInterpolator<AnimationFrameTime>
|
|
115
114
|
{
|
|
116
115
|
try context.logCompatibilityIssue("""
|
|
117
116
|
The Core Animation rendering engine partially supports time remapping keyframes,
|
|
@@ -56,11 +56,11 @@ private struct RepeaterTransform {
|
|
|
56
56
|
scale = repeater.scale
|
|
57
57
|
|
|
58
58
|
rotation = repeater.rotation.map { rotation in
|
|
59
|
-
|
|
59
|
+
LottieVector1D(rotation.value * Double(index))
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
position = repeater.position.map { position in
|
|
63
|
-
|
|
63
|
+
LottieVector3D(
|
|
64
64
|
x: position.x * Double(index),
|
|
65
65
|
y: position.y * Double(index),
|
|
66
66
|
z: position.z * Double(index))
|
|
@@ -69,17 +69,17 @@ private struct RepeaterTransform {
|
|
|
69
69
|
|
|
70
70
|
// MARK: Internal
|
|
71
71
|
|
|
72
|
-
let anchorPoint: KeyframeGroup<
|
|
73
|
-
let position: KeyframeGroup<
|
|
74
|
-
let rotation: KeyframeGroup<
|
|
75
|
-
let scale: KeyframeGroup<
|
|
72
|
+
let anchorPoint: KeyframeGroup<LottieVector3D>
|
|
73
|
+
let position: KeyframeGroup<LottieVector3D>
|
|
74
|
+
let rotation: KeyframeGroup<LottieVector1D>
|
|
75
|
+
let scale: KeyframeGroup<LottieVector3D>
|
|
76
76
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
// MARK: TransformModel
|
|
80
80
|
|
|
81
81
|
extension RepeaterTransform: TransformModel {
|
|
82
|
-
var _position: KeyframeGroup<
|
|
83
|
-
var _positionX: KeyframeGroup<
|
|
84
|
-
var _positionY: KeyframeGroup<
|
|
82
|
+
var _position: KeyframeGroup<LottieVector3D>? { position }
|
|
83
|
+
var _positionX: KeyframeGroup<LottieVector1D>? { nil }
|
|
84
|
+
var _positionY: KeyframeGroup<LottieVector1D>? { nil }
|
|
85
85
|
}
|
|
@@ -217,7 +217,11 @@ final class ShapeItemLayer: BaseAnimationLayer {
|
|
|
217
217
|
""")
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
try shapeLayer.addAnimations(
|
|
220
|
+
try shapeLayer.addAnimations(
|
|
221
|
+
for: shape.item,
|
|
222
|
+
context: context.for(shape),
|
|
223
|
+
pathMultiplier: trimPathMultiplier ?? 1,
|
|
224
|
+
roundedCorners: otherItems.first(RoundedCorners.self))
|
|
221
225
|
|
|
222
226
|
if let (fill, context) = otherItems.first(Fill.self, context: context) {
|
|
223
227
|
try shapeLayer.addAnimations(for: fill, context: context)
|
|
@@ -236,7 +240,8 @@ final class ShapeItemLayer: BaseAnimationLayer {
|
|
|
236
240
|
try layers.shapeMaskLayer.addAnimations(
|
|
237
241
|
for: shape.item,
|
|
238
242
|
context: context.for(shape),
|
|
239
|
-
pathMultiplier: 1
|
|
243
|
+
pathMultiplier: 1,
|
|
244
|
+
roundedCorners: otherItems.first(RoundedCorners.self))
|
|
240
245
|
|
|
241
246
|
if let (gradientFill, context) = otherItems.first(GradientFill.self, context: context) {
|
|
242
247
|
layers.shapeMaskLayer.fillRule = gradientFill.fillRule.caFillRule
|
|
@@ -258,7 +263,8 @@ final class ShapeItemLayer: BaseAnimationLayer {
|
|
|
258
263
|
try layers.shapeMaskLayer.addAnimations(
|
|
259
264
|
for: shape.item,
|
|
260
265
|
context: context.for(shape),
|
|
261
|
-
pathMultiplier: trimPathMultiplier ?? 1
|
|
266
|
+
pathMultiplier: trimPathMultiplier ?? 1,
|
|
267
|
+
roundedCorners: otherItems.first(RoundedCorners.self))
|
|
262
268
|
|
|
263
269
|
if let (gradientStroke, context) = otherItems.first(GradientStroke.self, context: context) {
|
|
264
270
|
try layers.gradientColorLayer.addGradientAnimations(for: gradientStroke, type: .rgb, context: context)
|