lottie-ios 3.2.2 → 3.4.1
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/actions/setup/action.yml +32 -0
- package/.github/issue_template.md +6 -23
- package/.github/workflows/main.yml +98 -0
- package/.spi.yml +6 -0
- package/.swift-version +1 -0
- package/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +7 -0
- package/.swiftpm/xcode/package.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/.swiftpm/xcode/xcuserdata/{brandonwithrow.xcuserdatad → cal.xcuserdatad}/xcschemes/xcschememanagement.plist +25 -15
- package/.swiftpm/xcode/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
- package/Gemfile +4 -0
- package/Gemfile.lock +102 -0
- package/Lottie.xcodeproj/project.pbxproj +2003 -1972
- package/Lottie.xcodeproj/project.xcworkspace/contents.xcworkspacedata +1 -1
- package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_iOS.xcscheme → Lottie (iOS).xcscheme } +15 -18
- package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_macOS.xcscheme → Lottie (macOS).xcscheme } +7 -20
- package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_tvOS.xcscheme → Lottie (tvOS).xcscheme } +5 -18
- package/Lottie.xcodeproj/xcuserdata/cal.xcuserdatad/xcschemes/xcschememanagement.plist +37 -0
- package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +24 -0
- package/Lottie.xcworkspace/contents.xcworkspacedata +10 -0
- package/Lottie.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/Lottie.xcworkspace/xcshareddata/swiftpm/Package.resolved +115 -0
- package/Lottie.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/{Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad → Lottie.xcworkspace/xcuserdata/cal.xcuserdatad}/xcdebugger/Breakpoints_v2.xcbkptlist +2 -2
- package/Lottie.xcworkspace/xcuserdata/cal.xcuserdatad/xcdebugger/Expressions.xcexplist +153 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +39 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +25 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +30 -0
- package/Package.resolved +88 -0
- package/Package.swift +10 -15
- package/README.md +40 -60
- package/Rakefile +121 -0
- package/Sources/Private/CoreAnimation/Animations/CAAnimation+TimingConfiguration.swift +81 -0
- package/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift +448 -0
- package/Sources/Private/CoreAnimation/Animations/CombinedShapeAnimation.swift +53 -0
- package/Sources/Private/CoreAnimation/Animations/CustomPathAnimation.swift +41 -0
- package/Sources/Private/CoreAnimation/Animations/EllipseAnimation.swift +28 -0
- package/Sources/Private/CoreAnimation/Animations/GradientAnimations.swift +210 -0
- package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +229 -0
- package/Sources/Private/CoreAnimation/Animations/OpacityAnimation.swift +52 -0
- package/Sources/Private/CoreAnimation/Animations/RectangleAnimation.swift +31 -0
- package/Sources/Private/CoreAnimation/Animations/ShapeAnimation.swift +246 -0
- package/Sources/Private/CoreAnimation/Animations/StarAnimation.swift +95 -0
- package/Sources/Private/CoreAnimation/Animations/StrokeAnimation.swift +70 -0
- package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +205 -0
- package/Sources/Private/CoreAnimation/Animations/VisibilityAnimation.swift +37 -0
- package/Sources/Private/CoreAnimation/CompatibilityTracker.swift +130 -0
- package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +492 -0
- package/Sources/Private/CoreAnimation/Extensions/CALayer+fillBounds.swift +35 -0
- package/Sources/Private/CoreAnimation/Extensions/KeyframeGroup+exactlyOneKeyframe.swift +37 -0
- package/Sources/Private/CoreAnimation/Extensions/Keyframes+combinedIfPossible.swift +73 -0
- package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +83 -0
- package/Sources/Private/CoreAnimation/Layers/BaseAnimationLayer.swift +33 -0
- package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +87 -0
- package/Sources/Private/CoreAnimation/Layers/CALayer+setupLayerHierarchy.swift +131 -0
- package/Sources/Private/CoreAnimation/Layers/GradientRenderLayer.swift +94 -0
- package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +79 -0
- package/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift +60 -0
- package/Sources/Private/CoreAnimation/Layers/MaskCompositionLayer.swift +138 -0
- package/Sources/Private/CoreAnimation/Layers/PreCompLayer.swift +140 -0
- package/Sources/Private/CoreAnimation/Layers/ShapeItemLayer.swift +302 -0
- package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +319 -0
- package/Sources/Private/CoreAnimation/Layers/SolidLayer.swift +47 -0
- package/Sources/Private/CoreAnimation/Layers/TextLayer.swift +91 -0
- package/Sources/Private/CoreAnimation/Layers/TransformLayer.swift +11 -0
- package/Sources/Private/CoreAnimation/ValueProviderStore.swift +139 -0
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/CompositionLayer.swift +93 -85
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/ImageCompositionLayer.swift +24 -21
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/MaskContainerLayer.swift +75 -54
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/NullCompositionLayer.swift +5 -5
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/PreCompositionLayer.swift +61 -44
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/ShapeCompositionLayer.swift +22 -20
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/SolidCompositionLayer.swift +26 -17
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/TextCompositionLayer.swift +42 -36
- package/{lottie-swift/src/Private/LayerContainers/AnimationContainer.swift → Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift} +200 -140
- package/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift +47 -0
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/CompositionLayersInitializer.swift +33 -24
- package/{lottie-swift/src/Private/LayerContainers/Utility/TextLayer.swift → Sources/Private/MainThread/LayerContainers/Utility/CoreTextRenderLayer.swift} +120 -106
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/InvertedMatteLayer.swift +25 -24
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift +41 -0
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/LayerImageProvider.swift +20 -16
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift +40 -0
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/LayerTransformNode.swift +82 -67
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Extensions/ItemsExtension.swift +4 -4
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/NodeProperty.swift +29 -21
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift +16 -10
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift +6 -6
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift +5 -5
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +10 -8
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueContainer.swift +25 -21
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift +23 -17
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift +125 -108
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift +22 -17
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +110 -79
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift +22 -16
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift +19 -15
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift +22 -20
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift +22 -23
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +242 -0
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift +30 -24
- package/{lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift → Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift} +93 -66
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift +23 -19
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +139 -0
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +170 -0
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/PathNodes/RectNode.swift +95 -58
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift +42 -29
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/PathNodes/StarNode.swift +108 -69
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +155 -0
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift +51 -37
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +54 -42
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +65 -57
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +84 -58
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +138 -119
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Protocols/AnimatorNode.swift +80 -79
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Protocols/PathNode.swift +5 -3
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Protocols/RenderNode.swift +22 -17
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +27 -24
- package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift +34 -25
- package/Sources/Private/Model/Animation.swift +160 -0
- package/Sources/Private/Model/Assets/Asset.swift +43 -0
- package/{lottie-swift/src → Sources}/Private/Model/Assets/AssetLibrary.swift +40 -13
- package/Sources/Private/Model/Assets/ImageAsset.swift +112 -0
- package/{lottie-swift/src → Sources}/Private/Model/Assets/PrecompAsset.swift +20 -10
- package/Sources/Private/Model/DictionaryInitializable.swift +67 -0
- package/Sources/Private/Model/Extensions/Bundle.swift +34 -0
- package/{lottie-swift/src → Sources}/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +8 -4
- package/Sources/Private/Model/Keyframes/KeyframeData.swift +113 -0
- package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +197 -0
- package/{lottie-swift/src → Sources}/Private/Model/Layers/ImageLayerModel.swift +21 -11
- package/Sources/Private/Model/Layers/LayerModel.swift +228 -0
- package/{lottie-swift/src → Sources}/Private/Model/Layers/PreCompLayerModel.swift +39 -22
- package/{lottie-swift/src → Sources}/Private/Model/Layers/ShapeLayerModel.swift +23 -12
- package/{lottie-swift/src → Sources}/Private/Model/Layers/SolidLayerModel.swift +31 -19
- package/{lottie-swift/src → Sources}/Private/Model/Layers/TextLayerModel.swift +33 -19
- package/Sources/Private/Model/Objects/DashPattern.swift +44 -0
- package/Sources/Private/Model/Objects/Marker.swift +33 -0
- package/Sources/Private/Model/Objects/Mask.swift +80 -0
- package/Sources/Private/Model/Objects/Transform.swift +179 -0
- package/Sources/Private/Model/ShapeItems/Ellipse.swift +75 -0
- package/Sources/Private/Model/ShapeItems/Fill.swift +74 -0
- package/Sources/Private/Model/ShapeItems/GradientFill.swift +124 -0
- package/Sources/Private/Model/ShapeItems/GradientStroke.swift +186 -0
- package/Sources/Private/Model/ShapeItems/Group.swift +48 -0
- package/{lottie-swift/src → Sources}/Private/Model/ShapeItems/Merge.swift +29 -11
- package/Sources/Private/Model/ShapeItems/Rectangle.swift +73 -0
- package/Sources/Private/Model/ShapeItems/Repeater.swift +136 -0
- package/Sources/Private/Model/ShapeItems/Shape.swift +56 -0
- package/Sources/Private/Model/ShapeItems/ShapeItem.swift +163 -0
- package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +136 -0
- package/Sources/Private/Model/ShapeItems/Star.swift +132 -0
- package/Sources/Private/Model/ShapeItems/Stroke.swift +102 -0
- package/Sources/Private/Model/ShapeItems/Trim.swift +78 -0
- package/Sources/Private/Model/Text/Font.swift +61 -0
- package/{lottie-swift/src → Sources}/Private/Model/Text/Glyph.swift +63 -39
- package/Sources/Private/Model/Text/TextAnimator.swift +164 -0
- package/Sources/Private/Model/Text/TextDocument.swift +123 -0
- package/Sources/Private/RootAnimationLayer.swift +51 -0
- package/{lottie-swift/src → Sources}/Private/Utility/Debugging/AnimatorNodeDebugging.swift +7 -7
- package/{lottie-swift/src → Sources}/Private/Utility/Debugging/LayerDebugging.swift +72 -48
- package/Sources/Private/Utility/Debugging/TestHelpers.swift +10 -0
- package/{lottie-swift/src → Sources}/Private/Utility/Extensions/AnimationKeypathExtension.swift +69 -59
- package/Sources/Private/Utility/Extensions/BlendMode+Filter.swift +31 -0
- package/Sources/Private/Utility/Extensions/CGColor+RGB.swift +22 -0
- package/{lottie-swift/src → Sources}/Private/Utility/Extensions/CGFloatExtensions.swift +70 -67
- package/Sources/Private/Utility/Extensions/DataExtension.swift +27 -0
- package/Sources/Private/Utility/Extensions/MathKit.swift +450 -0
- package/Sources/Private/Utility/Extensions/StringExtensions.swift +38 -0
- package/{lottie-swift/src → Sources}/Private/Utility/Helpers/AnimationContext.swift +42 -16
- package/Sources/Private/Utility/Interpolatable/InterpolatableExtensions.swift +134 -0
- package/{lottie-swift/src → Sources}/Private/Utility/Interpolatable/KeyframeExtensions.swift +13 -10
- package/Sources/Private/Utility/Interpolatable/KeyframeGroup+Extensions.swift +59 -0
- package/{lottie-swift/src → Sources}/Private/Utility/Primitives/BezierPath.swift +229 -143
- package/Sources/Private/Utility/Primitives/CGPointExtension.swift +35 -0
- package/{lottie-swift/src → Sources}/Private/Utility/Primitives/ColorExtension.swift +46 -14
- package/{lottie-swift/src → Sources}/Private/Utility/Primitives/CompoundBezierPath.swift +58 -49
- package/Sources/Private/Utility/Primitives/CurveVertex.swift +184 -0
- package/Sources/Private/Utility/Primitives/PathElement.swift +75 -0
- package/Sources/Private/Utility/Primitives/UnitBezier.swift +115 -0
- package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +341 -0
- package/Sources/Public/Animation/AnimationPublic.swift +266 -0
- package/Sources/Public/Animation/AnimationView.swift +1335 -0
- package/Sources/Public/Animation/AnimationViewInitializers.swift +109 -0
- package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +22 -0
- package/{lottie-swift/src → Sources}/Public/AnimationCache/LRUAnimationCache.swift +22 -18
- package/Sources/Public/DynamicProperties/AnimationKeypath.swift +49 -0
- package/Sources/Public/DynamicProperties/AnyValueProvider.swift +132 -0
- package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +54 -36
- package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +40 -36
- package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +123 -0
- package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +40 -35
- package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +40 -35
- package/{lottie-swift/src → Sources}/Public/FontProvider/AnimationFontProvider.swift +15 -8
- package/Sources/Public/ImageProvider/AnimationImageProvider.swift +21 -0
- package/Sources/Public/Keyframes/Interpolatable.swift +253 -0
- package/Sources/Public/Keyframes/Keyframe.swift +92 -0
- package/Sources/Public/Logging/LottieLogger.swift +137 -0
- package/Sources/Public/LottieConfiguration.swift +143 -0
- package/{lottie-swift/src → Sources}/Public/Primitives/AnimationTime.swift +1 -1
- package/{lottie-swift/src → Sources}/Public/Primitives/Color.swift +10 -6
- package/{lottie-swift/src → Sources}/Public/Primitives/Vectors.swift +13 -12
- package/Sources/Public/TextProvider/AnimationTextProvider.swift +53 -0
- package/{lottie-swift/src → Sources}/Public/iOS/AnimatedButton.swift +42 -26
- package/{lottie-swift/src → Sources}/Public/iOS/AnimatedControl.swift +100 -91
- package/{lottie-swift/src → Sources}/Public/iOS/AnimatedSwitch.swift +128 -94
- package/{lottie-swift/src → Sources}/Public/iOS/AnimationSubview.swift +3 -3
- package/Sources/Public/iOS/AnimationViewBase.swift +78 -0
- package/{lottie-swift/src → Sources}/Public/iOS/BundleImageProvider.swift +36 -34
- package/{lottie-swift/src → Sources}/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift +4 -0
- package/{lottie-swift/src → Sources}/Public/iOS/Compatibility/CompatibleAnimationView.swift +38 -29
- package/{lottie-swift/src → Sources}/Public/iOS/FilepathImageProvider.swift +24 -21
- package/{lottie-swift/src → Sources}/Public/iOS/UIColorExtension.swift +4 -4
- package/{lottie-swift/src/Public/MacOS/AnimationSubview.swift → Sources/Public/macOS/AnimationSubview.macOS.swift} +4 -5
- package/{lottie-swift/src/Public/MacOS/LottieView.swift → Sources/Public/macOS/AnimationViewBase.macOS.swift} +58 -50
- package/{lottie-swift/src/Public/MacOS/BundleImageProvider.swift → Sources/Public/macOS/BundleImageProvider.macOS.swift} +31 -32
- package/Sources/Public/macOS/FilepathImageProvider.macOS.swift +67 -0
- package/Tests/AnimationKeypathTests.swift +94 -0
- package/Tests/AutomaticEngineTests.swift +57 -0
- package/Tests/BundleTests.swift +25 -0
- package/Tests/DataURLTests.swift +64 -0
- package/Tests/ParsingTests.swift +43 -0
- package/Tests/PerformanceTests.swift +215 -0
- package/Tests/SnapshotConfiguration.swift +153 -0
- package/Tests/SnapshotTests.swift +265 -0
- package/Tests/Utils/Bundle+Module.swift +30 -0
- package/Tests/Utils/HardcodedFontProvider.swift +19 -0
- package/Tests/Utils/HardcodedImageProvider.swift +23 -0
- package/Tests/Utils/Snapshotting+presentationLayer.swift +47 -0
- package/Tests/ValueProvidersTests.swift +27 -0
- package/lottie-ios.podspec +11 -12
- package/package.json +1 -1
- package/script/test-carthage/Cartfile +1 -0
- package/script/test-carthage/Cartfile.resolved +1 -0
- package/script/test-carthage/CarthageTest/AppDelegate.swift +26 -0
- package/script/test-carthage/CarthageTest/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- package/script/test-carthage/CarthageTest/Assets.xcassets/AppIcon.appiconset/Contents.json +98 -0
- package/script/test-carthage/CarthageTest/Assets.xcassets/Contents.json +6 -0
- package/script/test-carthage/CarthageTest/Base.lproj/LaunchScreen.storyboard +25 -0
- package/script/test-carthage/CarthageTest/Base.lproj/Main.storyboard +24 -0
- package/script/test-carthage/CarthageTest/Info.plist +66 -0
- package/script/test-carthage/CarthageTest/SceneDelegate.swift +10 -0
- package/script/test-carthage/CarthageTest/ViewController.swift +15 -0
- package/script/test-carthage/CarthageTest-macOS/AppDelegate.swift +7 -0
- package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json +58 -0
- package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/Contents.json +6 -0
- package/script/test-carthage/CarthageTest-macOS/Base.lproj/Main.storyboard +717 -0
- package/script/test-carthage/CarthageTest-macOS/CarthageTest_macOS.entitlements +10 -0
- package/script/test-carthage/CarthageTest-macOS/ViewController.swift +15 -0
- package/script/test-carthage/CarthageTest.xcodeproj/project.pbxproj +532 -0
- package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/script/test-carthage/CarthageTest.xcodeproj/xcuserdata/cal.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
- package/script/test-carthage/CarthageTest.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +19 -0
- package/script/test-carthage/Mintfile +1 -0
- package/script/test-spm/LottieSPM.xcworkspace/contents.xcworkspacedata +7 -0
- package/script/test-spm/LottieSPM.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/script/test-spm/Mintfile +1 -0
- package/.swiftpm/xcode/package.xcworkspace/xcuserdata/brandonwithrow.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie/Info.plist +0 -22
- package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/brandonwithrow.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad/xcschemes/LottieLibraryMacOS.xcscheme +0 -80
- package/Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad/xcschemes/xcschememanagement.plist +0 -57
- package/lottie-swift/Assets/.gitkeep +0 -0
- package/lottie-swift/src/Private/LayerContainers/Utility/LayerFontProvider.swift +0 -37
- package/lottie-swift/src/Private/LayerContainers/Utility/LayerTextProvider.swift +0 -36
- package/lottie-swift/src/Private/Model/Animation.swift +0 -107
- package/lottie-swift/src/Private/Model/Assets/Asset.swift +0 -27
- package/lottie-swift/src/Private/Model/Assets/ImageAsset.swift +0 -48
- package/lottie-swift/src/Private/Model/Keyframes/Keyframe.swift +0 -128
- package/lottie-swift/src/Private/Model/Keyframes/KeyframeGroup.swift +0 -108
- package/lottie-swift/src/Private/Model/Layers/LayerModel.swift +0 -150
- package/lottie-swift/src/Private/Model/Objects/DashPattern.swift +0 -24
- package/lottie-swift/src/Private/Model/Objects/Marker.swift +0 -23
- package/lottie-swift/src/Private/Model/Objects/Mask.swift +0 -48
- package/lottie-swift/src/Private/Model/Objects/Transform.swift +0 -105
- package/lottie-swift/src/Private/Model/ShapeItems/Ellipse.swift +0 -50
- package/lottie-swift/src/Private/Model/ShapeItems/FillI.swift +0 -49
- package/lottie-swift/src/Private/Model/ShapeItems/GradientFill.swift +0 -86
- package/lottie-swift/src/Private/Model/ShapeItems/GradientStroke.swift +0 -125
- package/lottie-swift/src/Private/Model/ShapeItems/Group.swift +0 -32
- package/lottie-swift/src/Private/Model/ShapeItems/Rectangle.swift +0 -50
- package/lottie-swift/src/Private/Model/ShapeItems/Repeater.swift +0 -80
- package/lottie-swift/src/Private/Model/ShapeItems/Shape.swift +0 -37
- package/lottie-swift/src/Private/Model/ShapeItems/ShapeItem.swift +0 -95
- package/lottie-swift/src/Private/Model/ShapeItems/ShapeTransform.swift +0 -68
- package/lottie-swift/src/Private/Model/ShapeItems/Star.swift +0 -86
- package/lottie-swift/src/Private/Model/ShapeItems/Stroke.swift +0 -67
- package/lottie-swift/src/Private/Model/ShapeItems/Trim.swift +0 -53
- package/lottie-swift/src/Private/Model/Text/Font.swift +0 -35
- package/lottie-swift/src/Private/Model/Text/TextAnimator.swift +0 -99
- package/lottie-swift/src/Private/Model/Text/TextDocument.swift +0 -70
- package/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +0 -109
- package/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +0 -132
- package/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +0 -141
- package/lottie-swift/src/Private/Utility/Extensions/MathKit.swift +0 -539
- package/lottie-swift/src/Private/Utility/Extensions/StringExtensions.swift +0 -32
- package/lottie-swift/src/Private/Utility/Interpolatable/Interpolatable.swift +0 -18
- package/lottie-swift/src/Private/Utility/Interpolatable/InterpolatableExtensions.swift +0 -170
- package/lottie-swift/src/Private/Utility/Primitives/CurveVertex.swift +0 -177
- package/lottie-swift/src/Private/Utility/Primitives/PathElement.swift +0 -68
- package/lottie-swift/src/Private/Utility/Primitives/VectorsExtensions.swift +0 -218
- package/lottie-swift/src/Public/Animation/AnimationPublic.swift +0 -196
- package/lottie-swift/src/Public/Animation/AnimationView.swift +0 -1006
- package/lottie-swift/src/Public/Animation/AnimationViewInitializers.swift +0 -83
- package/lottie-swift/src/Public/AnimationCache/AnimationCacheProvider.swift +0 -24
- package/lottie-swift/src/Public/DynamicProperties/AnimationKeypath.swift +0 -46
- package/lottie-swift/src/Public/DynamicProperties/AnyValueProvider.swift +0 -29
- package/lottie-swift/src/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +0 -114
- package/lottie-swift/src/Public/ImageProvider/AnimationImageProvider.swift +0 -23
- package/lottie-swift/src/Public/MacOS/FilepathImageProvider.swift +0 -67
- package/lottie-swift/src/Public/TextProvider/AnimationTextProvider.swift +0 -39
- package/lottie-swift/src/Public/iOS/LottieView.swift +0 -62
- package/lottie-swift-testing.podspec +0 -32
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
// Created by Cal Stephens on 12/14/21.
|
|
2
|
+
// Copyright © 2021 Airbnb Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
import QuartzCore
|
|
5
|
+
|
|
6
|
+
extension CALayer {
|
|
7
|
+
|
|
8
|
+
// MARK: Internal
|
|
9
|
+
|
|
10
|
+
/// Constructs a `CAKeyframeAnimation` that reflects the given keyframes,
|
|
11
|
+
/// and adds it to this `CALayer`.
|
|
12
|
+
@nonobjc
|
|
13
|
+
func addAnimation<KeyframeValue, ValueRepresentation: Equatable>(
|
|
14
|
+
for property: LayerProperty<ValueRepresentation>,
|
|
15
|
+
keyframes: ContiguousArray<Keyframe<KeyframeValue>>,
|
|
16
|
+
value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation,
|
|
17
|
+
context: LayerAnimationContext)
|
|
18
|
+
throws
|
|
19
|
+
{
|
|
20
|
+
if let customAnimation = try customizedAnimation(for: property, context: context) {
|
|
21
|
+
add(customAnimation, timedWith: context)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
else if
|
|
25
|
+
let defaultAnimation = try defaultAnimation(
|
|
26
|
+
for: property,
|
|
27
|
+
keyframes: keyframes,
|
|
28
|
+
value: keyframeValueMapping,
|
|
29
|
+
context: context)
|
|
30
|
+
{
|
|
31
|
+
let timedAnimation = defaultAnimation.timed(with: context, for: self)
|
|
32
|
+
add(timedAnimation, forKey: property.caLayerKeypath)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// MARK: Private
|
|
37
|
+
|
|
38
|
+
/// Constructs a `CAAnimation` that reflects the given keyframes
|
|
39
|
+
/// - If the value can be applied directly to the CALayer using KVC,
|
|
40
|
+
/// then no `CAAnimation` will be created and the value will be applied directly.
|
|
41
|
+
@nonobjc
|
|
42
|
+
private func defaultAnimation<KeyframeValue, ValueRepresentation>(
|
|
43
|
+
for property: LayerProperty<ValueRepresentation>,
|
|
44
|
+
keyframes: ContiguousArray<Keyframe<KeyframeValue>>,
|
|
45
|
+
value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation,
|
|
46
|
+
context: LayerAnimationContext)
|
|
47
|
+
throws
|
|
48
|
+
-> CAAnimation?
|
|
49
|
+
{
|
|
50
|
+
guard !keyframes.isEmpty else { return nil }
|
|
51
|
+
|
|
52
|
+
// If there is exactly one keyframe value, we can improve performance
|
|
53
|
+
// by applying that value directly to the layer instead of creating
|
|
54
|
+
// a relatively expensive `CAKeyframeAnimation`.
|
|
55
|
+
if keyframes.count == 1 {
|
|
56
|
+
return singleKeyframeAnimation(
|
|
57
|
+
for: property,
|
|
58
|
+
keyframeValue: try keyframeValueMapping(keyframes[0].value),
|
|
59
|
+
writeDirectlyToPropertyIfPossible: true)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Split the keyframes into segments with the same `CAAnimationCalculationMode` value
|
|
63
|
+
// - Each of these segments will become their own `CAKeyframeAnimation`
|
|
64
|
+
let animationSegments = keyframes.segmentsSplitByCalculationMode()
|
|
65
|
+
|
|
66
|
+
// If we only have a single segment, we can just create a single `CAKeyframeAnimation`
|
|
67
|
+
// instead of wrapping it in a `CAAnimationGroup` -- this reduces allocation overhead a bit.
|
|
68
|
+
if animationSegments.count == 1 {
|
|
69
|
+
return try keyframeAnimation(
|
|
70
|
+
for: property,
|
|
71
|
+
keyframes: animationSegments[0],
|
|
72
|
+
value: keyframeValueMapping,
|
|
73
|
+
context: context)
|
|
74
|
+
} else {
|
|
75
|
+
return try animationGroup(
|
|
76
|
+
for: property,
|
|
77
|
+
animationSegments: animationSegments,
|
|
78
|
+
value: keyframeValueMapping,
|
|
79
|
+
context: context)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// A `CAAnimation` that applies the custom value from the `AnyValueProvider`
|
|
84
|
+
/// registered for this specific property's `AnimationKeypath`,
|
|
85
|
+
/// if one has been registered using `AnimationView.setValueProvider(_:keypath:)`.
|
|
86
|
+
@nonobjc
|
|
87
|
+
private func customizedAnimation<ValueRepresentation>(
|
|
88
|
+
for property: LayerProperty<ValueRepresentation>,
|
|
89
|
+
context: LayerAnimationContext)
|
|
90
|
+
throws
|
|
91
|
+
-> CAPropertyAnimation?
|
|
92
|
+
{
|
|
93
|
+
guard
|
|
94
|
+
let customizableProperty = property.customizableProperty,
|
|
95
|
+
let customKeyframes = try context.valueProviderStore.customKeyframes(
|
|
96
|
+
of: customizableProperty,
|
|
97
|
+
for: AnimationKeypath(keys: context.currentKeypath.keys + customizableProperty.name.map { $0.rawValue }),
|
|
98
|
+
context: context)
|
|
99
|
+
else { return nil }
|
|
100
|
+
|
|
101
|
+
// Since custom animations are overriding an existing animation,
|
|
102
|
+
// we always have to create a CAAnimation and can't write directly
|
|
103
|
+
// to the layer property
|
|
104
|
+
if
|
|
105
|
+
customKeyframes.keyframes.count == 1,
|
|
106
|
+
let singleKeyframeAnimation = singleKeyframeAnimation(
|
|
107
|
+
for: property,
|
|
108
|
+
keyframeValue: customKeyframes.keyframes[0].value,
|
|
109
|
+
writeDirectlyToPropertyIfPossible: false)
|
|
110
|
+
{
|
|
111
|
+
return singleKeyframeAnimation
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return try keyframeAnimation(
|
|
115
|
+
for: property,
|
|
116
|
+
keyframes: Array(customKeyframes.keyframes),
|
|
117
|
+
value: { $0 },
|
|
118
|
+
context: context)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// Creates an animation that applies a single keyframe to this layer property
|
|
122
|
+
/// - In many cases this animation can be omitted entirely, and the underlying
|
|
123
|
+
/// property can be set directly. In that case, no animation will be created.
|
|
124
|
+
private func singleKeyframeAnimation<ValueRepresentation>(
|
|
125
|
+
for property: LayerProperty<ValueRepresentation>,
|
|
126
|
+
keyframeValue: ValueRepresentation,
|
|
127
|
+
writeDirectlyToPropertyIfPossible: Bool)
|
|
128
|
+
-> CABasicAnimation?
|
|
129
|
+
{
|
|
130
|
+
if writeDirectlyToPropertyIfPossible {
|
|
131
|
+
// If the keyframe value is the same as the layer's default value for this property,
|
|
132
|
+
// then we can just ignore this set of keyframes.
|
|
133
|
+
if keyframeValue == property.defaultValue {
|
|
134
|
+
return nil
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// If the property on the CALayer being animated hasn't been modified from the default yet,
|
|
138
|
+
// then we can apply the keyframe value directly to the layer using KVC instead
|
|
139
|
+
// of creating a `CAAnimation`.
|
|
140
|
+
if
|
|
141
|
+
let defaultValue = property.defaultValue,
|
|
142
|
+
defaultValue == value(forKey: property.caLayerKeypath) as? ValueRepresentation
|
|
143
|
+
{
|
|
144
|
+
setValue(keyframeValue, forKeyPath: property.caLayerKeypath)
|
|
145
|
+
return nil
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Otherwise, we still need to create a `CAAnimation`, but we can
|
|
150
|
+
// create a simple `CABasicAnimation` that is still less expensive
|
|
151
|
+
// than computing a `CAKeyframeAnimation`.
|
|
152
|
+
let animation = CABasicAnimation(keyPath: property.caLayerKeypath)
|
|
153
|
+
animation.fromValue = keyframeValue
|
|
154
|
+
animation.toValue = keyframeValue
|
|
155
|
+
return animation
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// Creates a `CAAnimationGroup` that wraps a `CAKeyframeAnimation` for each
|
|
159
|
+
/// of the given `animationSegments`
|
|
160
|
+
private func animationGroup<KeyframeValue, ValueRepresentation>(
|
|
161
|
+
for property: LayerProperty<ValueRepresentation>,
|
|
162
|
+
animationSegments: [[Keyframe<KeyframeValue>]],
|
|
163
|
+
value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation,
|
|
164
|
+
context: LayerAnimationContext)
|
|
165
|
+
throws
|
|
166
|
+
-> CAAnimationGroup
|
|
167
|
+
{
|
|
168
|
+
// Build the `CAKeyframeAnimation` for each segment of keyframes
|
|
169
|
+
// with the same `CAAnimationCalculationMode`.
|
|
170
|
+
// - Here we have a non-zero number of animation segments,
|
|
171
|
+
// all of which have a non-zero number of keyframes.
|
|
172
|
+
let segmentAnimations: [CAKeyframeAnimation] = try animationSegments.indices.map { index in
|
|
173
|
+
let animationSegment = animationSegments[index]
|
|
174
|
+
var segmentStartTime = context.time(for: animationSegment.first!.time)
|
|
175
|
+
var segmentEndTime = context.time(for: animationSegment.last!.time)
|
|
176
|
+
|
|
177
|
+
// Every portion of the animation timeline has to be covered by a `CAKeyframeAnimation`,
|
|
178
|
+
// so if this is the first or last segment then the start/end time should be exactly
|
|
179
|
+
// the start/end time of the animation itself.
|
|
180
|
+
let isFirstSegment = (index == animationSegments.indices.first!)
|
|
181
|
+
let isLastSegment = (index == animationSegments.indices.last!)
|
|
182
|
+
|
|
183
|
+
if isFirstSegment {
|
|
184
|
+
segmentStartTime = context.time(for: context.animation.startFrame)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if isLastSegment {
|
|
188
|
+
segmentEndTime = context.time(for: context.animation.endFrame)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let segmentDuration = segmentEndTime - segmentStartTime
|
|
192
|
+
|
|
193
|
+
// We're building `CAKeyframeAnimation`s, so the `keyTimes` are expressed
|
|
194
|
+
// relative to 0 (`segmentStartTime`) and 1 (`segmentEndTime`). This is different
|
|
195
|
+
// from the default behavior of the `keyframeAnimation` method, where times
|
|
196
|
+
// are expressed relative to the entire animation duration.
|
|
197
|
+
let customKeyTimes = animationSegment.map { keyframeModel -> NSNumber in
|
|
198
|
+
let keyframeTime = context.time(for: keyframeModel.time)
|
|
199
|
+
let segmentProgressTime = ((keyframeTime - segmentStartTime) / segmentDuration)
|
|
200
|
+
return segmentProgressTime as NSNumber
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let animation = try keyframeAnimation(
|
|
204
|
+
for: property,
|
|
205
|
+
keyframes: animationSegment,
|
|
206
|
+
value: keyframeValueMapping,
|
|
207
|
+
customKeyTimes: customKeyTimes,
|
|
208
|
+
context: context)
|
|
209
|
+
|
|
210
|
+
animation.duration = segmentDuration
|
|
211
|
+
animation.beginTime = segmentStartTime
|
|
212
|
+
return animation
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let fullAnimation = CAAnimationGroup()
|
|
216
|
+
fullAnimation.animations = segmentAnimations
|
|
217
|
+
return fullAnimation
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/// Creates and validates a `CAKeyframeAnimation` for the given keyframes
|
|
221
|
+
private func keyframeAnimation<KeyframeValue, ValueRepresentation>(
|
|
222
|
+
for property: LayerProperty<ValueRepresentation>,
|
|
223
|
+
keyframes: [Keyframe<KeyframeValue>],
|
|
224
|
+
value keyframeValueMapping: (KeyframeValue) throws -> ValueRepresentation,
|
|
225
|
+
customKeyTimes: [NSNumber]? = nil,
|
|
226
|
+
context: LayerAnimationContext)
|
|
227
|
+
throws
|
|
228
|
+
-> CAKeyframeAnimation
|
|
229
|
+
{
|
|
230
|
+
// Convert the list of `Keyframe<T>` into
|
|
231
|
+
// the representation used by `CAKeyframeAnimation`
|
|
232
|
+
var keyTimes = customKeyTimes ?? keyframes.map { keyframeModel -> NSNumber in
|
|
233
|
+
NSNumber(value: Float(context.progressTime(for: keyframeModel.time)))
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
var timingFunctions = self.timingFunctions(for: keyframes)
|
|
237
|
+
let calculationMode = self.calculationMode(for: keyframes)
|
|
238
|
+
|
|
239
|
+
let animation = CAKeyframeAnimation(keyPath: property.caLayerKeypath)
|
|
240
|
+
|
|
241
|
+
// Position animations define a `CGPath` curve that should be followed,
|
|
242
|
+
// instead of animating directly between keyframe point values.
|
|
243
|
+
if property.caLayerKeypath == LayerProperty<CGPoint>.position.caLayerKeypath {
|
|
244
|
+
animation.path = try path(keyframes: keyframes, value: { value in
|
|
245
|
+
guard let point = try keyframeValueMapping(value) as? CGPoint else {
|
|
246
|
+
context.logger.assertionFailure("Cannot create point from keyframe with value \(value)")
|
|
247
|
+
return .zero
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return point
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// All other types of keyframes provide individual values that are interpolated by Core Animation
|
|
255
|
+
else {
|
|
256
|
+
var values = try keyframes.map { keyframeModel in
|
|
257
|
+
try keyframeValueMapping(keyframeModel.value)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
validate(
|
|
261
|
+
values: &values,
|
|
262
|
+
keyTimes: &keyTimes,
|
|
263
|
+
timingFunctions: &timingFunctions,
|
|
264
|
+
for: calculationMode,
|
|
265
|
+
context: context)
|
|
266
|
+
|
|
267
|
+
animation.values = values
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
animation.calculationMode = calculationMode
|
|
271
|
+
animation.keyTimes = keyTimes
|
|
272
|
+
animation.timingFunctions = timingFunctions
|
|
273
|
+
return animation
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/// The `CAAnimationCalculationMode` that should be used for a `CAKeyframeAnimation`
|
|
277
|
+
/// animating the given keyframes
|
|
278
|
+
private func calculationMode<KeyframeValue>(
|
|
279
|
+
for keyframes: [Keyframe<KeyframeValue>])
|
|
280
|
+
-> CAAnimationCalculationMode
|
|
281
|
+
{
|
|
282
|
+
// At this point we expect all of the animations to have been split in
|
|
283
|
+
// to segments based on the `CAAnimationCalculationMode`, so we can just
|
|
284
|
+
// check the first keyframe.
|
|
285
|
+
if keyframes[0].isHold {
|
|
286
|
+
return .discrete
|
|
287
|
+
} else {
|
|
288
|
+
return .linear
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/// `timingFunctions` to apply to a `CAKeyframeAnimation` animating the given keyframes
|
|
293
|
+
private func timingFunctions<KeyframeValue>(
|
|
294
|
+
for keyframes: [Keyframe<KeyframeValue>])
|
|
295
|
+
-> [CAMediaTimingFunction]
|
|
296
|
+
{
|
|
297
|
+
// Compute the timing function between each keyframe and the subsequent keyframe
|
|
298
|
+
var timingFunctions: [CAMediaTimingFunction] = []
|
|
299
|
+
|
|
300
|
+
for (index, keyframe) in keyframes.enumerated()
|
|
301
|
+
where index != keyframes.indices.last
|
|
302
|
+
{
|
|
303
|
+
let nextKeyframe = keyframes[index + 1]
|
|
304
|
+
|
|
305
|
+
let controlPoint1 = keyframe.outTangent?.pointValue ?? .zero
|
|
306
|
+
let controlPoint2 = nextKeyframe.inTangent?.pointValue ?? CGPoint(x: 1, y: 1)
|
|
307
|
+
|
|
308
|
+
timingFunctions.append(CAMediaTimingFunction(
|
|
309
|
+
controlPoints:
|
|
310
|
+
Float(controlPoint1.x),
|
|
311
|
+
Float(controlPoint1.y),
|
|
312
|
+
Float(controlPoint2.x),
|
|
313
|
+
Float(controlPoint2.y)))
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return timingFunctions
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/// Creates a `CGPath` for the given `position` keyframes,
|
|
320
|
+
/// which accounts for `spatialInTangent`s and `spatialOutTangents`
|
|
321
|
+
private func path<KeyframeValue>(
|
|
322
|
+
keyframes positionKeyframes: [Keyframe<KeyframeValue>],
|
|
323
|
+
value keyframeValueMapping: (KeyframeValue) throws -> CGPoint) rethrows
|
|
324
|
+
-> CGPath
|
|
325
|
+
{
|
|
326
|
+
let path = CGMutablePath()
|
|
327
|
+
|
|
328
|
+
for (index, keyframe) in positionKeyframes.enumerated() {
|
|
329
|
+
if index == positionKeyframes.indices.first {
|
|
330
|
+
path.move(to: try keyframeValueMapping(keyframe.value))
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if index != positionKeyframes.indices.last {
|
|
334
|
+
let nextKeyframe = positionKeyframes[index + 1]
|
|
335
|
+
|
|
336
|
+
if
|
|
337
|
+
let controlPoint1 = keyframe.spatialOutTangent?.pointValue,
|
|
338
|
+
let controlPoint2 = nextKeyframe.spatialInTangent?.pointValue,
|
|
339
|
+
controlPoint1 != .zero,
|
|
340
|
+
controlPoint2 != .zero
|
|
341
|
+
{
|
|
342
|
+
path.addCurve(
|
|
343
|
+
to: try keyframeValueMapping(nextKeyframe.value),
|
|
344
|
+
control1: try keyframeValueMapping(keyframe.value) + controlPoint1,
|
|
345
|
+
control2: try keyframeValueMapping(nextKeyframe.value) + controlPoint2)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
else {
|
|
349
|
+
path.addLine(to: try keyframeValueMapping(nextKeyframe.value))
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
path.closeSubpath()
|
|
355
|
+
return path
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/// Validates that the requirements of the `CAKeyframeAnimation` API are met correctly
|
|
359
|
+
private func validate<ValueRepresentation>(
|
|
360
|
+
values: inout [ValueRepresentation],
|
|
361
|
+
keyTimes: inout [NSNumber],
|
|
362
|
+
timingFunctions: inout [CAMediaTimingFunction],
|
|
363
|
+
for calculationMode: CAAnimationCalculationMode,
|
|
364
|
+
context: LayerAnimationContext)
|
|
365
|
+
{
|
|
366
|
+
// Validate that we have correct start (0.0) and end (1.0) keyframes.
|
|
367
|
+
// From the documentation of `CAKeyframeAnimation.keyTimes`:
|
|
368
|
+
// - The first value in the `keyTimes` array must be 0.0 and the last value must be 1.0.
|
|
369
|
+
if keyTimes.first != 0.0 {
|
|
370
|
+
keyTimes.insert(0.0, at: 0)
|
|
371
|
+
values.insert(values[0], at: 0)
|
|
372
|
+
timingFunctions.insert(CAMediaTimingFunction(name: .linear), at: 0)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if keyTimes.last != 1.0 {
|
|
376
|
+
keyTimes.append(1.0)
|
|
377
|
+
values.append(values.last!)
|
|
378
|
+
timingFunctions.append(CAMediaTimingFunction(name: .linear))
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
switch calculationMode {
|
|
382
|
+
case .linear, .cubic:
|
|
383
|
+
// From the documentation of `CAKeyframeAnimation.keyTimes`:
|
|
384
|
+
// - The number of elements in the keyTimes array
|
|
385
|
+
// should match the number of elements in the values property
|
|
386
|
+
context.logger.assert(
|
|
387
|
+
values.count == keyTimes.count,
|
|
388
|
+
"`values.count` must exactly equal `keyTimes.count`")
|
|
389
|
+
|
|
390
|
+
context.logger.assert(
|
|
391
|
+
timingFunctions.count == (values.count - 1),
|
|
392
|
+
"`timingFunctions.count` must exactly equal `values.count - 1`")
|
|
393
|
+
|
|
394
|
+
case .discrete:
|
|
395
|
+
// From the documentation of `CAKeyframeAnimation.keyTimes`:
|
|
396
|
+
// - If the calculationMode is set to discrete... the keyTimes array
|
|
397
|
+
// should have one more entry than appears in the values array.
|
|
398
|
+
values.removeLast()
|
|
399
|
+
|
|
400
|
+
context.logger.assert(
|
|
401
|
+
keyTimes.count == values.count + 1,
|
|
402
|
+
"`keyTimes.count` must exactly equal `values.count + 1`")
|
|
403
|
+
|
|
404
|
+
default:
|
|
405
|
+
context.logger.assertionFailure("""
|
|
406
|
+
Unexpected keyframe calculation mode \(calculationMode)
|
|
407
|
+
""")
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
extension RandomAccessCollection {
|
|
414
|
+
/// Splits this array of `Keyframe`s into segments with the same `CAAnimationCalculationMode`
|
|
415
|
+
/// - Keyframes with `isHold=true` become `discrete`, and keyframes with `isHold=false`
|
|
416
|
+
/// become linear. Each `CAKeyframeAnimation` can only be one or the other, so each
|
|
417
|
+
/// `calculationModeSegment` becomes its own `CAKeyframeAnimation`.
|
|
418
|
+
func segmentsSplitByCalculationMode<KeyframeValue>() -> [[Element]]
|
|
419
|
+
where Element == Keyframe<KeyframeValue>, Index == Int
|
|
420
|
+
{
|
|
421
|
+
var segments: [[Element]] = []
|
|
422
|
+
var currentSegment: [Element] = []
|
|
423
|
+
|
|
424
|
+
for keyframe in self {
|
|
425
|
+
guard let mostRecentKeyframe = currentSegment.last else {
|
|
426
|
+
currentSegment.append(keyframe)
|
|
427
|
+
continue
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// When `isHold` changes between any two given keyframes, we have to create a new segment
|
|
431
|
+
if keyframe.isHold != mostRecentKeyframe.isHold {
|
|
432
|
+
// Add this keyframe to both the existing segment that is ending,
|
|
433
|
+
// so we know how long that segment is, and the new segment,
|
|
434
|
+
// so we know when that segment starts.
|
|
435
|
+
currentSegment.append(keyframe)
|
|
436
|
+
segments.append(currentSegment)
|
|
437
|
+
currentSegment = [keyframe]
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
else {
|
|
441
|
+
currentSegment.append(keyframe)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
segments.append(currentSegment)
|
|
446
|
+
return segments
|
|
447
|
+
}
|
|
448
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Created by Cal Stephens on 1/28/22.
|
|
2
|
+
// Copyright © 2022 Airbnb Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
import QuartzCore
|
|
5
|
+
|
|
6
|
+
extension CAShapeLayer {
|
|
7
|
+
/// Adds animations for the given `CombinedShapeItem` to this `CALayer`
|
|
8
|
+
@nonobjc
|
|
9
|
+
func addAnimations(
|
|
10
|
+
for combinedShapes: CombinedShapeItem,
|
|
11
|
+
context: LayerAnimationContext,
|
|
12
|
+
pathMultiplier: PathMultiplier)
|
|
13
|
+
throws
|
|
14
|
+
{
|
|
15
|
+
try addAnimation(
|
|
16
|
+
for: .path,
|
|
17
|
+
keyframes: combinedShapes.shapes.keyframes,
|
|
18
|
+
value: { paths in
|
|
19
|
+
let combinedPath = CGMutablePath()
|
|
20
|
+
for path in paths {
|
|
21
|
+
combinedPath.addPath(path.cgPath().duplicated(times: pathMultiplier))
|
|
22
|
+
}
|
|
23
|
+
return combinedPath
|
|
24
|
+
},
|
|
25
|
+
context: context)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// MARK: - CombinedShapeItem
|
|
30
|
+
|
|
31
|
+
/// A custom `ShapeItem` subclass that combines multiple `Shape`s into a single `KeyframeGroup`
|
|
32
|
+
final class CombinedShapeItem: ShapeItem {
|
|
33
|
+
|
|
34
|
+
// MARK: Lifecycle
|
|
35
|
+
|
|
36
|
+
init(shapes: KeyframeGroup<[BezierPath]>, name: String) {
|
|
37
|
+
self.shapes = shapes
|
|
38
|
+
super.init(name: name, type: .shape, hidden: false)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
required init(from _: Decoder) throws {
|
|
42
|
+
fatalError("init(from:) has not been implemented")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
required init(dictionary _: [String: Any]) throws {
|
|
46
|
+
fatalError("init(dictionary:) has not been implemented")
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// MARK: Internal
|
|
50
|
+
|
|
51
|
+
let shapes: KeyframeGroup<[BezierPath]>
|
|
52
|
+
|
|
53
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Created by Cal Stephens on 12/21/21.
|
|
2
|
+
// Copyright © 2021 Airbnb Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
import QuartzCore
|
|
5
|
+
|
|
6
|
+
extension CAShapeLayer {
|
|
7
|
+
/// Adds animations for the given `BezierPath` keyframes to this `CALayer`
|
|
8
|
+
@nonobjc
|
|
9
|
+
func addAnimations(
|
|
10
|
+
for customPath: KeyframeGroup<BezierPath>,
|
|
11
|
+
context: LayerAnimationContext,
|
|
12
|
+
pathMultiplier: PathMultiplier = 1,
|
|
13
|
+
transformPath: (CGPath) -> CGPath = { $0 })
|
|
14
|
+
throws
|
|
15
|
+
{
|
|
16
|
+
try addAnimation(
|
|
17
|
+
for: .path,
|
|
18
|
+
keyframes: customPath.keyframes,
|
|
19
|
+
value: { pathKeyframe in
|
|
20
|
+
transformPath(pathKeyframe.cgPath().duplicated(times: pathMultiplier))
|
|
21
|
+
},
|
|
22
|
+
context: context)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
extension CGPath {
|
|
27
|
+
/// Duplicates this `CGPath` so that it is repeated the given number of times
|
|
28
|
+
func duplicated(times: Int) -> CGPath {
|
|
29
|
+
if times <= 1 {
|
|
30
|
+
return self
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let cgPath = CGMutablePath()
|
|
34
|
+
|
|
35
|
+
for _ in 0..<times {
|
|
36
|
+
cgPath.addPath(self)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return cgPath
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Created by Cal Stephens on 12/21/21.
|
|
2
|
+
// Copyright © 2021 Airbnb Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
import QuartzCore
|
|
5
|
+
|
|
6
|
+
extension CAShapeLayer {
|
|
7
|
+
/// Adds animations for the given `Ellipse` to this `CALayer`
|
|
8
|
+
@nonobjc
|
|
9
|
+
func addAnimations(
|
|
10
|
+
for ellipse: Ellipse,
|
|
11
|
+
context: LayerAnimationContext,
|
|
12
|
+
pathMultiplier: PathMultiplier)
|
|
13
|
+
throws
|
|
14
|
+
{
|
|
15
|
+
try addAnimation(
|
|
16
|
+
for: .path,
|
|
17
|
+
keyframes: ellipse.size.keyframes,
|
|
18
|
+
value: { sizeKeyframe in
|
|
19
|
+
BezierPath.ellipse(
|
|
20
|
+
size: sizeKeyframe.sizeValue,
|
|
21
|
+
center: try ellipse.position.exactlyOneKeyframe(context: context, description: "ellipse position").value.pointValue,
|
|
22
|
+
direction: ellipse.direction)
|
|
23
|
+
.cgPath()
|
|
24
|
+
.duplicated(times: pathMultiplier)
|
|
25
|
+
},
|
|
26
|
+
context: context)
|
|
27
|
+
}
|
|
28
|
+
}
|