diff --git a/Sources/Private/CoreAnimation/CoreAnimationLayer.swift b/Sources/Private/CoreAnimation/CoreAnimationLayer.swift index 594368dc51..0d7e9ac76b 100644 --- a/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +++ b/Sources/Private/CoreAnimation/CoreAnimationLayer.swift @@ -495,6 +495,14 @@ extension CoreAnimationLayer: RootAnimationLayer { rebuildCurrentAnimation() } + func removeValueProvider(for keypath: AnimationKeypath) { + valueProviderStore.removeValueProvider(for: keypath) + + // We need to rebuild the current animation after removing a value provider, + // since any existing `CAAnimation`s could now be out of date. + rebuildCurrentAnimation() + } + func getValue(for _: AnimationKeypath, atFrame _: AnimationFrameTime?) -> Any? { logger.assertionFailure(""" The Core Animation rendering engine doesn't support querying values for individual frames diff --git a/Sources/Private/CoreAnimation/ValueProviderStore.swift b/Sources/Private/CoreAnimation/ValueProviderStore.swift index 76934114bc..6e32062aca 100644 --- a/Sources/Private/CoreAnimation/ValueProviderStore.swift +++ b/Sources/Private/CoreAnimation/ValueProviderStore.swift @@ -40,6 +40,11 @@ final class ValueProviderStore { valueProviders.append((keypath: keypath, valueProvider: valueProvider)) } + /// Removes all ValueProviders for the given `AnimationKeypath` + func removeValueProvider(for keypath: AnimationKeypath) { + valueProviders.removeAll(where: { $0.keypath.matches(keypath) }) + } + /// Retrieves the custom value keyframes for the given property, /// if an `AnyValueProvider` was registered for the given keypath. func customKeyframes( diff --git a/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift b/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift index 14e45a6fe4..0d5c5b627b 100644 --- a/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +++ b/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift @@ -241,6 +241,17 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer { } } + func removeValueProvider(for keypath: AnimationKeypath) { + for layer in animationLayers { + if let foundProperties = layer.nodeProperties(for: keypath) { + for property in foundProperties { + property.removeProvider() + } + layer.displayWithFrame(frame: presentation()?.currentFrame ?? currentFrame, forceUpdates: true) + } + } + } + func getValue(for keypath: AnimationKeypath, atFrame: CGFloat?) -> Any? { for layer in animationLayers { if diff --git a/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.swift b/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.swift index 8702f2c59c..571b970902 100644 --- a/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.swift +++ b/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.swift @@ -45,6 +45,11 @@ class NodeProperty: AnyNodeProperty { valueContainer.setNeedsUpdate() } + func removeProvider() { + valueProvider = originalValueProvider + valueContainer.setNeedsUpdate() + } + func update(frame: CGFloat) { typedContainer.setValue(valueProvider.value(frame: frame), forFrame: frame) } diff --git a/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift b/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift index 132d96a894..ff27db812b 100644 --- a/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift +++ b/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift @@ -33,6 +33,9 @@ protocol AnyNodeProperty { /// Sets the value provider for the property. func setProvider(provider: AnyValueProvider) + + /// Removes the value provider for the property. + func removeProvider() } extension AnyNodeProperty { diff --git a/Sources/Private/RootAnimationLayer.swift b/Sources/Private/RootAnimationLayer.swift index aa1854162c..12839fb62f 100644 --- a/Sources/Private/RootAnimationLayer.swift +++ b/Sources/Private/RootAnimationLayer.swift @@ -36,6 +36,7 @@ protocol RootAnimationLayer: CALayer { func logHierarchyKeypaths() func allHierarchyKeypaths() -> [String] func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) + func removeValueProvider(for keypath: AnimationKeypath) func getValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? func getOriginalValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? diff --git a/Sources/Public/Animation/LottieAnimationLayer.swift b/Sources/Public/Animation/LottieAnimationLayer.swift index df369974fc..d9f2f7be41 100644 --- a/Sources/Public/Animation/LottieAnimationLayer.swift +++ b/Sources/Public/Animation/LottieAnimationLayer.swift @@ -854,6 +854,16 @@ public class LottieAnimationLayer: CALayer { animationLayer.setValueProvider(valueProvider, keypath: keypath) } + public func removeValueProvider(for keypath: AnimationKeypath) { + guard let animationLayer = rootAnimationLayer else { return } + + valueProviders.forEach { + guard $0.key.matches(keypath) else { return } + valueProviders[$0.key] = nil + } + animationLayer.removeValueProvider(for: keypath) + } + /// Reads the value of a property specified by the Keypath. /// Returns nil if no property is found. /// diff --git a/Sources/Public/Animation/LottieAnimationView.swift b/Sources/Public/Animation/LottieAnimationView.swift index 22b70e5e1c..0c0676acb3 100644 --- a/Sources/Public/Animation/LottieAnimationView.swift +++ b/Sources/Public/Animation/LottieAnimationView.swift @@ -673,6 +673,12 @@ open class LottieAnimationView: LottieAnimationViewBase { lottieAnimationLayer.setValueProvider(valueProvider, keypath: keypath) } + /// Sets a ValueProvider for the specified keypath. The value provider will be removed + /// on all properties that match the keypath. + public func removeValueProvider(for keypath: AnimationKeypath) { + lottieAnimationLayer.removeValueProvider(for: keypath) + } + /// Reads the value of a property specified by the Keypath. /// Returns nil if no property is found. /// diff --git a/Sources/Public/Controls/AnimatedControl.swift b/Sources/Public/Controls/AnimatedControl.swift index ae2cdd563f..a22fde0528 100644 --- a/Sources/Public/Controls/AnimatedControl.swift +++ b/Sources/Public/Controls/AnimatedControl.swift @@ -190,6 +190,11 @@ open class AnimatedControl: LottieControlType { animationView.setValueProvider(valueProvider, keypath: keypath) } + /// Removes a ValueProvider for the specified keypath + public func removeValueProvider(for keypath: AnimationKeypath) { + animationView.removeValueProvider(for: keypath) + } + // MARK: Internal var stateMap: [UInt: String] = [:] diff --git a/Tests/ValueProvidersTests.swift b/Tests/ValueProvidersTests.swift index a5d0889b96..e44813c036 100644 --- a/Tests/ValueProvidersTests.swift +++ b/Tests/ValueProvidersTests.swift @@ -76,5 +76,24 @@ final class ValueProvidersTests: XCTestCase { for: "Layer.Shape Group.Stroke 1.Color", context: animationContext) XCTAssertEqual(keyFramesQuery4?.keyframes.map(\.value.components), [[0, 0, 0, 1]]) + + // Test removing specific keypath + let keypathToRemove: AnimationKeypath = "**.Color" + store.setValueProvider(ColorValueProvider(.black), keypath: keypathToRemove) + store.removeValueProvider(for: keypathToRemove) + let keyFramesQuery5 = try store.customKeyframes( + of: .color, + for: "Layer.Shape Group.Stroke 1.Color", + context: animationContext) + XCTAssertNil(keyFramesQuery5) + + // Test removing wildcard keypath + store.setValueProvider(ColorValueProvider(.black), keypath: "**1.Color") + store.removeValueProvider(for: "**.Color") + let keyFramesQuery6 = try store.customKeyframes( + of: .color, + for: "Layer.Shape Group.Stroke 1.Color", + context: animationContext) + XCTAssertNil(keyFramesQuery6) } }