diff --git a/Sources/Private/CoreAnimation/ValueProviderStore.swift b/Sources/Private/CoreAnimation/ValueProviderStore.swift index 2fe937aa59..91f1140532 100644 --- a/Sources/Private/CoreAnimation/ValueProviderStore.swift +++ b/Sources/Private/CoreAnimation/ValueProviderStore.swift @@ -36,7 +36,9 @@ final class ValueProviderStore { properties. Supported properties are: \(supportedProperties.joined(separator: ", ")). """) - valueProviders.append((keypath: keypath, valueProvider: valueProvider)) + orderedKeyPaths.remove(keypath) + orderedKeyPaths.insert(keypath, at: orderedKeyPaths.count) + valueProviders[keypath] = valueProvider } // Retrieves the custom value keyframes for the given property, @@ -93,16 +95,19 @@ final class ValueProviderStore { // MARK: Private private let logger: LottieLogger + private var valueProviders = [AnimationKeypath: AnyValueProvider]() + private var orderedKeyPaths = NSMutableOrderedSet() - private var valueProviders = [(keypath: AnimationKeypath, valueProvider: AnyValueProvider)]() - - /// Retrieves the most-recently-registered Value Provider that matches the given keypat + /// Retrieves the most-recently-registered Value Provider that matches the given keypath. private func valueProvider(for keypath: AnimationKeypath) -> AnyValueProvider? { // Find the last keypath matching the given keypath, // so we return the value provider that was registered most-recently - valueProviders.last(where: { registeredKeypath, _ in - keypath.matches(registeredKeypath) - })?.valueProvider + guard + let matchingPath = orderedKeyPaths.reversed.first(where: { element in + guard let registeredKeypath = (element as? AnimationKeypath) else { return false } + return keypath.matches(registeredKeypath) + }) as? AnimationKeypath else { return nil } + return valueProviders[matchingPath] } } diff --git a/Tests/SnapshotConfiguration.swift b/Tests/SnapshotConfiguration.swift index 970e65177a..05bd915493 100644 --- a/Tests/SnapshotConfiguration.swift +++ b/Tests/SnapshotConfiguration.swift @@ -186,5 +186,6 @@ extension SnapshotConfiguration { extension LottieColor { static let black = LottieColor(r: 0, g: 0, b: 0, a: 1) static let red = LottieColor(r: 1, g: 0, b: 0, a: 1) + static let blue = LottieColor(r: 0, g: 0, b: 1, a: 1) } #endif diff --git a/Tests/ValueProvidersTests.swift b/Tests/ValueProvidersTests.swift index 8b316ea5eb..94775252d3 100644 --- a/Tests/ValueProvidersTests.swift +++ b/Tests/ValueProvidersTests.swift @@ -5,8 +5,8 @@ // Created by Marcelo Fabri on 5/5/22. // -import Lottie import XCTest +@testable import Lottie @MainActor final class ValueProvidersTests: XCTestCase { @@ -27,4 +27,53 @@ final class ValueProvidersTests: XCTestCase { XCTAssertEqual(originalColor, LottieColor(r: 0.4, g: 0.16, b: 0.7, a: 1)) } + func testValueProviderStore() async throws { + let optionalAnimationView = await SnapshotConfiguration.makeAnimationView( + for: "HamburgerArrow", + configuration: .init(renderingEngine: .mainThread)) + let animation = try XCTUnwrap(optionalAnimationView?.animation) + + let store = ValueProviderStore(logger: .printToConsole) + let animationContext = LayerAnimationContext( + animation: animation, + timingConfiguration: .init(), + startFrame: 0, + endFrame: 100, + valueProviderStore: store, + compatibilityTracker: .init(mode: .track, logger: .printToConsole), + logger: .printToConsole, + currentKeypath: .init(keys: []), + textProvider: DictionaryTextProvider([:])) + + // Test that the store returns the expected value for the provider. + store.setValueProvider(ColorValueProvider(.red), keypath: "**.Color") + let keyFramesQuery1 = try store.customKeyframes( + of: .color, + for: "Layer.Shape Group.Stroke 1.Color", + context: animationContext) + XCTAssertEqual(keyFramesQuery1?.keyframes.map(\.value.components), [[1, 0, 0, 1]]) + + // Test a different provider/keypath. + store.setValueProvider(ColorValueProvider(.blue), keypath: "A1.Shape 1.Stroke 1.Color") + let keyFramesQuery2 = try store.customKeyframes( + of: .color, + for: "A1.Shape 1.Stroke 1.Color", + context: animationContext) + XCTAssertEqual(keyFramesQuery2?.keyframes.map(\.value.components), [[0, 0, 1, 1]]) + + // Test that adding a different keypath didn't disrupt the original one. + let keyFramesQuery3 = try store.customKeyframes( + of: .color, + for: "Layer.Shape Group.Stroke 1.Color", + context: animationContext) + XCTAssertEqual(keyFramesQuery3?.keyframes.map(\.value.components), [[1, 0, 0, 1]]) + + // Test overriding the original keypath with a new provider stores the new provider. + store.setValueProvider(ColorValueProvider(.black), keypath: "**.Color") + let keyFramesQuery4 = try store.customKeyframes( + of: .color, + for: "Layer.Shape Group.Stroke 1.Color", + context: animationContext) + XCTAssertEqual(keyFramesQuery4?.keyframes.map(\.value.components), [[0, 0, 0, 1]]) + } }