lottie-ios 4.1.1 → 4.1.3

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 (26) hide show
  1. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  2. package/README.md +4 -4
  3. package/Rakefile +5 -1
  4. package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +9 -2
  5. package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +58 -9
  6. package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +16 -12
  7. package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +19 -4
  8. package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +2 -2
  9. package/Sources/Private/CoreAnimation/Layers/PreCompLayer.swift +1 -1
  10. package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +13 -2
  11. package/Sources/Private/MainThread/LayerContainers/Utility/LayerTransformNode.swift +13 -4
  12. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +17 -5
  13. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +25 -6
  14. package/Sources/Private/Model/Objects/Transform.swift +58 -17
  15. package/Sources/Private/Model/ShapeItems/Repeater.swift +41 -7
  16. package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +61 -7
  17. package/Sources/Private/Model/Text/TextAnimator.swift +37 -5
  18. package/Sources/Private/Utility/Primitives/ColorExtension.swift +10 -13
  19. package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +28 -6
  20. package/Sources/Public/Animation/LottieAnimationView.swift +161 -159
  21. package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +9 -3
  22. package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +32 -0
  23. package/Sources/Public/iOS/LottieAnimationViewBase.swift +1 -1
  24. package/Sources/Public/macOS/LottieAnimationViewBase.macOS.swift +1 -1
  25. package/lottie-ios.podspec +1 -1
  26. package/package.json +1 -1
package/README.md CHANGED
@@ -8,7 +8,7 @@ Lottie is a cross-platform library for iOS, macOS, tvOS, [Android](https://githu
8
8
  Lottie loads and renders animations and vectors exported in the bodymovin JSON format. Bodymovin JSON can be created and exported from After Effects with [bodymovin](https://github.com/bodymovin/bodymovin), Sketch with [Lottie Sketch Export](https://github.com/buba447/Lottie-Sketch-Export), and from [Haiku](https://www.haiku.ai).
9
9
 
10
10
  Designers can create **and ship** beautiful animations without an engineer painstakingly recreating them by hand.
11
- Since the animation is backed by JSON they are extremely small in size but can be large in complexity!
11
+ Since the animations are backed by JSON, they are extremely small in size but can be large in complexity!
12
12
  Animations can be played, resized, looped, sped up, slowed down, reversed, and even interactively scrubbed.
13
13
  Lottie can play or loop just a portion of the animation as well, the possibilities are endless!
14
14
  Animations can even be ***changed at runtime*** in various ways! Change the color, position, or any keyframable value!
@@ -41,10 +41,10 @@ To install Lottie using [Swift Package Manager](https://github.com/apple/swift-p
41
41
  or you can add the following dependency to your `Package.swift`:
42
42
 
43
43
  ```swift
44
- .package(url: "https://github.com/airbnb/lottie-spm.git", from: "4.1.1")
44
+ .package(url: "https://github.com/airbnb/lottie-spm.git", from: "4.1.3")
45
45
  ```
46
46
 
47
- When using Swift Package Manager we recommend using the [lottie-spm](https://github.com/airbnb/lottie-spm) repo instead of the main lottie-ios repo. The main git repository for [lottie-ios](https://github.com/airbnb/lottie-ios) is somewhat large (300+ MB), and Swift Package Manager always downloads the full repository with all git hsitory. The [lottie-spm](https://github.com/airbnb/lottie-spm) repo is much smaller (less than 500kb), so can be downloaded much more quickly.
47
+ When using Swift Package Manager we recommend using the [lottie-spm](https://github.com/airbnb/lottie-spm) repo instead of the main lottie-ios repo. The main git repository for [lottie-ios](https://github.com/airbnb/lottie-ios) is somewhat large (300+ MB), and Swift Package Manager always downloads the full repository with all git history. The [lottie-spm](https://github.com/airbnb/lottie-spm) repo is much smaller (less than 500kb), so can be downloaded much more quickly.
48
48
 
49
49
  Instead of downloading the full git history of Lottie and building it from source, the lottie-spm repo just contains a pointer to the precompiled XCFramework included in the [latest lottie-ios release](https://github.com/airbnb/lottie-ios/releases/latest) (typically ~8MB). If you prefer to include Lottie source directly your project, you can directly depend on the main lottie-ios repo by referencing `https://github.com/airbnb/lottie-ios.git` instead.
50
50
 
@@ -88,7 +88,7 @@ We always appreciate contributions from the community. To make changes to the pr
88
88
 
89
89
  All pull requests with new features or bug fixes that affect how animations render should include snapshot test cases that validate the included changes.
90
90
  - To add a new sample animation to the snapshot testing suite, you can add the `.json` file to `Tests/Samples`. Re-run the snapshot tests to generate the new snapshot image files.
91
- - To update existing snapshots after making changes, you can set `isRecording = true` in `SnapshotTests.swift` and then re-run the snapshot tests.
91
+ - To update existing snapshots after making changes, you can set `isRecording = true` in `SnapshotTests.swift` `setUp()` method and then re-run the snapshot tests.
92
92
 
93
93
  The project also includes several helpful commands defined in our [Rakefile](https://github.com/airbnb/lottie-ios/blob/master/Rakefile). To use these, you need to install [Bundler](https://bundler.io/):
94
94
 
package/Rakefile CHANGED
@@ -49,6 +49,7 @@ namespace :build do
49
49
  sh 'rm -rf .build/archives'
50
50
  xcodebuild('archive -workspace Lottie.xcworkspace -scheme "Lottie (iOS)" -destination generic/platform=iOS -archivePath ".build/archives/Lottie_iOS" SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES')
51
51
  xcodebuild('archive -workspace Lottie.xcworkspace -scheme "Lottie (iOS)" -destination "generic/platform=iOS Simulator" -archivePath ".build/archives/Lottie_iOS_Simulator" SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES')
52
+ xcodebuild('archive -workspace Lottie.xcworkspace -scheme "Lottie (iOS)" -destination "generic/platform=macOS,variant=Mac Catalyst" -archivePath ".build/archives/Lottie_Mac_Catalyst" SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES')
52
53
  xcodebuild('archive -workspace Lottie.xcworkspace -scheme "Lottie (macOS)" -destination generic/platform=macOS -archivePath ".build/archives/Lottie_macOS" SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES')
53
54
  xcodebuild('archive -workspace Lottie.xcworkspace -scheme "Lottie (tvOS)" -destination generic/platform=tvOS -archivePath ".build/archives/Lottie_tvOS" SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES')
54
55
  xcodebuild('archive -workspace Lottie.xcworkspace -scheme "Lottie (tvOS)" -destination "generic/platform=tvOS Simulator" -archivePath ".build/archives/Lottie_tvOS_Simulator" SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES')
@@ -57,13 +58,16 @@ namespace :build do
57
58
  '-create-xcframework',
58
59
  '-framework .build/archives/Lottie_iOS.xcarchive/Products/Library/Frameworks/Lottie.framework',
59
60
  '-framework .build/archives/Lottie_iOS_Simulator.xcarchive/Products/Library/Frameworks/Lottie.framework',
61
+ '-framework .build/archives/Lottie_Mac_Catalyst.xcarchive/Products/Library/Frameworks/Lottie.framework',
60
62
  '-framework .build/archives/Lottie_macOS.xcarchive/Products/Library/Frameworks/Lottie.framework',
61
63
  '-framework .build/archives/Lottie_tvOS.xcarchive/Products/Library/Frameworks/Lottie.framework',
62
64
  '-framework .build/archives/Lottie_tvOS_Simulator.xcarchive/Products/Library/Frameworks/Lottie.framework',
63
65
  '-output .build/archives/Lottie.xcframework'
64
66
  ].join(" "))
65
67
  Dir.chdir('.build/archives') do
66
- sh 'zip -r Lottie.xcframework.zip Lottie.xcframework'
68
+ # Use --symlinks to avoid "Multiple binaries share the same codesign path. This can happen if your build process copies frameworks by following symlinks."
69
+ # error when validating macOS apps (#1948)
70
+ sh 'zip -r --symlinks Lottie.xcframework.zip Lottie.xcframework'
67
71
  sh 'rm -rf Lottie.xcframework'
68
72
  end
69
73
  sh 'swift package compute-checksum .build/archives/Lottie.xcframework.zip'
@@ -109,9 +109,9 @@ extension LayerProperty {
109
109
  customizableProperty: nil /* currently unsupported */ )
110
110
  }
111
111
 
112
- static var rotation: LayerProperty<CGFloat> {
112
+ static var rotationX: LayerProperty<CGFloat> {
113
113
  .init(
114
- caLayerKeypath: "transform.rotation",
114
+ caLayerKeypath: "transform.rotation.x",
115
115
  defaultValue: 0,
116
116
  customizableProperty: nil /* currently unsupported */ )
117
117
  }
@@ -123,6 +123,13 @@ extension LayerProperty {
123
123
  customizableProperty: nil /* currently unsupported */ )
124
124
  }
125
125
 
126
+ static var rotationZ: LayerProperty<CGFloat> {
127
+ .init(
128
+ caLayerKeypath: "transform.rotation.z",
129
+ defaultValue: 0,
130
+ customizableProperty: nil /* currently unsupported */ )
131
+ }
132
+
126
133
  static var anchorPoint: LayerProperty<CGPoint> {
127
134
  .init(
128
135
  caLayerKeypath: #keyPath(CALayer.anchorPoint),
@@ -24,8 +24,14 @@ protocol TransformModel {
24
24
  /// The scale of the transform
25
25
  var scale: KeyframeGroup<LottieVector3D> { get }
26
26
 
27
- /// The rotation of the transform. Note: This is single dimensional rotation.
28
- var rotation: KeyframeGroup<LottieVector1D> { get }
27
+ /// The rotation of the transform on X axis.
28
+ var rotationX: KeyframeGroup<LottieVector1D> { get }
29
+
30
+ /// The rotation of the transform on Y axis.
31
+ var rotationY: KeyframeGroup<LottieVector1D> { get }
32
+
33
+ /// The rotation of the transform on Z axis.
34
+ var rotationZ: KeyframeGroup<LottieVector1D> { get }
29
35
  }
30
36
 
31
37
  // MARK: - Transform + TransformModel
@@ -75,7 +81,7 @@ extension CALayer {
75
81
  try addPositionAnimations(from: transformModel, context: context)
76
82
  try addAnchorPointAnimation(from: transformModel, context: context)
77
83
  try addScaleAnimations(from: transformModel, context: context)
78
- try addRotationAnimation(from: transformModel, context: context)
84
+ try addRotationAnimations(from: transformModel, context: context)
79
85
  }
80
86
  }
81
87
 
@@ -227,14 +233,53 @@ extension CALayer {
227
233
  context: context)
228
234
  }
229
235
 
230
- private func addRotationAnimation(
236
+ private func addRotationAnimations(
231
237
  from transformModel: TransformModel,
232
238
  context: LayerAnimationContext)
233
239
  throws
234
240
  {
241
+ let containsXRotationValues = transformModel.rotationX.keyframes.contains(where: { $0.value.cgFloatValue != 0 })
242
+ let containsYRotationValues = transformModel.rotationY.keyframes.contains(where: { $0.value.cgFloatValue != 0 })
243
+
244
+ // When `rotation.x` or `rotation.y` is used, it doesn't render property in test snapshots
245
+ // but do renders correctly on the simulator / device
246
+ if TestHelpers.snapshotTestsAreRunning {
247
+ if containsXRotationValues {
248
+ context.logger.warn("""
249
+ `rotation.x` values are not displayed correctly in snapshot tests
250
+ """)
251
+ }
252
+
253
+ if containsYRotationValues {
254
+ context.logger.warn("""
255
+ `rotation.y` values are not displayed correctly in snapshot tests
256
+ """)
257
+ }
258
+ }
259
+
260
+ // Lottie animation files express rotation in degrees
261
+ // (e.g. 90º, 180º, 360º) so we covert to radians to get the
262
+ // values expected by Core Animation (e.g. π/2, π, 2π)
263
+
264
+ try addAnimation(
265
+ for: .rotationX,
266
+ keyframes: transformModel.rotationX.keyframes,
267
+ value: { rotationDegrees in
268
+ rotationDegrees.cgFloatValue * .pi / 180
269
+ },
270
+ context: context)
271
+
272
+ try addAnimation(
273
+ for: .rotationY,
274
+ keyframes: transformModel.rotationY.keyframes,
275
+ value: { rotationDegrees in
276
+ rotationDegrees.cgFloatValue * .pi / 180
277
+ },
278
+ context: context)
279
+
235
280
  try addAnimation(
236
- for: .rotation,
237
- keyframes: transformModel.rotation.keyframes,
281
+ for: .rotationZ,
282
+ keyframes: transformModel.rotationZ.keyframes,
238
283
  value: { rotationDegrees in
239
284
  // Lottie animation files express rotation in degrees
240
285
  // (e.g. 90º, 180º, 360º) so we covert to radians to get the
@@ -257,15 +302,19 @@ extension CALayer {
257
302
  transformModel.anchor,
258
303
  transformModel.position,
259
304
  transformModel.scale,
260
- transformModel.rotation,
305
+ transformModel.rotationX,
306
+ transformModel.rotationY,
307
+ transformModel.rotationZ,
261
308
  transformModel.skew,
262
309
  transformModel.skewAxis,
263
- makeCombinedResult: { anchor, position, scale, rotation, skew, skewAxis in
310
+ makeCombinedResult: { anchor, position, scale, rotationX, rotationY, rotationZ, skew, skewAxis in
264
311
  CATransform3D.makeTransform(
265
312
  anchor: anchor.pointValue,
266
313
  position: position.pointValue,
267
314
  scale: scale.sizeValue,
268
- rotation: rotation.cgFloatValue,
315
+ rotationX: rotationX.cgFloatValue,
316
+ rotationY: rotationY.cgFloatValue,
317
+ rotationZ: rotationZ.cgFloatValue,
269
318
  skew: skew.cgFloatValue,
270
319
  skewAxis: skewAxis.cgFloatValue)
271
320
  })
@@ -68,20 +68,21 @@ enum Keyframes {
68
68
  /// Combines the given keyframe groups of `Keyframe<T>`s into a single keyframe group of of `Keyframe<[T]>`s
69
69
  /// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged
70
70
  /// - Otherwise, the keyframes are manually interpolated at each frame in the animation
71
- static func combined<T1, T2, T3, T4, T5, T6, CombinedResult>(
71
+ static func combined<T1, T2, T3, T4, T5, T6, T7, CombinedResult>(
72
72
  _ k1: KeyframeGroup<T1>,
73
73
  _ k2: KeyframeGroup<T2>,
74
74
  _ k3: KeyframeGroup<T3>,
75
75
  _ k4: KeyframeGroup<T4>,
76
76
  _ k5: KeyframeGroup<T5>,
77
77
  _ k6: KeyframeGroup<T6>,
78
- makeCombinedResult: (T1, T2, T3, T4, T5, T6) -> CombinedResult)
78
+ _ k7: KeyframeGroup<T7>,
79
+ makeCombinedResult: (T1, T2, T3, T4, T5, T6, T7) -> CombinedResult)
79
80
  -> KeyframeGroup<CombinedResult>
80
81
  where T1: AnyInterpolatable, T2: AnyInterpolatable, T3: AnyInterpolatable, T4: AnyInterpolatable,
81
- T5: AnyInterpolatable, T6: AnyInterpolatable
82
+ T5: AnyInterpolatable, T6: AnyInterpolatable, T7: AnyInterpolatable
82
83
  {
83
84
  Keyframes.combined(
84
- [k1, k2, k3, k4, k5, k6],
85
+ [k1, k2, k3, k4, k5, k6, k7],
85
86
  makeCombinedResult: { untypedValues in
86
87
  guard
87
88
  let t1 = untypedValues[0] as? T1,
@@ -89,17 +90,18 @@ enum Keyframes {
89
90
  let t3 = untypedValues[2] as? T3,
90
91
  let t4 = untypedValues[3] as? T4,
91
92
  let t5 = untypedValues[4] as? T5,
92
- let t6 = untypedValues[5] as? T6
93
+ let t6 = untypedValues[5] as? T6,
94
+ let t7 = untypedValues[6] as? T7
93
95
  else { return nil }
94
96
 
95
- return makeCombinedResult(t1, t2, t3, t4, t5, t6)
97
+ return makeCombinedResult(t1, t2, t3, t4, t5, t6, t7)
96
98
  })
97
99
  }
98
100
 
99
101
  /// Combines the given keyframe groups of `Keyframe<T>`s into a single keyframe group of of `Keyframe<[T]>`s
100
102
  /// - If all of the `KeyframeGroup`s have the exact same animation timing, the keyframes are merged
101
103
  /// - Otherwise, the keyframes are manually interpolated at each frame in the animation
102
- static func combined<T1, T2, T3, T4, T5, T6, T7, CombinedResult>(
104
+ static func combined<T1, T2, T3, T4, T5, T6, T7, T8, CombinedResult>(
103
105
  _ k1: KeyframeGroup<T1>,
104
106
  _ k2: KeyframeGroup<T2>,
105
107
  _ k3: KeyframeGroup<T3>,
@@ -107,13 +109,14 @@ enum Keyframes {
107
109
  _ k5: KeyframeGroup<T5>,
108
110
  _ k6: KeyframeGroup<T6>,
109
111
  _ k7: KeyframeGroup<T7>,
110
- makeCombinedResult: (T1, T2, T3, T4, T5, T6, T7) -> CombinedResult)
112
+ _ k8: KeyframeGroup<T8>,
113
+ makeCombinedResult: (T1, T2, T3, T4, T5, T6, T7, T8) -> CombinedResult)
111
114
  -> KeyframeGroup<CombinedResult>
112
115
  where T1: AnyInterpolatable, T2: AnyInterpolatable, T3: AnyInterpolatable, T4: AnyInterpolatable,
113
- T5: AnyInterpolatable, T6: AnyInterpolatable, T7: AnyInterpolatable
116
+ T5: AnyInterpolatable, T6: AnyInterpolatable, T7: AnyInterpolatable, T8: AnyInterpolatable
114
117
  {
115
118
  Keyframes.combined(
116
- [k1, k2, k3, k4, k5, k6, k7],
119
+ [k1, k2, k3, k4, k5, k6, k7, k8],
117
120
  makeCombinedResult: { untypedValues in
118
121
  guard
119
122
  let t1 = untypedValues[0] as? T1,
@@ -122,10 +125,11 @@ enum Keyframes {
122
125
  let t4 = untypedValues[3] as? T4,
123
126
  let t5 = untypedValues[4] as? T5,
124
127
  let t6 = untypedValues[5] as? T6,
125
- let t7 = untypedValues[6] as? T7
128
+ let t7 = untypedValues[6] as? T7,
129
+ let t8 = untypedValues[7] as? T8
126
130
  else { return nil }
127
131
 
128
- return makeCombinedResult(t1, t2, t3, t4, t5, t6, t7)
132
+ return makeCombinedResult(t1, t2, t3, t4, t5, t6, t7, t8)
129
133
  })
130
134
  }
131
135
 
@@ -17,6 +17,7 @@ class BaseCompositionLayer: BaseAnimationLayer {
17
17
  setupSublayers()
18
18
  compositingFilter = layerModel.blendMode.filterName
19
19
  name = layerModel.name
20
+ contentsLayer.name = "\(layerModel.name) (Content)"
20
21
  }
21
22
 
22
23
  required init?(coder _: NSCoder) {
@@ -36,6 +37,10 @@ class BaseCompositionLayer: BaseAnimationLayer {
36
37
 
37
38
  // MARK: Internal
38
39
 
40
+ /// The layer that content / sublayers should be rendered in.
41
+ /// This is the layer that transform animations are applied to.
42
+ let contentsLayer = BaseAnimationLayer()
43
+
39
44
  /// Whether or not this layer render should render any visible content
40
45
  var renderLayerContents: Bool { true }
41
46
 
@@ -55,12 +60,12 @@ class BaseCompositionLayer: BaseAnimationLayer {
55
60
  func setupLayerAnimations(context: LayerAnimationContext) throws {
56
61
  let context = context.addingKeypathComponent(baseLayerModel.name)
57
62
 
58
- try addTransformAnimations(for: baseLayerModel.transform, context: context)
63
+ try contentsLayer.addTransformAnimations(for: baseLayerModel.transform, context: context)
59
64
 
60
65
  if renderLayerContents {
61
- try addOpacityAnimation(for: baseLayerModel.transform, context: context)
66
+ try contentsLayer.addOpacityAnimation(for: baseLayerModel.transform, context: context)
62
67
 
63
- addVisibilityAnimation(
68
+ contentsLayer.addVisibilityAnimation(
64
69
  inFrame: CGFloat(baseLayerModel.inFrame),
65
70
  outFrame: CGFloat(baseLayerModel.outFrame),
66
71
  context: context)
@@ -71,17 +76,27 @@ class BaseCompositionLayer: BaseAnimationLayer {
71
76
  try super.setupAnimations(context: context)
72
77
  }
73
78
 
79
+ override func addSublayer(_ layer: CALayer) {
80
+ if layer === contentsLayer {
81
+ super.addSublayer(contentsLayer)
82
+ } else {
83
+ contentsLayer.addSublayer(layer)
84
+ }
85
+ }
86
+
74
87
  // MARK: Private
75
88
 
76
89
  private let baseLayerModel: LayerModel
77
90
 
78
91
  private func setupSublayers() {
92
+ addSublayer(contentsLayer)
93
+
79
94
  if
80
95
  renderLayerContents,
81
96
  let masks = baseLayerModel.masks?.filter({ $0.mode != .none }),
82
97
  !masks.isEmpty
83
98
  {
84
- mask = MaskCompositionLayer(masks: masks)
99
+ contentsLayer.mask = MaskCompositionLayer(masks: masks)
85
100
  }
86
101
  }
87
102
 
@@ -42,12 +42,12 @@ final class ImageLayer: BaseCompositionLayer {
42
42
  let image = context.imageProvider.imageForAsset(asset: imageAsset)
43
43
  else {
44
44
  self.imageAsset = nil
45
- contents = nil
45
+ contentsLayer.contents = nil
46
46
  return
47
47
  }
48
48
 
49
49
  self.imageAsset = imageAsset
50
- contents = image
50
+ contentsLayer.contents = image
51
51
  setNeedsLayout()
52
52
  }
53
53
 
@@ -101,7 +101,7 @@ extension PreCompLayer: CustomLayoutLayer {
101
101
  width: CGFloat(preCompLayer.width),
102
102
  height: CGFloat(preCompLayer.height))
103
103
 
104
- masksToBounds = true
104
+ contentsLayer.masksToBounds = true
105
105
  }
106
106
  }
107
107
 
@@ -55,7 +55,15 @@ private struct RepeaterTransform {
55
55
  anchorPoint = repeater.anchorPoint
56
56
  scale = repeater.scale
57
57
 
58
- rotation = repeater.rotation.map { rotation in
58
+ rotationX = repeater.rotationX.map { rotation in
59
+ LottieVector1D(rotation.value * Double(index))
60
+ }
61
+
62
+ rotationY = repeater.rotationY.map { rotation in
63
+ LottieVector1D(rotation.value * Double(index))
64
+ }
65
+
66
+ rotationZ = repeater.rotationZ.map { rotation in
59
67
  LottieVector1D(rotation.value * Double(index))
60
68
  }
61
69
 
@@ -71,7 +79,10 @@ private struct RepeaterTransform {
71
79
 
72
80
  let anchorPoint: KeyframeGroup<LottieVector3D>
73
81
  let position: KeyframeGroup<LottieVector3D>
74
- let rotation: KeyframeGroup<LottieVector1D>
82
+ let rotationX: KeyframeGroup<LottieVector1D>
83
+ let rotationY: KeyframeGroup<LottieVector1D>
84
+ let rotationZ: KeyframeGroup<LottieVector1D>
85
+
75
86
  let scale: KeyframeGroup<LottieVector3D>
76
87
 
77
88
  }
@@ -18,13 +18,18 @@ final class LayerTransformProperties: NodePropertyMap, KeypathSearchable {
18
18
  init(transform: Transform) {
19
19
  anchor = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.anchorPoint.keyframes))
20
20
  scale = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.scale.keyframes))
21
- rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotation.keyframes))
21
+ rotationX = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotationX.keyframes))
22
+ rotationY = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotationY.keyframes))
23
+ rotationZ = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotationZ.keyframes))
22
24
  opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.opacity.keyframes))
23
25
 
24
26
  var propertyMap: [String: AnyNodeProperty] = [
25
27
  "Anchor Point" : anchor,
26
28
  "Scale" : scale,
27
- "Rotation" : rotation,
29
+ "Rotation": rotationZ,
30
+ "Rotation X" : rotationX,
31
+ "Rotation Y" : rotationY,
32
+ "Rotation Z" : rotationZ,
28
33
  "Opacity" : opacity,
29
34
  ]
30
35
 
@@ -64,7 +69,9 @@ final class LayerTransformProperties: NodePropertyMap, KeypathSearchable {
64
69
 
65
70
  let anchor: NodeProperty<LottieVector3D>
66
71
  let scale: NodeProperty<LottieVector3D>
67
- let rotation: NodeProperty<LottieVector1D>
72
+ let rotationX: NodeProperty<LottieVector1D>
73
+ let rotationY: NodeProperty<LottieVector1D>
74
+ let rotationZ: NodeProperty<LottieVector1D>
68
75
  let position: NodeProperty<LottieVector3D>?
69
76
  let positionX: NodeProperty<LottieVector1D>?
70
77
  let positionY: NodeProperty<LottieVector1D>?
@@ -130,7 +137,9 @@ class LayerTransformNode: AnimatorNode {
130
137
  anchor: transformProperties.anchor.value.pointValue,
131
138
  position: position,
132
139
  scale: transformProperties.scale.value.sizeValue,
133
- rotation: transformProperties.rotation.value.cgFloatValue,
140
+ rotationX: transformProperties.rotationX.value.cgFloatValue,
141
+ rotationY: transformProperties.rotationY.value.cgFloatValue,
142
+ rotationZ: transformProperties.rotationZ.value.cgFloatValue,
134
143
  skew: nil,
135
144
  skewAxis: nil)
136
145
 
@@ -20,7 +20,9 @@ final class GroupNodeProperties: NodePropertyMap, KeypathSearchable {
20
20
  anchor = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.anchor.keyframes))
21
21
  position = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.position.keyframes))
22
22
  scale = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.scale.keyframes))
23
- rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotation.keyframes))
23
+ rotationX = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotationX.keyframes))
24
+ rotationY = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotationY.keyframes))
25
+ rotationZ = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotationZ.keyframes))
24
26
  opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.opacity.keyframes))
25
27
  skew = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.skew.keyframes))
26
28
  skewAxis = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.skewAxis.keyframes))
@@ -29,7 +31,9 @@ final class GroupNodeProperties: NodePropertyMap, KeypathSearchable {
29
31
  anchor = NodeProperty(provider: SingleValueProvider(LottieVector3D(x: CGFloat(0), y: CGFloat(0), z: CGFloat(0))))
30
32
  position = NodeProperty(provider: SingleValueProvider(LottieVector3D(x: CGFloat(0), y: CGFloat(0), z: CGFloat(0))))
31
33
  scale = NodeProperty(provider: SingleValueProvider(LottieVector3D(x: CGFloat(100), y: CGFloat(100), z: CGFloat(100))))
32
- rotation = NodeProperty(provider: SingleValueProvider(LottieVector1D(0)))
34
+ rotationX = NodeProperty(provider: SingleValueProvider(LottieVector1D(0)))
35
+ rotationY = NodeProperty(provider: SingleValueProvider(LottieVector1D(0)))
36
+ rotationZ = NodeProperty(provider: SingleValueProvider(LottieVector1D(0)))
33
37
  opacity = NodeProperty(provider: SingleValueProvider(LottieVector1D(100)))
34
38
  skew = NodeProperty(provider: SingleValueProvider(LottieVector1D(0)))
35
39
  skewAxis = NodeProperty(provider: SingleValueProvider(LottieVector1D(0)))
@@ -38,7 +42,10 @@ final class GroupNodeProperties: NodePropertyMap, KeypathSearchable {
38
42
  "Anchor Point" : anchor,
39
43
  "Position" : position,
40
44
  "Scale" : scale,
41
- "Rotation" : rotation,
45
+ "Rotation" : rotationZ,
46
+ "Rotation X" : rotationX,
47
+ "Rotation Y" : rotationY,
48
+ "Rotation Z" : rotationZ,
42
49
  "Opacity" : opacity,
43
50
  "Skew" : skew,
44
51
  "Skew Axis" : skewAxis,
@@ -58,7 +65,10 @@ final class GroupNodeProperties: NodePropertyMap, KeypathSearchable {
58
65
  let anchor: NodeProperty<LottieVector3D>
59
66
  let position: NodeProperty<LottieVector3D>
60
67
  let scale: NodeProperty<LottieVector3D>
61
- let rotation: NodeProperty<LottieVector1D>
68
+ let rotationX: NodeProperty<LottieVector1D>
69
+ let rotationY: NodeProperty<LottieVector1D>
70
+ let rotationZ: NodeProperty<LottieVector1D>
71
+
62
72
  let opacity: NodeProperty<LottieVector1D>
63
73
  let skew: NodeProperty<LottieVector1D>
64
74
  let skewAxis: NodeProperty<LottieVector1D>
@@ -68,7 +78,9 @@ final class GroupNodeProperties: NodePropertyMap, KeypathSearchable {
68
78
  anchor: anchor.value.pointValue,
69
79
  position: position.value.pointValue,
70
80
  scale: scale.value.sizeValue,
71
- rotation: rotation.value.cgFloatValue,
81
+ rotationX: rotationX.value.cgFloatValue,
82
+ rotationY: rotationY.value.cgFloatValue,
83
+ rotationZ: rotationZ.value.cgFloatValue,
72
84
  skew: skew.value.cgFloatValue,
73
85
  skewAxis: skewAxis.value.cgFloatValue)
74
86
  }
@@ -54,11 +54,26 @@ final class TextAnimatorNodeProperties: NodePropertyMap, KeypathSearchable {
54
54
  skewAxis = nil
55
55
  }
56
56
 
57
- if let keyframeGroup = textAnimator.rotation {
58
- rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes))
59
- properties["Rotation"] = rotation
57
+ if let keyframeGroup = textAnimator.rotationX {
58
+ rotationX = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes))
59
+ properties["Rotation X"] = rotationX
60
60
  } else {
61
- rotation = nil
61
+ rotationX = nil
62
+ }
63
+
64
+ if let keyframeGroup = textAnimator.rotationY {
65
+ rotationY = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes))
66
+ properties["Rotation Y"] = rotationY
67
+ } else {
68
+ rotationY = nil
69
+ }
70
+
71
+ if let keyframeGroup = textAnimator.rotationZ {
72
+ rotationZ = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes))
73
+ properties["Rotation Z"] = rotationZ
74
+ properties["Rotation"] = rotationZ
75
+ } else {
76
+ rotationZ = nil
62
77
  }
63
78
 
64
79
  if let keyframeGroup = textAnimator.opacity {
@@ -110,7 +125,9 @@ final class TextAnimatorNodeProperties: NodePropertyMap, KeypathSearchable {
110
125
  let scale: NodeProperty<LottieVector3D>?
111
126
  let skew: NodeProperty<LottieVector1D>?
112
127
  let skewAxis: NodeProperty<LottieVector1D>?
113
- let rotation: NodeProperty<LottieVector1D>?
128
+ let rotationX: NodeProperty<LottieVector1D>?
129
+ let rotationY: NodeProperty<LottieVector1D>?
130
+ let rotationZ: NodeProperty<LottieVector1D>?
114
131
  let opacity: NodeProperty<LottieVector1D>?
115
132
  let strokeColor: NodeProperty<LottieColor>?
116
133
  let fillColor: NodeProperty<LottieColor>?
@@ -125,7 +142,9 @@ final class TextAnimatorNodeProperties: NodePropertyMap, KeypathSearchable {
125
142
  anchor: anchor?.value.pointValue ?? .zero,
126
143
  position: position?.value.pointValue ?? .zero,
127
144
  scale: scale?.value.sizeValue ?? CGSize(width: 100, height: 100),
128
- rotation: rotation?.value.cgFloatValue ?? 0,
145
+ rotationX: rotationX?.value.cgFloatValue ?? 0,
146
+ rotationY: rotationY?.value.cgFloatValue ?? 0,
147
+ rotationZ: rotationZ?.value.cgFloatValue ?? 0,
129
148
  skew: skew?.value.cgFloatValue,
130
149
  skewAxis: skewAxis?.value.cgFloatValue)
131
150
  }