Skip to content

Commit a0c713b

Browse files
caldaiago849
authored andcommitted
Update text provider API to use full AnimationKeypath values (airbnb#2183)
1 parent d9eb26f commit a0c713b

File tree

38 files changed

+412
-60
lines changed

38 files changed

+412
-60
lines changed

Lottie.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@
284284
080DF07F2A95718200BE2D96 /* AnimationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E9C95D22822F43100677516 /* AnimationContext.swift */; };
285285
080DF0802A95718200BE2D96 /* AnyEquatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABF033B32A7B0ABA00F8C228 /* AnyEquatable.swift */; };
286286
080DF0812A95718200BE2D96 /* LottieAnimationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0819D2A02A718CAE00D7DE49 /* LottieAnimationSource.swift */; };
287+
080F5FDC2AB1075000ADC32C /* TextProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080F5FDB2AB1075000ADC32C /* TextProviderTests.swift */; };
287288
0819D2A12A718CAE00D7DE49 /* LottieAnimationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0819D2A02A718CAE00D7DE49 /* LottieAnimationSource.swift */; };
288289
0819D2A22A718CAE00D7DE49 /* LottieAnimationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0819D2A02A718CAE00D7DE49 /* LottieAnimationSource.swift */; };
289290
0819D2A32A718CAE00D7DE49 /* LottieAnimationSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0819D2A02A718CAE00D7DE49 /* LottieAnimationSource.swift */; };
@@ -1159,6 +1160,7 @@
11591160

11601161
/* Begin PBXFileReference section */
11611162
080DEF622A95707B00BE2D96 /* Lottie.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Lottie.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1163+
080F5FDB2AB1075000ADC32C /* TextProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextProviderTests.swift; sourceTree = "<group>"; };
11621164
0819D2A02A718CAE00D7DE49 /* LottieAnimationSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieAnimationSource.swift; sourceTree = "<group>"; };
11631165
0820D5922A8ACD67007D705C /* LottieButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieButton.swift; sourceTree = "<group>"; };
11641166
0820D5962A8ACDD7007D705C /* AnimatedButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimatedButton.swift; sourceTree = "<group>"; };
@@ -1752,6 +1754,7 @@
17521754
D453D8AE28FF9BC600D3F49C /* AnimationCacheProviderTests.swift */,
17531755
08CB2680291ED2B700B4F071 /* AnimationViewTests.swift */,
17541756
2E70F79E295BB6D30089A0EF /* CompatibleAnimationViewTests.swift */,
1757+
080F5FDB2AB1075000ADC32C /* TextProviderTests.swift */,
17551758
);
17561759
path = Tests;
17571760
sourceTree = "<group>";
@@ -3233,6 +3236,7 @@
32333236
36E57EAC28AF7ADF00B7EFDA /* HardcodedTextProvider.swift in Sources */,
32343237
2E72128527BB32DB0027BC56 /* PerformanceTests.swift in Sources */,
32353238
6DB3BDC328245AA2002A276D /* ParsingTests.swift in Sources */,
3239+
080F5FDC2AB1075000ADC32C /* TextProviderTests.swift in Sources */,
32363240
6DB3BDB628243FA5002A276D /* ValueProvidersTests.swift in Sources */,
32373241
2E72128327BB329C0027BC56 /* AnimationKeypathTests.swift in Sources */,
32383242
2E044E272820536800FA773B /* AutomaticEngineTests.swift in Sources */,

Sources/Private/CoreAnimation/CoreAnimationLayer.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ final class CoreAnimationLayer: BaseAnimationLayer {
1717
init(
1818
animation: LottieAnimation,
1919
imageProvider: AnimationImageProvider,
20-
textProvider: AnimationTextProvider,
20+
textProvider: AnimationKeypathTextProvider,
2121
fontProvider: AnimationFontProvider,
2222
maskAnimationToBounds: Bool,
2323
compatibilityTrackerMode: CompatibilityTracker.Mode,
@@ -108,9 +108,9 @@ final class CoreAnimationLayer: BaseAnimationLayer {
108108
didSet { reloadImages() }
109109
}
110110

111-
/// The `AnimationTextProvider` that `TextLayer`'s use to retrieve texts,
111+
/// The `AnimationKeypathTextProvider` that `TextLayer`'s use to retrieve texts,
112112
/// that they should use to render their text context
113-
var textProvider: AnimationTextProvider {
113+
var textProvider: AnimationKeypathTextProvider {
114114
didSet {
115115
// We need to rebuild the current animation after updating the text provider,
116116
// since this is used in `TextLayer.setupAnimations(context:)`
@@ -449,7 +449,7 @@ extension CoreAnimationLayer: RootAnimationLayer {
449449
}
450450

451451
func forceDisplayUpdate() {
452-
// Unimplemented / unused
452+
display()
453453
}
454454

455455
func logHierarchyKeypaths() {

Sources/Private/CoreAnimation/Layers/AnimationLayer.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ struct LayerAnimationContext {
4141
/// The AnimationKeypath represented by the current layer
4242
var currentKeypath: AnimationKeypath
4343

44-
/// The `AnimationTextProvider`
45-
var textProvider: AnimationTextProvider
44+
/// The `AnimationKeypathTextProvider`
45+
var textProvider: AnimationKeypathTextProvider
4646

4747
/// Records the given animation keypath so it can be logged or collected into a list
4848
/// - Used for `CoreAnimationLayer.logHierarchyKeypaths()` and `allHierarchyKeypaths()`

Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import QuartzCore
99
struct LayerContext {
1010
let animation: LottieAnimation
1111
let imageProvider: AnimationImageProvider
12-
let textProvider: AnimationTextProvider
12+
let textProvider: AnimationKeypathTextProvider
1313
let fontProvider: AnimationFontProvider
1414
let compatibilityTracker: CompatibilityTracker
1515
var layerName: String

Sources/Private/CoreAnimation/Layers/TextLayer.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,19 @@ final class TextLayer: BaseCompositionLayer {
4444
context: textAnimationContext,
4545
description: "text layer text")
4646

47-
renderLayer.text = context.textProvider.textFor(
48-
keypathName: textAnimationContext.currentKeypath.fullPath,
49-
sourceText: sourceText.text)
47+
// Prior to Lottie 4.3.0 the Core Animation rendering engine always just used `LegacyAnimationTextProvider`
48+
// but incorrectly called it with the full keypath string, unlike the Main Thread rendering engine
49+
// which only used the last component of the keypath. Starting in Lottie 4.3.0 we use `AnimationKeypathTextProvider`
50+
// instead if implemented.
51+
if let keypathTextValue = context.textProvider.text(for: textAnimationContext.currentKeypath, sourceText: sourceText.text) {
52+
renderLayer.text = keypathTextValue
53+
} else if let legacyTextProvider = context.textProvider as? LegacyAnimationTextProvider {
54+
renderLayer.text = legacyTextProvider.textFor(
55+
keypathName: textAnimationContext.currentKeypath.fullPath,
56+
sourceText: sourceText.text)
57+
} else {
58+
renderLayer.text = sourceText.text
59+
}
5060

5161
renderLayer.sizeToFit()
5262
}

Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ final class PreCompositionLayer: CompositionLayer {
1616
precomp: PreCompLayerModel,
1717
asset: PrecompAsset,
1818
layerImageProvider: LayerImageProvider,
19-
textProvider: AnimationTextProvider,
19+
layerTextProvider: LayerTextProvider,
20+
textProvider: AnimationKeypathTextProvider,
2021
fontProvider: AnimationFontProvider,
2122
assetLibrary: AssetLibrary?,
22-
frameRate: CGFloat)
23+
frameRate: CGFloat,
24+
rootAnimationLayer: MainThreadAnimationLayer?)
2325
{
2426
animationLayers = []
2527
if let keyframes = precomp.timeRemapping?.keyframes {
@@ -36,11 +38,14 @@ final class PreCompositionLayer: CompositionLayer {
3638
let layers = asset.layers.initializeCompositionLayers(
3739
assetLibrary: assetLibrary,
3840
layerImageProvider: layerImageProvider,
41+
layerTextProvider: layerTextProvider,
3942
textProvider: textProvider,
4043
fontProvider: fontProvider,
41-
frameRate: frameRate)
44+
frameRate: frameRate,
45+
rootAnimationLayer: rootAnimationLayer)
4246

4347
var imageLayers = [ImageCompositionLayer]()
48+
var textLayers = [TextCompositionLayer]()
4449

4550
var mattedLayer: CompositionLayer? = nil
4651

@@ -50,6 +55,9 @@ final class PreCompositionLayer: CompositionLayer {
5055
if let imageLayer = layer as? ImageCompositionLayer {
5156
imageLayers.append(imageLayer)
5257
}
58+
if let textLayer = layer as? TextCompositionLayer {
59+
textLayers.append(textLayer)
60+
}
5361
if let matte = mattedLayer {
5462
/// The previous layer requires this layer to be its matte
5563
matte.matteLayer = layer
@@ -69,6 +77,7 @@ final class PreCompositionLayer: CompositionLayer {
6977
childKeypaths.append(contentsOf: layers)
7078

7179
layerImageProvider.addImageLayers(imageLayers)
80+
layerTextProvider.addTextLayers(textLayers)
7281
}
7382

7483
override init(layer: Any) {

Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,12 @@ final class TextCompositionLayer: CompositionLayer {
4747

4848
// MARK: Lifecycle
4949

50-
init(textLayer: TextLayerModel, textProvider: AnimationTextProvider, fontProvider: AnimationFontProvider) {
50+
init(
51+
textLayer: TextLayerModel,
52+
textProvider: AnimationKeypathTextProvider,
53+
fontProvider: AnimationFontProvider,
54+
rootAnimationLayer: MainThreadAnimationLayer?)
55+
{
5156
var rootNode: TextAnimatorNode?
5257
for animator in textLayer.animators {
5358
rootNode = TextAnimatorNode(parentNode: rootNode, textAnimator: animator)
@@ -57,6 +62,7 @@ final class TextCompositionLayer: CompositionLayer {
5762

5863
self.textProvider = textProvider
5964
self.fontProvider = fontProvider
65+
self.rootAnimationLayer = rootAnimationLayer
6066

6167
super.init(layer: textLayer, size: .zero)
6268
contentsLayer.addSublayer(self.textLayer)
@@ -92,8 +98,18 @@ final class TextCompositionLayer: CompositionLayer {
9298
let textDocument: KeyframeInterpolator<TextDocument>?
9399

94100
let textLayer = CoreTextRenderLayer()
95-
var textProvider: AnimationTextProvider
101+
var textProvider: AnimationKeypathTextProvider
96102
var fontProvider: AnimationFontProvider
103+
weak var rootAnimationLayer: MainThreadAnimationLayer?
104+
105+
lazy var fullAnimationKeypath: AnimationKeypath = {
106+
// Individual layers don't know their full keypaths, so we have to delegate
107+
// to the `MainThreadAnimationLayer` to search the layer hierarchy and find
108+
// the full keypath (which includes this layer's parent layers)
109+
rootAnimationLayer?.keypath(for: self)
110+
// If that failed for some reason, just use the last path component (which we do have here)
111+
?? AnimationKeypath(keypath: keypathName)
112+
}()
97113

98114
override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) {
99115
guard let textDocument = textDocument else { return }
@@ -108,11 +124,23 @@ final class TextCompositionLayer: CompositionLayer {
108124

109125
// Get Text Attributes
110126
let text = textDocument.value(frame: frame) as! TextDocument
127+
128+
// Prior to Lottie 4.3.0 the Main Thread rendering engine always just used `LegacyAnimationTextProvider`
129+
// and called it with the `keypathName` (only the last path component of the full keypath).
130+
// Starting in Lottie 4.3.0 we use `AnimationKeypathTextProvider` instead if implemented.
131+
let textString: String
132+
if let keypathTextValue = textProvider.text(for: fullAnimationKeypath, sourceText: text.text) {
133+
textString = keypathTextValue
134+
} else if let legacyTextProvider = textProvider as? LegacyAnimationTextProvider {
135+
textString = legacyTextProvider.textFor(keypathName: keypathName, sourceText: text.text)
136+
} else {
137+
textString = text.text
138+
}
139+
111140
let strokeColor = rootNode?.textOutputNode.strokeColor ?? text.strokeColorData?.cgColorValue
112141
let strokeWidth = rootNode?.textOutputNode.strokeWidth ?? CGFloat(text.strokeWidth ?? 0)
113142
let tracking = (CGFloat(text.fontSize) * (rootNode?.textOutputNode.tracking ?? CGFloat(text.tracking))) / 1000.0
114143
let matrix = rootNode?.textOutputNode.xform ?? CATransform3DIdentity
115-
let textString = textProvider.textFor(keypathName: keypathName, sourceText: text.text)
116144
let ctFont = fontProvider.fontFor(family: text.fontFamily, size: CGFloat(text.fontSize))
117145

118146
// Set all of the text layer options

Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
2121
init(
2222
animation: LottieAnimation,
2323
imageProvider: AnimationImageProvider,
24-
textProvider: AnimationTextProvider,
24+
textProvider: AnimationKeypathTextProvider,
2525
fontProvider: AnimationFontProvider,
2626
maskAnimationToBounds: Bool,
2727
logger: LottieLogger)
@@ -37,9 +37,11 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
3737
let layers = animation.layers.initializeCompositionLayers(
3838
assetLibrary: animation.assetLibrary,
3939
layerImageProvider: layerImageProvider,
40+
layerTextProvider: layerTextProvider,
4041
textProvider: textProvider,
4142
fontProvider: fontProvider,
42-
frameRate: CGFloat(animation.framerate))
43+
frameRate: CGFloat(animation.framerate),
44+
rootAnimationLayer: self)
4345

4446
var imageLayers = [ImageCompositionLayer]()
4547
var textLayers = [TextCompositionLayer]()
@@ -191,7 +193,7 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
191193
}
192194
}
193195

194-
var textProvider: AnimationTextProvider {
196+
var textProvider: AnimationKeypathTextProvider {
195197
get { layerTextProvider.textProvider }
196198
set { layerTextProvider.textProvider = newValue }
197199
}
@@ -272,6 +274,15 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
272274
return nil
273275
}
274276

277+
func keypath(for layerToFind: CALayer) -> AnimationKeypath? {
278+
for layer in animationLayers {
279+
if let foundKeypath = layer.keypath(for: layerToFind) {
280+
return foundKeypath
281+
}
282+
}
283+
return nil
284+
}
285+
275286
func animatorNodes(for keypath: AnimationKeypath) -> [AnimatorNode]? {
276287
var results = [AnimatorNode]()
277288
for layer in animationLayers {

Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ extension Array where Element == LayerModel {
1313
func initializeCompositionLayers(
1414
assetLibrary: AssetLibrary?,
1515
layerImageProvider: LayerImageProvider,
16-
textProvider: AnimationTextProvider,
16+
layerTextProvider: LayerTextProvider,
17+
textProvider: AnimationKeypathTextProvider,
1718
fontProvider: AnimationFontProvider,
18-
frameRate: CGFloat) -> [CompositionLayer]
19+
frameRate: CGFloat,
20+
rootAnimationLayer: MainThreadAnimationLayer?)
21+
-> [CompositionLayer]
1922
{
2023
var compositionLayers = [CompositionLayer]()
2124
var layerMap = [Int : CompositionLayer]()
@@ -45,10 +48,12 @@ extension Array where Element == LayerModel {
4548
precomp: precompLayer,
4649
asset: precompAsset,
4750
layerImageProvider: layerImageProvider,
51+
layerTextProvider: layerTextProvider,
4852
textProvider: textProvider,
4953
fontProvider: fontProvider,
5054
assetLibrary: assetLibrary,
51-
frameRate: frameRate)
55+
frameRate: frameRate,
56+
rootAnimationLayer: rootAnimationLayer)
5257
compositionLayers.append(precompContainer)
5358
layerMap[layer.index] = precompContainer
5459
} else if
@@ -62,7 +67,11 @@ extension Array where Element == LayerModel {
6267
compositionLayers.append(imageContainer)
6368
layerMap[layer.index] = imageContainer
6469
} else if let textLayer = layer as? TextLayerModel {
65-
let textContainer = TextCompositionLayer(textLayer: textLayer, textProvider: textProvider, fontProvider: fontProvider)
70+
let textContainer = TextCompositionLayer(
71+
textLayer: textLayer,
72+
textProvider: textProvider,
73+
fontProvider: fontProvider,
74+
rootAnimationLayer: rootAnimationLayer)
6675
compositionLayers.append(textContainer)
6776
layerMap[layer.index] = textContainer
6877
} else {

Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ final class LayerTextProvider {
1212

1313
// MARK: Lifecycle
1414

15-
init(textProvider: AnimationTextProvider) {
15+
init(textProvider: AnimationKeypathTextProvider) {
1616
self.textProvider = textProvider
1717
textLayers = []
1818
reloadTexts()
@@ -22,7 +22,7 @@ final class LayerTextProvider {
2222

2323
private(set) var textLayers: [TextCompositionLayer]
2424

25-
var textProvider: AnimationTextProvider {
25+
var textProvider: AnimationKeypathTextProvider {
2626
didSet {
2727
reloadTexts()
2828
}

0 commit comments

Comments
 (0)