lottie-ios 4.4.2 → 4.5.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.
Files changed (82) hide show
  1. package/.github/workflows/main.yml +12 -39
  2. package/Lottie.xcodeproj/project.pbxproj +28 -0
  3. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  4. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +19 -6
  5. package/Package.resolved +2 -2
  6. package/Package.swift +1 -1
  7. package/README.md +2 -2
  8. package/Rakefile +1 -1
  9. package/Sources/PrivacyInfo.xcprivacy +1 -1
  10. package/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift +2 -2
  11. package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +6 -6
  12. package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +24 -9
  13. package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +2 -2
  14. package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +7 -7
  15. package/Sources/Private/CoreAnimation/Layers/ShapeItemLayer.swift +12 -12
  16. package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +19 -19
  17. package/Sources/Private/CoreAnimation/ValueProviderStore.swift +4 -4
  18. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift +13 -6
  19. package/Sources/Private/EmbeddedLibraries/LRUCache/LRUCache.swift +3 -3
  20. package/Sources/Private/EmbeddedLibraries/ZipFoundation/Archive+BackingConfiguration.swift +10 -6
  21. package/Sources/Private/EmbeddedLibraries/ZipFoundation/Archive+Helpers.swift +4 -0
  22. package/Sources/Private/EmbeddedLibraries/ZipFoundation/Archive+Progress.swift +2 -2
  23. package/Sources/Private/EmbeddedLibraries/ZipFoundation/Archive+Reading.swift +5 -0
  24. package/Sources/Private/EmbeddedLibraries/ZipFoundation/Archive+Writing.swift +2 -0
  25. package/Sources/Private/EmbeddedLibraries/ZipFoundation/Data+Compression.swift +1 -0
  26. package/Sources/Private/EmbeddedLibraries/ZipFoundation/Entry+ZIP64.swift +2 -2
  27. package/Sources/Private/EmbeddedLibraries/ZipFoundation/Entry.swift +1 -0
  28. package/Sources/Private/EmbeddedLibraries/ZipFoundation/FileManager+ZIP.swift +10 -9
  29. package/Sources/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.swift +19 -0
  30. package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +7 -7
  31. package/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift +3 -0
  32. package/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift +24 -14
  33. package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +11 -10
  34. package/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift +2 -0
  35. package/Sources/Private/MainThread/LayerContainers/Utility/CoreTextRenderLayer.swift +102 -17
  36. package/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.swift +11 -11
  37. package/Sources/Private/MainThread/NodeRenderSystem/Extensions/ItemsExtension.swift +2 -0
  38. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/LayerEffectNodes/DropShadowNode.swift +102 -0
  39. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/LayerEffectNodes/LayerEffectNode.swift +24 -0
  40. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift +4 -4
  41. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift +16 -16
  42. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +4 -4
  43. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +5 -5
  44. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +60 -1
  45. package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +2 -2
  46. package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +9 -6
  47. package/Sources/Private/Model/LayerEffects/EffectValues/EffectValue.swift +4 -4
  48. package/Sources/Private/Model/LayerEffects/LayerEffect.swift +2 -2
  49. package/Sources/Private/Model/LayerStyles/LayerStyle.swift +2 -2
  50. package/Sources/Private/Model/Layers/LayerModel.swift +7 -7
  51. package/Sources/Private/Model/ShapeItems/ShapeItem.swift +15 -15
  52. package/Sources/Private/Model/Text/TextAnimator.swift +47 -1
  53. package/Sources/Private/Utility/Debugging/LayerDebugging.swift +6 -6
  54. package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +16 -16
  55. package/Sources/Private/Utility/Extensions/BlendMode+Filter.swift +16 -16
  56. package/Sources/Private/Utility/Extensions/CGColor+RGB.swift +18 -0
  57. package/Sources/Private/Utility/Extensions/CGFloatExtensions.swift +23 -23
  58. package/Sources/Private/Utility/Extensions/MathKit.swift +4 -11
  59. package/Sources/Private/Utility/Interpolatable/InterpolatableExtensions.swift +9 -3
  60. package/Sources/Private/Utility/LottieAnimationSource.swift +4 -4
  61. package/Sources/Private/Utility/Primitives/BezierPath.swift +16 -2
  62. package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +2 -2
  63. package/Sources/Private/Utility/Primitives/ColorExtension.swift +20 -20
  64. package/Sources/Private/Utility/Primitives/CompoundBezierPath.swift +9 -9
  65. package/Sources/Public/Animation/LottieAnimationLayer.swift +36 -31
  66. package/Sources/Public/Animation/LottieAnimationView.swift +15 -15
  67. package/Sources/Public/Animation/LottieAnimationViewInitializers.swift +4 -0
  68. package/Sources/Public/Animation/LottiePlaybackMode.swift +12 -12
  69. package/Sources/Public/Animation/LottieView.swift +53 -5
  70. package/Sources/Public/Configuration/ReducedMotionOption.swift +5 -5
  71. package/Sources/Public/Configuration/RenderingEngineOption.swift +4 -4
  72. package/Sources/Public/Controls/AnimatedSwitch.swift +2 -2
  73. package/Sources/Public/DotLottie/DotLottieFile.swift +2 -2
  74. package/Sources/Public/DynamicProperties/AnyValueProvider.swift +9 -9
  75. package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +2 -2
  76. package/Sources/Public/Keyframes/Interpolatable.swift +2 -2
  77. package/Sources/Public/Primitives/LottieColor.swift +3 -3
  78. package/Sources/Public/TextProvider/AnimationTextProvider.swift +4 -4
  79. package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +13 -12
  80. package/Version.xcconfig +2 -2
  81. package/lottie-ios.podspec +2 -2
  82. package/package.json +1 -1
@@ -0,0 +1,102 @@
1
+ // Created by Lan Xu on 2024/6/7.
2
+ // Copyright © 2024 Airbnb Inc. All rights reserved.
3
+
4
+ import QuartzCore
5
+
6
+ // MARK: - DropShadowNode
7
+
8
+ final class DropShadowNode: LayerEffectNode {
9
+
10
+ // MARK: Lifecycle
11
+
12
+ init(model: DropShadowModel) {
13
+ properties = DropShadowNodeProperties(model: model)
14
+ }
15
+
16
+ // MARK: Internal
17
+
18
+ var properties: DropShadowNodeProperties
19
+
20
+ var propertyMap: any NodePropertyMap {
21
+ properties
22
+ }
23
+
24
+ func applyEffect(to layer: CALayer) {
25
+ if let opacity = properties.opacity?.value.cgFloatValue {
26
+ // Lottie animation files express opacity as a numerical percentage value
27
+ // (e.g. 0%, 50%, 100%) so we divide by 100 to get the decimal values
28
+ // expected by Core Animation (e.g. 0.0, 0.5, 1.0).
29
+ layer.shadowOpacity = Float(opacity / 100)
30
+ }
31
+ if
32
+ let angleDegrees = properties.angle?.value.cgFloatValue,
33
+ let distance = properties.distance?.value.cgFloatValue
34
+ {
35
+ // Lottie animation files express rotation in degrees
36
+ // (e.g. 90º, 180º, 360º) so we convert to radians to get the
37
+ // values expected by Core Animation (e.g. π/2, π, 2π)
38
+ let angleRadians = (angleDegrees * .pi) / 180
39
+
40
+ // Lottie animation files express the `shadowOffset` as (angle, distance) pair,
41
+ // which we convert to the expected x / y offset values:
42
+ let offsetX = distance * cos(angleRadians)
43
+ let offsetY = distance * sin(angleRadians)
44
+ layer.shadowOffset = .init(width: offsetX, height: offsetY)
45
+ }
46
+ layer.shadowColor = properties.color?.value.cgColorValue
47
+ layer.shadowRadius = properties.radius?.value.cgFloatValue ?? 0
48
+ }
49
+
50
+ }
51
+
52
+ // MARK: - DropShadowNodeProperties
53
+
54
+ final class DropShadowNodeProperties: NodePropertyMap {
55
+
56
+ // MARK: Lifecycle
57
+
58
+ init(model: DropShadowModel) {
59
+ if let opacityKeyframes = model._opacity?.keyframes {
60
+ opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: opacityKeyframes))
61
+ propertyMap[PropertyName.opacity.rawValue] = opacity
62
+ } else {
63
+ opacity = nil
64
+ }
65
+ if let radiusKeyframes = model._radius?.keyframes {
66
+ radius = NodeProperty(provider: KeyframeInterpolator(keyframes: radiusKeyframes))
67
+ propertyMap["Radius"] = radius
68
+ } else {
69
+ radius = nil
70
+ }
71
+ if let colorKeyFrames = model._color?.keyframes {
72
+ color = NodeProperty(provider: KeyframeInterpolator(keyframes: colorKeyFrames))
73
+ propertyMap[PropertyName.color.rawValue] = color
74
+ } else {
75
+ color = nil
76
+ }
77
+ if let angleKeyFrames = model._angle?.keyframes {
78
+ angle = NodeProperty(provider: KeyframeInterpolator(keyframes: angleKeyFrames))
79
+ propertyMap["Angle"] = angle
80
+ } else {
81
+ angle = nil
82
+ }
83
+ if let distanceKeyFrame = model._distance?.keyframes {
84
+ distance = NodeProperty(provider: KeyframeInterpolator(keyframes: distanceKeyFrame))
85
+ propertyMap["Distance"] = distance
86
+ } else {
87
+ distance = nil
88
+ }
89
+ properties = Array(propertyMap.values)
90
+ }
91
+
92
+ // MARK: Internal
93
+
94
+ var propertyMap: [String: AnyNodeProperty] = [:]
95
+ var properties: [AnyNodeProperty]
96
+
97
+ let opacity: NodeProperty<LottieVector1D>?
98
+ let radius: NodeProperty<LottieVector1D>?
99
+ let color: NodeProperty<LottieColor>?
100
+ let angle: NodeProperty<LottieVector1D>?
101
+ let distance: NodeProperty<LottieVector1D>?
102
+ }
@@ -0,0 +1,24 @@
1
+ // Created by Lan Xu on 2024/6/8.
2
+ // Copyright © 2024 Airbnb Inc. All rights reserved.
3
+
4
+ import QuartzCore
5
+
6
+ // MARK: - LayerEffectNode
7
+
8
+ protocol LayerEffectNode {
9
+ func applyEffect(to layer: CALayer)
10
+ var propertyMap: NodePropertyMap { get }
11
+ }
12
+
13
+ extension LayerEffectNode {
14
+
15
+ func updateWithFrame(layer: CALayer, frame: CGFloat) {
16
+ for property in propertyMap.properties {
17
+ if property.needsUpdate(frame: frame) {
18
+ property.update(frame: frame)
19
+ }
20
+ }
21
+ applyEffect(to: layer)
22
+ }
23
+
24
+ }
@@ -11,18 +11,18 @@ extension FillRule {
11
11
  var cgFillRule: CGPathFillRule {
12
12
  switch self {
13
13
  case .evenOdd:
14
- return .evenOdd
14
+ .evenOdd
15
15
  default:
16
- return .winding
16
+ .winding
17
17
  }
18
18
  }
19
19
 
20
20
  var caFillRule: CAShapeLayerFillRule {
21
21
  switch self {
22
22
  case .evenOdd:
23
- return CAShapeLayerFillRule.evenOdd
23
+ CAShapeLayerFillRule.evenOdd
24
24
  default:
25
- return CAShapeLayerFillRule.nonZero
25
+ CAShapeLayerFillRule.nonZero
26
26
  }
27
27
  }
28
28
  }
@@ -11,26 +11,26 @@ extension LineJoin {
11
11
  var cgLineJoin: CGLineJoin {
12
12
  switch self {
13
13
  case .bevel:
14
- return .bevel
14
+ .bevel
15
15
  case .none:
16
- return .miter
16
+ .miter
17
17
  case .miter:
18
- return .miter
18
+ .miter
19
19
  case .round:
20
- return .round
20
+ .round
21
21
  }
22
22
  }
23
23
 
24
24
  var caLineJoin: CAShapeLayerLineJoin {
25
25
  switch self {
26
26
  case .none:
27
- return CAShapeLayerLineJoin.miter
27
+ CAShapeLayerLineJoin.miter
28
28
  case .miter:
29
- return CAShapeLayerLineJoin.miter
29
+ CAShapeLayerLineJoin.miter
30
30
  case .round:
31
- return CAShapeLayerLineJoin.round
31
+ CAShapeLayerLineJoin.round
32
32
  case .bevel:
33
- return CAShapeLayerLineJoin.bevel
33
+ CAShapeLayerLineJoin.bevel
34
34
  }
35
35
  }
36
36
  }
@@ -39,26 +39,26 @@ extension LineCap {
39
39
  var cgLineCap: CGLineCap {
40
40
  switch self {
41
41
  case .none:
42
- return .butt
42
+ .butt
43
43
  case .butt:
44
- return .butt
44
+ .butt
45
45
  case .round:
46
- return .round
46
+ .round
47
47
  case .square:
48
- return .square
48
+ .square
49
49
  }
50
50
  }
51
51
 
52
52
  var caLineCap: CAShapeLayerLineCap {
53
53
  switch self {
54
54
  case .none:
55
- return CAShapeLayerLineCap.butt
55
+ CAShapeLayerLineCap.butt
56
56
  case .butt:
57
- return CAShapeLayerLineCap.butt
57
+ CAShapeLayerLineCap.butt
58
58
  case .round:
59
- return CAShapeLayerLineCap.round
59
+ CAShapeLayerLineCap.round
60
60
  case .square:
61
- return CAShapeLayerLineCap.square
61
+ CAShapeLayerLineCap.square
62
62
  }
63
63
  }
64
64
  }
@@ -122,7 +122,7 @@ extension BezierPath {
122
122
  var vertices = [CurveVertex(point: point + position, inTangentRelative: .zero, outTangentRelative: .zero)]
123
123
 
124
124
  var previousPoint = point
125
- currentAngle += anglePerPoint;
125
+ currentAngle += anglePerPoint
126
126
  for _ in 0..<Int(ceil(numberOfPoints)) {
127
127
  previousPoint = point
128
128
  point = CGPoint(
@@ -131,8 +131,8 @@ extension BezierPath {
131
131
 
132
132
  if outerRoundedness != 0 {
133
133
  let cp1Theta = (atan2(previousPoint.y, previousPoint.x) - CGFloat.pi / 2)
134
- let cp1Dx = cos(cp1Theta);
135
- let cp1Dy = sin(cp1Theta);
134
+ let cp1Dx = cos(cp1Theta)
135
+ let cp1Dy = sin(cp1Theta)
136
136
 
137
137
  let cp2Theta = (atan2(point.y, point.x) - CGFloat.pi / 2)
138
138
  let cp2Dx = cos(cp2Theta)
@@ -154,7 +154,7 @@ extension BezierPath {
154
154
  } else {
155
155
  vertices.append(CurveVertex(point: point + position, inTangentRelative: .zero, outTangentRelative: .zero))
156
156
  }
157
- currentAngle += anglePerPoint;
157
+ currentAngle += anglePerPoint
158
158
  }
159
159
  let reverse = direction == .counterClockwise
160
160
  if reverse {
@@ -169,11 +169,11 @@ extension [DashElement] {
169
169
  }
170
170
 
171
171
  extension [CGFloat] {
172
- // If all of the items in the dash pattern are zeros, then we shouldn't attempt to render it.
173
- // This causes Core Animation to have extremely poor performance for some reason, even though
174
- // it doesn't affect the appearance of the animation.
175
- // - We check for `== 0.01` instead of `== 0` because `dashPattern.shapeLayerConfiguration`
176
- // converts all `0` values to `0.01` to work around a different Core Animation rendering issue.
172
+ /// If all of the items in the dash pattern are zeros, then we shouldn't attempt to render it.
173
+ /// This causes Core Animation to have extremely poor performance for some reason, even though
174
+ /// it doesn't affect the appearance of the animation.
175
+ /// - We check for `== 0.01` instead of `== 0` because `dashPattern.shapeLayerConfiguration`
176
+ /// converts all `0` values to `0.01` to work around a different Core Animation rendering issue.
177
177
  var isSupportedLayerDashPattern: Bool {
178
178
  !allSatisfy { $0 == 0.01 }
179
179
  }
@@ -15,6 +15,7 @@ final class TextAnimatorNodeProperties: NodePropertyMap, KeypathSearchable {
15
15
 
16
16
  init(textAnimator: TextAnimator) {
17
17
  keypathName = textAnimator.name
18
+ textRangeUnit = textAnimator.textRangeUnit
18
19
  var properties = [String : AnyNodeProperty]()
19
20
 
20
21
  if let keyframeGroup = textAnimator.anchor {
@@ -109,6 +110,27 @@ final class TextAnimatorNodeProperties: NodePropertyMap, KeypathSearchable {
109
110
  tracking = nil
110
111
  }
111
112
 
113
+ if let startKeyframes = textAnimator.start {
114
+ start = NodeProperty(provider: KeyframeInterpolator(keyframes: startKeyframes.keyframes))
115
+ properties["Start"] = start
116
+ } else {
117
+ start = nil
118
+ }
119
+
120
+ if let endKeyframes = textAnimator.end {
121
+ end = NodeProperty(provider: KeyframeInterpolator(keyframes: endKeyframes.keyframes))
122
+ properties["End"] = end
123
+ } else {
124
+ end = nil
125
+ }
126
+
127
+ if let selectedRangeOpacityKeyframes = textAnimator.opacity {
128
+ selectedRangeOpacity = NodeProperty(provider: KeyframeInterpolator(keyframes: selectedRangeOpacityKeyframes.keyframes))
129
+ properties["SelectedRangeOpacity"] = selectedRangeOpacity
130
+ } else {
131
+ selectedRangeOpacity = nil
132
+ }
133
+
112
134
  keypathProperties = properties
113
135
 
114
136
  self.properties = Array(keypathProperties.values)
@@ -131,6 +153,10 @@ final class TextAnimatorNodeProperties: NodePropertyMap, KeypathSearchable {
131
153
  let fillColor: NodeProperty<LottieColor>?
132
154
  let strokeWidth: NodeProperty<LottieVector1D>?
133
155
  let tracking: NodeProperty<LottieVector1D>?
156
+ let start: NodeProperty<LottieVector1D>?
157
+ let end: NodeProperty<LottieVector1D>?
158
+ let selectedRangeOpacity: NodeProperty<LottieVector1D>?
159
+ let textRangeUnit: TextRangeUnit?
134
160
 
135
161
  let keypathProperties: [String: AnyNodeProperty]
136
162
  let properties: [AnyNodeProperty]
@@ -223,6 +249,33 @@ final class TextOutputNode: NodeOutput {
223
249
  }
224
250
  }
225
251
 
252
+ var start: CGFloat? {
253
+ get {
254
+ _start
255
+ }
256
+ set {
257
+ _start = newValue
258
+ }
259
+ }
260
+
261
+ var end: CGFloat? {
262
+ get {
263
+ _end
264
+ }
265
+ set {
266
+ _end = newValue
267
+ }
268
+ }
269
+
270
+ var selectedRangeOpacity: CGFloat? {
271
+ get {
272
+ _selectedRangeOpacity
273
+ }
274
+ set {
275
+ _selectedRangeOpacity = newValue
276
+ }
277
+ }
278
+
226
279
  func hasOutputUpdates(_: CGFloat) -> Bool {
227
280
  // TODO Fix This
228
281
  true
@@ -236,6 +289,9 @@ final class TextOutputNode: NodeOutput {
236
289
  fileprivate var _fillColor: CGColor?
237
290
  fileprivate var _tracking: CGFloat?
238
291
  fileprivate var _strokeWidth: CGFloat?
292
+ fileprivate var _start: CGFloat?
293
+ fileprivate var _end: CGFloat?
294
+ fileprivate var _selectedRangeOpacity: CGFloat?
239
295
  }
240
296
 
241
297
  // MARK: - TextAnimatorNode
@@ -278,10 +334,13 @@ class TextAnimatorNode: AnimatorNode {
278
334
 
279
335
  func rebuildOutputs(frame _: CGFloat) {
280
336
  textOutputNode.xform = textAnimatorProperties.caTransform
281
- textOutputNode.opacity = (textAnimatorProperties.opacity?.value.cgFloatValue ?? 100) * 0.01
337
+ textOutputNode.opacity = 1.0
282
338
  textOutputNode.strokeColor = textAnimatorProperties.strokeColor?.value.cgColorValue
283
339
  textOutputNode.fillColor = textAnimatorProperties.fillColor?.value.cgColorValue
284
340
  textOutputNode.tracking = textAnimatorProperties.tracking?.value.cgFloatValue ?? 1
285
341
  textOutputNode.strokeWidth = textAnimatorProperties.strokeWidth?.value.cgFloatValue ?? 0
342
+ textOutputNode.start = textAnimatorProperties.start?.value.cgFloatValue
343
+ textOutputNode.end = textAnimatorProperties.end?.value.cgFloatValue
344
+ textOutputNode.selectedRangeOpacity = (textAnimatorProperties.opacity?.value.cgFloatValue).flatMap { $0 * 0.01 }
286
345
  }
287
346
  }
@@ -29,9 +29,9 @@ struct DotLottieAnimation: Codable {
29
29
  var loopMode: LottieLoopMode {
30
30
  switch mode {
31
31
  case .bounce:
32
- return .autoReverse
32
+ .autoReverse
33
33
  case .normal, nil:
34
- return (loop ?? false) ? .loop : .playOnce
34
+ (loop ?? false) ? .loop : .playOnce
35
35
  }
36
36
  }
37
37
 
@@ -151,12 +151,15 @@ extension KeyframeGroup: DictionaryInitializable where T: AnyInitializable {
151
151
  {
152
152
  keyframes = [Keyframe<T>(value)]
153
153
  } else {
154
- var frameDictionaries: [[String: Any]]
155
- if let singleFrameDictionary = dictionary[KeyframeWrapperKey.keyframeData.rawValue] as? [String: Any] {
156
- frameDictionaries = [singleFrameDictionary]
157
- } else {
158
- frameDictionaries = try dictionary.value(for: KeyframeWrapperKey.keyframeData)
159
- }
154
+ let frameDictionaries: [[String: Any]] =
155
+ if
156
+ let singleFrameDictionary =
157
+ dictionary[KeyframeWrapperKey.keyframeData.rawValue] as? [String: Any]
158
+ {
159
+ [singleFrameDictionary]
160
+ } else {
161
+ try dictionary.value(for: KeyframeWrapperKey.keyframeData)
162
+ }
160
163
  var previousKeyframeData: KeyframeData<T>?
161
164
  for frameDictionary in frameDictionaries {
162
165
  let data = try KeyframeData<T>(dictionary: frameDictionary)
@@ -23,14 +23,14 @@ extension EffectValueType: ClassFamily {
23
23
  func getType() -> AnyObject.Type {
24
24
  switch self {
25
25
  case .slider:
26
- return Vector1DEffectValue.self
26
+ Vector1DEffectValue.self
27
27
  case .angle:
28
- return Vector1DEffectValue.self
28
+ Vector1DEffectValue.self
29
29
  case .color:
30
- return ColorEffectValue.self
30
+ ColorEffectValue.self
31
31
  case .unknown:
32
32
  // Unsupported
33
- return LayerEffect.self
33
+ LayerEffect.self
34
34
  }
35
35
  }
36
36
  }
@@ -21,10 +21,10 @@ extension LayerEffectType: ClassFamily {
21
21
  func getType() -> AnyObject.Type {
22
22
  switch self {
23
23
  case .dropShadow:
24
- return DropShadowEffect.self
24
+ DropShadowEffect.self
25
25
  case .unknown:
26
26
  // Unsupported
27
- return LayerEffect.self
27
+ LayerEffect.self
28
28
  }
29
29
  }
30
30
  }
@@ -20,10 +20,10 @@ extension LayerStyleType: ClassFamily {
20
20
  func getType() -> AnyObject.Type {
21
21
  switch self {
22
22
  case .dropShadow:
23
- return DropShadowStyle.self
23
+ DropShadowStyle.self
24
24
  case .unknown:
25
25
  // Unsupported
26
- return LayerStyle.self
26
+ LayerStyle.self
27
27
  }
28
28
  }
29
29
  }
@@ -14,19 +14,19 @@ extension LayerType: ClassFamily {
14
14
  func getType() -> AnyObject.Type {
15
15
  switch self {
16
16
  case .precomp:
17
- return PreCompLayerModel.self
17
+ PreCompLayerModel.self
18
18
  case .solid:
19
- return SolidLayerModel.self
19
+ SolidLayerModel.self
20
20
  case .image:
21
- return ImageLayerModel.self
21
+ ImageLayerModel.self
22
22
  case .null:
23
- return LayerModel.self
23
+ LayerModel.self
24
24
  case .shape:
25
- return ShapeLayerModel.self
25
+ ShapeLayerModel.self
26
26
  case .text:
27
- return TextLayerModel.self
27
+ TextLayerModel.self
28
28
  case .unknown:
29
- return LayerModel.self
29
+ LayerModel.self
30
30
  }
31
31
  }
32
32
  }
@@ -38,35 +38,35 @@ extension ShapeType: ClassFamily {
38
38
  func getType() -> AnyObject.Type {
39
39
  switch self {
40
40
  case .ellipse:
41
- return Ellipse.self
41
+ Ellipse.self
42
42
  case .fill:
43
- return Fill.self
43
+ Fill.self
44
44
  case .gradientFill:
45
- return GradientFill.self
45
+ GradientFill.self
46
46
  case .group:
47
- return Group.self
47
+ Group.self
48
48
  case .gradientStroke:
49
- return GradientStroke.self
49
+ GradientStroke.self
50
50
  case .merge:
51
- return Merge.self
51
+ Merge.self
52
52
  case .rectangle:
53
- return Rectangle.self
53
+ Rectangle.self
54
54
  case .repeater:
55
- return Repeater.self
55
+ Repeater.self
56
56
  case .round:
57
- return RoundedCorners.self
57
+ RoundedCorners.self
58
58
  case .shape:
59
- return Shape.self
59
+ Shape.self
60
60
  case .star:
61
- return Star.self
61
+ Star.self
62
62
  case .stroke:
63
- return Stroke.self
63
+ Stroke.self
64
64
  case .trim:
65
- return Trim.self
65
+ Trim.self
66
66
  case .transform:
67
- return ShapeTransform.self
67
+ ShapeTransform.self
68
68
  default:
69
- return ShapeItem.self
69
+ ShapeItem.self
70
70
  }
71
71
  }
72
72
  }
@@ -5,6 +5,15 @@
5
5
  // Created by Brandon Withrow on 1/9/19.
6
6
  //
7
7
 
8
+ // MARK: - TextRangeUnit
9
+
10
+ enum TextRangeUnit: Int, RawRepresentable, Codable {
11
+ case percentage = 1
12
+ case index = 2
13
+ }
14
+
15
+ // MARK: - TextAnimator
16
+
8
17
  final class TextAnimator: Codable, DictionaryInitializable {
9
18
 
10
19
  // MARK: Lifecycle
@@ -12,6 +21,7 @@ final class TextAnimator: Codable, DictionaryInitializable {
12
21
  required init(from decoder: Decoder) throws {
13
22
  let container = try decoder.container(keyedBy: TextAnimator.CodingKeys.self)
14
23
  name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
24
+
15
25
  let animatorContainer = try container.nestedContainer(keyedBy: TextAnimatorKeys.self, forKey: .textAnimator)
16
26
  fillColor = try animatorContainer.decodeIfPresent(KeyframeGroup<LottieColor>.self, forKey: .fillColor)
17
27
  strokeColor = try animatorContainer.decodeIfPresent(KeyframeGroup<LottieColor>.self, forKey: .strokeColor)
@@ -32,10 +42,16 @@ final class TextAnimator: Codable, DictionaryInitializable {
32
42
  rotationZ = nil
33
43
  }
34
44
  opacity = try animatorContainer.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .opacity)
45
+
46
+ let selectorContainer = try? container.nestedContainer(keyedBy: TextSelectorKeys.self, forKey: .textSelector)
47
+ start = try? selectorContainer?.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .start)
48
+ end = try? selectorContainer?.decodeIfPresent(KeyframeGroup<LottieVector1D>.self, forKey: .end)
49
+ textRangeUnit = try? selectorContainer?.decodeIfPresent(TextRangeUnit.self, forKey: .textRangeUnits)
35
50
  }
36
51
 
37
52
  init(dictionary: [String: Any]) throws {
38
53
  name = (try? dictionary.value(for: CodingKeys.name)) ?? ""
54
+
39
55
  let animatorDictionary: [String: Any] = try dictionary.value(for: CodingKeys.textAnimator)
40
56
  if let fillColorDictionary = animatorDictionary[TextAnimatorKeys.fillColor.rawValue] as? [String: Any] {
41
57
  fillColor = try? KeyframeGroup<LottieColor>(dictionary: fillColorDictionary)
@@ -107,6 +123,26 @@ final class TextAnimator: Codable, DictionaryInitializable {
107
123
  } else {
108
124
  opacity = nil
109
125
  }
126
+
127
+ let selectorDictionary: [String: Any] = try dictionary.value(for: CodingKeys.textSelector)
128
+
129
+ if let startDictionary = selectorDictionary[TextSelectorKeys.start.rawValue] as? [String: Any] {
130
+ start = try KeyframeGroup<LottieVector1D>(dictionary: startDictionary)
131
+ } else {
132
+ start = nil
133
+ }
134
+
135
+ if let endDictionary = selectorDictionary[TextSelectorKeys.end.rawValue] as? [String: Any] {
136
+ end = try KeyframeGroup<LottieVector1D>(dictionary: endDictionary)
137
+ } else {
138
+ end = nil
139
+ }
140
+
141
+ if let textRangeUnitValue = selectorDictionary[TextSelectorKeys.textRangeUnits.rawValue] as? Int {
142
+ textRangeUnit = TextRangeUnit(rawValue: textRangeUnitValue)
143
+ } else {
144
+ textRangeUnit = nil
145
+ }
110
146
  }
111
147
 
112
148
  // MARK: Internal
@@ -152,6 +188,15 @@ final class TextAnimator: Codable, DictionaryInitializable {
152
188
  /// Tracking
153
189
  let tracking: KeyframeGroup<LottieVector1D>?
154
190
 
191
+ /// Start
192
+ let start: KeyframeGroup<LottieVector1D>?
193
+
194
+ /// End
195
+ let end: KeyframeGroup<LottieVector1D>?
196
+
197
+ /// The type of unit used by the start/end ranges
198
+ let textRangeUnit: TextRangeUnit?
199
+
155
200
  func encode(to encoder: Encoder) throws {
156
201
  var container = encoder.container(keyedBy: CodingKeys.self)
157
202
  var animatorContainer = container.nestedContainer(keyedBy: TextAnimatorKeys.self, forKey: .textAnimator)
@@ -164,7 +209,7 @@ final class TextAnimator: Codable, DictionaryInitializable {
164
209
  // MARK: Private
165
210
 
166
211
  private enum CodingKeys: String, CodingKey {
167
- // case textSelector = "s" TODO
212
+ case textSelector = "s"
168
213
  case textAnimator = "a"
169
214
  case name = "nm"
170
215
  }
@@ -173,6 +218,7 @@ final class TextAnimator: Codable, DictionaryInitializable {
173
218
  case start = "s"
174
219
  case end = "e"
175
220
  case offset = "o"
221
+ case textRangeUnits = "r"
176
222
  }
177
223
 
178
224
  private enum TextAnimatorKeys: String, CodingKey {
@@ -95,12 +95,12 @@ extension CALayer {
95
95
  }
96
96
 
97
97
  if visible {
98
- let style: LayerDebugStyle
99
- if let layerDebugging = self as? LayerDebugging {
100
- style = layerDebugging.debugStyle
101
- } else {
102
- style = LayerDebugStyle.defaultStyle()
103
- }
98
+ let style: LayerDebugStyle =
99
+ if let layerDebugging = self as? LayerDebugging {
100
+ layerDebugging.debugStyle
101
+ } else {
102
+ LayerDebugStyle.defaultStyle()
103
+ }
104
104
  let debugLayer = DebugLayer(style: style)
105
105
  var container = self
106
106
  if let cust = self as? CustomLayerDebugging {