lottie-ios 4.2.0 → 4.3.1

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 (193) hide show
  1. package/.github/workflows/main.yml +117 -11
  2. package/Gemfile +1 -0
  3. package/Gemfile.lock +5 -0
  4. package/Lottie.xcodeproj/project.pbxproj +1546 -194
  5. package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  6. package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (iOS).xcscheme +1 -1
  7. package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (macOS).xcscheme +1 -1
  8. package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (tvOS).xcscheme +4 -5
  9. package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (visionOS).xcscheme +66 -0
  10. package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +18 -0
  11. package/Lottie.xcworkspace/xcshareddata/swiftpm/Package.resolved +2 -11
  12. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  13. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +374 -116
  14. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +16 -5
  15. package/Package.resolved +2 -2
  16. package/Package.swift +16 -9
  17. package/README.md +2 -2
  18. package/Rakefile +122 -25
  19. package/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift +3 -4
  20. package/Sources/Private/CoreAnimation/Animations/DropShadowAnimation.swift +160 -0
  21. package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +83 -10
  22. package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +85 -79
  23. package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +11 -7
  24. package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +67 -6
  25. package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +21 -2
  26. package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +9 -0
  27. package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +1 -0
  28. package/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift +1 -1
  29. package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +2 -0
  30. package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +11 -13
  31. package/Sources/Private/CoreAnimation/Layers/SolidLayer.swift +20 -5
  32. package/Sources/Private/CoreAnimation/Layers/TextLayer.swift +13 -3
  33. package/Sources/Private/CoreAnimation/ValueProviderStore.swift +6 -4
  34. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Collection+Diff.swift +263 -0
  35. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Diffable.swift +18 -0
  36. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/DiffableSection.swift +16 -0
  37. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/IndexChangeset.swift +187 -0
  38. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/SectionedChangeset.swift +32 -0
  39. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Logging/EpoxyLogger.swift +99 -0
  40. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/CallbackContextEpoxyModeled.swift +8 -0
  41. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelArrayBuilder.swift +48 -0
  42. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelProperty.swift +158 -0
  43. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelStorage.swift +88 -0
  44. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModeled.swift +54 -0
  45. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/AnyEpoxyModelProperty.swift +29 -0
  46. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/ClassReference.swift +39 -0
  47. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/AnimatedProviding.swift +10 -0
  48. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DataIDProviding.swift +57 -0
  49. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidDisplayProviding.swift +41 -0
  50. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidEndDisplayingProviding.swift +41 -0
  51. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidSelectProviding.swift +36 -0
  52. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ErasedContentProviding.swift +49 -0
  53. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/MakeViewProviding.swift +60 -0
  54. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetBehaviorsProviding.swift +38 -0
  55. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetContentProviding.swift +38 -0
  56. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/StyleIDProviding.swift +37 -0
  57. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/TraitCollectionProviding.swift +14 -0
  58. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewDifferentiatorProviding.swift +34 -0
  59. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewProviding.swift +13 -0
  60. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/WillDisplayProviding.swift +41 -0
  61. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/ViewEpoxyModeled.swift +10 -0
  62. package/Sources/Private/EmbeddedLibraries/EpoxyCore/README.md +31 -0
  63. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingController.swift +46 -0
  64. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingView.swift +391 -0
  65. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIIntrinsicContentSizeInvalidator.swift +44 -0
  66. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUILayoutMargins.swift +51 -0
  67. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxyableView+SwiftUIView.swift +172 -0
  68. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/MeasuringViewRepresentable.swift +128 -0
  69. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift +452 -0
  70. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/SwiftUIView.swift +148 -0
  71. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIView+SwiftUIView.swift +40 -0
  72. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIViewConfiguringSwiftUIView.swift +43 -0
  73. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/BehaviorsConfigurableView.swift +45 -0
  74. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ContentConfigurableView.swift +36 -0
  75. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/EpoxyableView.swift +5 -0
  76. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/StyledView.swift +42 -0
  77. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ViewType.swift +51 -0
  78. package/Sources/Private/EmbeddedLibraries/README.md +27 -0
  79. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+BackingConfiguration.swift +4 -4
  80. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+MemoryFile.swift +2 -2
  81. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Writing.swift +4 -4
  82. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive.swift +8 -7
  83. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Compression.swift +5 -5
  84. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/FileManager+ZIP.swift +4 -4
  85. package/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.swift +6 -0
  86. package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +3 -1
  87. package/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift +18 -5
  88. package/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift +31 -3
  89. package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +33 -8
  90. package/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift +8 -1
  91. package/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift +13 -4
  92. package/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift +2 -2
  93. package/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.swift +1 -0
  94. package/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift +4 -4
  95. package/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +1 -1
  96. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +3 -1
  97. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +2 -1
  98. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift +2 -1
  99. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +1 -1
  100. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +2 -2
  101. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +1 -1
  102. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +1 -1
  103. package/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +6 -2
  104. package/Sources/Private/Model/Assets/Asset.swift +8 -0
  105. package/Sources/Private/Model/Assets/AssetLibrary.swift +2 -2
  106. package/Sources/Private/Model/Assets/ImageAsset.swift +3 -3
  107. package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +16 -2
  108. package/Sources/Private/Model/DotLottie/DotLottieImageProvider.swift +20 -7
  109. package/Sources/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +26 -0
  110. package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +8 -5
  111. package/Sources/Private/Model/LayerEffects/DropShadowEffect.swift +45 -0
  112. package/Sources/Private/Model/LayerEffects/EffectValues/ColorEffectValue.swift +38 -0
  113. package/Sources/Private/Model/LayerEffects/EffectValues/EffectValue.swift +98 -0
  114. package/Sources/Private/Model/LayerEffects/EffectValues/Vector1DEffectValue.swift +38 -0
  115. package/Sources/Private/Model/LayerEffects/LayerEffect.swift +103 -0
  116. package/Sources/Private/Model/LayerStyles/DropShadowStyle.swift +72 -0
  117. package/Sources/Private/Model/LayerStyles/LayerStyle.swift +85 -0
  118. package/Sources/Private/Model/Layers/LayerModel.swift +27 -0
  119. package/Sources/Private/Model/Objects/Marker.swift +1 -1
  120. package/Sources/Private/Model/Objects/Transform.swift +1 -2
  121. package/Sources/Private/Model/ShapeItems/GradientFill.swift +1 -1
  122. package/Sources/Private/Model/ShapeItems/GradientStroke.swift +2 -2
  123. package/Sources/Private/Model/ShapeItems/Merge.swift +1 -1
  124. package/Sources/Private/Model/ShapeItems/ShapeItem.swift +31 -26
  125. package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +0 -9
  126. package/Sources/Private/Model/ShapeItems/Star.swift +1 -1
  127. package/Sources/Private/Model/Text/Font.swift +2 -2
  128. package/Sources/Private/Model/Text/Glyph.swift +1 -1
  129. package/Sources/Private/RootAnimationLayer.swift +2 -2
  130. package/Sources/Private/Utility/Debugging/LayerDebugging.swift +3 -1
  131. package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +32 -2
  132. package/Sources/Private/Utility/Helpers/AnimationContext.swift +4 -2
  133. package/Sources/Private/Utility/Helpers/AnyEquatable.swift +24 -0
  134. package/Sources/Private/Utility/Helpers/Binding+Map.swift +18 -0
  135. package/Sources/Private/Utility/Helpers/View+ValueChanged.swift +20 -0
  136. package/Sources/Private/Utility/LottieAnimationSource.swift +41 -0
  137. package/Sources/Private/Utility/Primitives/BezierPath.swift +2 -2
  138. package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +2 -2
  139. package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +1 -1
  140. package/Sources/Public/Animation/LottieAnimation.swift +21 -2
  141. package/Sources/Public/Animation/LottieAnimationLayer.swift +1464 -0
  142. package/Sources/Public/Animation/LottieAnimationView.swift +259 -736
  143. package/Sources/Public/Animation/LottiePlaybackMode.swift +117 -0
  144. package/Sources/Public/Animation/LottieView.swift +462 -0
  145. package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +2 -1
  146. package/Sources/Public/AnimationCache/DefaultAnimationCache.swift +5 -6
  147. package/Sources/Public/Configuration/DecodingStrategy.swift +15 -0
  148. package/Sources/Public/Configuration/LottieConfiguration.swift +47 -0
  149. package/Sources/Public/Configuration/ReducedMotionOption.swift +114 -0
  150. package/Sources/Public/{LottieConfiguration.swift → Configuration/RenderingEngineOption.swift} +2 -57
  151. package/Sources/Public/{iOS → Controls}/AnimatedButton.swift +56 -13
  152. package/Sources/Public/{iOS → Controls}/AnimatedControl.swift +80 -8
  153. package/Sources/Public/{iOS → Controls}/AnimatedSwitch.swift +71 -31
  154. package/Sources/Public/Controls/LottieButton.swift +122 -0
  155. package/Sources/Public/Controls/LottieSwitch.swift +144 -0
  156. package/Sources/Public/Controls/LottieViewType.swift +79 -0
  157. package/Sources/Public/DotLottie/DotLottieConfiguration.swift +24 -0
  158. package/Sources/Public/DotLottie/DotLottieFile.swift +21 -6
  159. package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +79 -0
  160. package/Sources/Public/DynamicProperties/AnimationKeypath.swift +11 -1
  161. package/Sources/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +14 -0
  162. package/Sources/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +13 -0
  163. package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +22 -5
  164. package/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +14 -0
  165. package/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +13 -0
  166. package/Sources/Public/FontProvider/AnimationFontProvider.swift +8 -0
  167. package/Sources/Public/ImageProvider/AnimationImageProvider.swift +24 -0
  168. package/Sources/Public/Keyframes/Keyframe.swift +6 -0
  169. package/Sources/Public/Primitives/Vectors.swift +2 -2
  170. package/Sources/Public/TextProvider/AnimationTextProvider.swift +79 -6
  171. package/Sources/Public/iOS/AnimationSubview.swift +1 -1
  172. package/Sources/Public/iOS/BundleImageProvider.swift +16 -2
  173. package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +6 -4
  174. package/Sources/Public/iOS/FilepathImageProvider.swift +22 -3
  175. package/Sources/Public/iOS/LottieAnimationViewBase.swift +7 -1
  176. package/Sources/Public/macOS/BundleImageProvider.macOS.swift +15 -1
  177. package/Sources/Public/macOS/FilepathImageProvider.macOS.swift +21 -2
  178. package/lottie-ios.podspec +2 -1
  179. package/package.json +1 -1
  180. package/Sources/Private/Model/DotLottie/DotLottieConfiguration.swift +0 -15
  181. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Helpers.swift +0 -0
  182. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Progress.swift +0 -0
  183. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Reading.swift +0 -0
  184. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ReadingDeprecated.swift +0 -0
  185. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+WritingDeprecated.swift +0 -0
  186. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ZIP64.swift +0 -0
  187. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+CompressionDeprecated.swift +0 -0
  188. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Serialization.swift +0 -0
  189. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+Serialization.swift +0 -0
  190. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+ZIP64.swift +0 -0
  191. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry.swift +0 -0
  192. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/README.md +0 -0
  193. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/URL+ZIP.swift +0 -0
@@ -38,11 +38,14 @@ struct LayerAnimationContext {
38
38
  /// The logger that should be used for assertions and warnings
39
39
  let logger: LottieLogger
40
40
 
41
+ /// Mutable state related to log events, stored on the `CoreAnimationLayer`.
42
+ let loggingState: LoggingState
43
+
41
44
  /// The AnimationKeypath represented by the current layer
42
45
  var currentKeypath: AnimationKeypath
43
46
 
44
- /// The `AnimationTextProvider`
45
- var textProvider: AnimationTextProvider
47
+ /// The `AnimationKeypathTextProvider`
48
+ var textProvider: AnimationKeypathTextProvider
46
49
 
47
50
  /// Records the given animation keypath so it can be logged or collected into a list
48
51
  /// - Used for `CoreAnimationLayer.logHierarchyKeypaths()` and `allHierarchyKeypaths()`
@@ -84,3 +87,19 @@ struct LayerAnimationContext {
84
87
  return copy
85
88
  }
86
89
  }
90
+
91
+ // MARK: - LoggingState
92
+
93
+ /// Mutable state related to log events, stored on the `CoreAnimationLayer`.
94
+ final class LoggingState {
95
+
96
+ // MARK: Lifecycle
97
+
98
+ init() { }
99
+
100
+ // MARK: Internal
101
+
102
+ /// Whether or not the warning about unsupported After Effects expressions
103
+ /// has been logged yet for this layer.
104
+ var hasLoggedAfterEffectsExpressionsWarning = false
105
+ }
@@ -67,6 +67,15 @@ class BaseCompositionLayer: BaseAnimationLayer {
67
67
  inFrame: CGFloat(baseLayerModel.inFrame),
68
68
  outFrame: CGFloat(baseLayerModel.outFrame),
69
69
  context: context)
70
+
71
+ // There are two different drop shadow schemas, either using `DropShadowEffect` or `DropShadowStyle`.
72
+ // If both happen to be present, prefer the `DropShadowEffect` (which is the drop shadow schema
73
+ // supported on other platforms).
74
+ let dropShadowEffect = baseLayerModel.effects.first(where: { $0 is DropShadowEffect }) as? DropShadowModel
75
+ let dropShadowStyle = baseLayerModel.styles.first(where: { $0 is DropShadowStyle }) as? DropShadowModel
76
+ if let dropShadowModel = dropShadowEffect ?? dropShadowStyle {
77
+ try contentsLayer.addDropShadowAnimations(for: dropShadowModel, context: context)
78
+ }
70
79
  }
71
80
  }
72
81
 
@@ -48,6 +48,7 @@ final class ImageLayer: BaseCompositionLayer {
48
48
 
49
49
  self.imageAsset = imageAsset
50
50
  contentsLayer.contents = image
51
+ contentsLayer.contentsGravity = context.imageProvider.contentsGravity(for: imageAsset)
51
52
  setNeedsLayout()
52
53
  }
53
54
 
@@ -9,7 +9,7 @@ import QuartzCore
9
9
  struct LayerContext {
10
10
  let animation: LottieAnimation
11
11
  let imageProvider: AnimationImageProvider
12
- let textProvider: AnimationTextProvider
12
+ let textProvider: AnimationKeypathTextProvider
13
13
  let fontProvider: AnimationFontProvider
14
14
  let compatibilityTracker: CompatibilityTracker
15
15
  var layerName: String
@@ -93,4 +93,6 @@ extension RepeaterTransform: TransformModel {
93
93
  var _position: KeyframeGroup<LottieVector3D>? { position }
94
94
  var _positionX: KeyframeGroup<LottieVector1D>? { nil }
95
95
  var _positionY: KeyframeGroup<LottieVector1D>? { nil }
96
+ var _skew: KeyframeGroup<LottieVector1D>? { nil }
97
+ var _skewAxis: KeyframeGroup<LottieVector1D>? { nil }
96
98
  }
@@ -41,7 +41,8 @@ final class ShapeLayer: BaseCompositionLayer {
41
41
  if let repeater = shapeLayer.items.first(where: { $0 is Repeater }) as? Repeater {
42
42
  try setUpRepeater(repeater, context: context)
43
43
  } else {
44
- try setupGroups(from: shapeLayer.items, parentGroup: nil, parentGroupPath: [], context: context)
44
+ let shapeItems = shapeLayer.items.map { ShapeItemLayer.Item(item: $0, groupPath: []) }
45
+ try setupGroups(from: shapeItems, parentGroup: nil, parentGroupPath: [], context: context)
45
46
  }
46
47
  }
47
48
 
@@ -50,7 +51,8 @@ final class ShapeLayer: BaseCompositionLayer {
50
51
  let copyCount = Int(try repeater.copies.exactlyOneKeyframe(context: context, description: "repeater copies").value)
51
52
 
52
53
  for index in 0..<copyCount {
53
- for groupLayer in try makeGroupLayers(from: items, parentGroup: nil, parentGroupPath: [], context: context) {
54
+ let shapeItems = items.map { ShapeItemLayer.Item(item: $0, groupPath: []) }
55
+ for groupLayer in try makeGroupLayers(from: shapeItems, parentGroup: nil, parentGroupPath: [], context: context) {
54
56
  let repeatedLayer = RepeaterLayer(repeater: repeater, childLayer: groupLayer, index: index)
55
57
  addSublayer(repeatedLayer)
56
58
  }
@@ -127,7 +129,7 @@ final class GroupLayer: BaseAnimationLayer {
127
129
  private func setupLayerHierarchy(context: LayerContext) throws {
128
130
  // Groups can contain other groups, so we may have to continue
129
131
  // recursively creating more `GroupLayer`s
130
- try setupGroups(from: group.items, parentGroup: group, parentGroupPath: groupPath, context: context)
132
+ try setupGroups(from: items, parentGroup: group, parentGroupPath: groupPath, context: context)
131
133
 
132
134
  // Create `ShapeItemLayer`s for each subgroup of shapes that should be rendered as a single unit
133
135
  // - These groups are listed from front-to-back, so we have to add the sublayers in reverse order
@@ -183,7 +185,7 @@ extension CALayer {
183
185
  /// - Each `Group` item becomes its own `GroupLayer` sublayer.
184
186
  /// - Other `ShapeItem` are applied to all sublayers
185
187
  fileprivate func setupGroups(
186
- from items: [ShapeItem],
188
+ from items: [ShapeItemLayer.Item],
187
189
  parentGroup: Group?,
188
190
  parentGroupPath: [String],
189
191
  context: LayerContext)
@@ -204,29 +206,27 @@ extension CALayer {
204
206
  /// - Each `Group` item becomes its own `GroupLayer` sublayer.
205
207
  /// - Other `ShapeItem` are applied to all sublayers
206
208
  fileprivate func makeGroupLayers(
207
- from items: [ShapeItem],
209
+ from items: [ShapeItemLayer.Item],
208
210
  parentGroup: Group?,
209
211
  parentGroupPath: [String],
210
212
  context: LayerContext)
211
213
  throws -> [GroupLayer]
212
214
  {
213
- var (groupItems, otherItems) = items
214
- .filter { !$0.hidden }
215
- .grouped(by: { $0 is Group })
215
+ var groupItems = items.compactMap { $0.item as? Group }.filter { !$0.hidden }
216
+ var otherItems = items.filter { !($0.item is Group) && !$0.item.hidden }
216
217
 
217
218
  // Handle the top-level `shapeLayer.items` array. This is typically just a single `Group`,
218
219
  // but in practice can be any combination of items. The implementation expects all path-drawing
219
220
  // shape items to be managed by a `GroupLayer`, so if there's a top-level path item we
220
221
  // have to create a placeholder group.
221
- if parentGroup == nil, otherItems.contains(where: { $0.drawsCGPath }) {
222
- groupItems = [Group(items: items, name: "")]
222
+ if parentGroup == nil, otherItems.contains(where: { $0.item.drawsCGPath }) {
223
+ groupItems = [Group(items: items.map { $0.item }, name: "")]
223
224
  otherItems = []
224
225
  }
225
226
 
226
227
  // Any child items that wouldn't be included in a valid shape render group
227
228
  // need to be applied to child groups (otherwise they'd be silently ignored).
228
229
  let inheritedItemsForChildGroups = otherItems
229
- .map { ShapeItemLayer.Item(item: $0, groupPath: parentGroupPath) }
230
230
  .shapeRenderGroups(groupHasChildGroupsToInheritUnusedItems: !groupItems.isEmpty)
231
231
  .unusedItems
232
232
 
@@ -235,8 +235,6 @@ extension CALayer {
235
235
  let groupsInZAxisOrder = groupItems.reversed()
236
236
 
237
237
  return try groupsInZAxisOrder.compactMap { group in
238
- guard let group = group as? Group else { return nil }
239
-
240
238
  var pathForChildren = parentGroupPath
241
239
  if !group.name.isEmpty {
242
240
  pathForChildren.append(group.name)
@@ -30,16 +30,31 @@ final class SolidLayer: BaseCompositionLayer {
30
30
  super.init(layer: typedLayer)
31
31
  }
32
32
 
33
+ // MARK: Internal
34
+
35
+ override func setupAnimations(context: LayerAnimationContext) throws {
36
+ try super.setupAnimations(context: context)
37
+
38
+ // Even though the Lottie json schema provides a fixed `solidLayer.colorHex` value,
39
+ // we still need to create a set of keyframes and go through the standard `CAAnimation`
40
+ // codepath so that this value can be customized using the custom `ValueProvider`s API.
41
+ try shapeLayer.addAnimation(
42
+ for: .fillColor,
43
+ keyframes: KeyframeGroup(solidLayer.colorHex.cgColor),
44
+ value: { $0 },
45
+ context: context)
46
+ }
47
+
33
48
  // MARK: Private
34
49
 
35
50
  private let solidLayer: SolidLayerModel
36
51
 
52
+ /// Render the fill color in a child `CAShapeLayer`
53
+ /// - Using a `CAShapeLayer` specifically, instead of a `CALayer` with a `backgroundColor`,
54
+ /// allows the size of the fill shape to be different from `contentsLayer.size`.
55
+ private let shapeLayer = CAShapeLayer()
56
+
37
57
  private func setupContentLayer() {
38
- // Render the fill color in a child `CAShapeLayer`
39
- // - Using a `CAShapeLayer` specifically, instead of a `CALayer` with a `backgroundColor`,
40
- // allows the size of the fill shape to be different from `contentsLayer.size`.
41
- let shapeLayer = CAShapeLayer()
42
- shapeLayer.fillColor = solidLayer.colorHex.cgColor
43
58
  shapeLayer.path = CGPath(rect: .init(x: 0, y: 0, width: solidLayer.width, height: solidLayer.height), transform: nil)
44
59
  addSublayer(shapeLayer)
45
60
  }
@@ -44,9 +44,19 @@ final class TextLayer: BaseCompositionLayer {
44
44
  context: textAnimationContext,
45
45
  description: "text layer text")
46
46
 
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
+ }
50
60
 
51
61
  renderLayer.sizeToFit()
52
62
  }
@@ -36,6 +36,7 @@ final class ValueProviderStore {
36
36
  properties. Supported properties are: \(supportedProperties.joined(separator: ", ")).
37
37
  """)
38
38
 
39
+ valueProviders.removeAll(where: { $0.keypath == keypath })
39
40
  valueProviders.append((keypath: keypath, valueProvider: valueProvider))
40
41
  }
41
42
 
@@ -72,9 +73,11 @@ final class ValueProviderStore {
72
73
 
73
74
  // Convert the type-erased keyframe values using this `CustomizableProperty`'s conversion closure
74
75
  let typedKeyframes = typeErasedKeyframes.compactMap { typeErasedKeyframe -> Keyframe<Value>? in
75
- guard let convertedValue = customizableProperty.conversion(typeErasedKeyframe.value) else {
76
+ guard let convertedValue = customizableProperty.conversion(typeErasedKeyframe.value, anyValueProvider) else {
76
77
  logger.assertionFailure("""
77
- Could not convert value of type \(type(of: typeErasedKeyframe.value)) to expected type \(Value.self)
78
+ Could not convert value of type \(type(of: typeErasedKeyframe.value)) from \(anyValueProvider) to expected type \(
79
+ Value
80
+ .self)
78
81
  """)
79
82
  return nil
80
83
  }
@@ -93,10 +96,9 @@ final class ValueProviderStore {
93
96
  // MARK: Private
94
97
 
95
98
  private let logger: LottieLogger
96
-
97
99
  private var valueProviders = [(keypath: AnimationKeypath, valueProvider: AnyValueProvider)]()
98
100
 
99
- /// Retrieves the most-recently-registered Value Provider that matches the given keypat
101
+ /// Retrieves the most-recently-registered Value Provider that matches the given keypath.
100
102
  private func valueProvider(for keypath: AnimationKeypath) -> AnyValueProvider? {
101
103
  // Find the last keypath matching the given keypath,
102
104
  // so we return the value provider that was registered most-recently
@@ -0,0 +1,263 @@
1
+ // Created by Laura Skelton on 11/25/16.
2
+ // Copyright © 2016 Airbnb. All rights reserved.
3
+
4
+ // MARK: - Collection
5
+
6
+ extension Collection where Element: Diffable, Index == Int {
7
+
8
+ /// Diffs two collections (e.g. `Array`s) of `Diffable` items, returning an `IndexChangeset`
9
+ /// representing the minimal set of changes to get from the other collection to this collection.
10
+ ///
11
+ /// - Parameters:
12
+ /// - from other: The collection of old data.
13
+ func makeChangeset(from other: Self) -> IndexChangeset {
14
+ // Arranging the elements contiguously prior to diffing improves performance by ~40%.
15
+ let new = ContiguousArray(self)
16
+ let old = ContiguousArray(other)
17
+
18
+ /// The entries in both this and the other collection, keyed by their `dataID`s.
19
+ var entries = [AnyHashable: EpoxyEntry](minimumCapacity: new.count)
20
+ var duplicates = [EpoxyEntry]()
21
+
22
+ var newResults = ContiguousArray<NewRecord>()
23
+ newResults.reserveCapacity(new.count)
24
+
25
+ for index in new.indices {
26
+ let id = new[index].diffIdentifier
27
+ let entry = entries[id, default: EpoxyEntry()]
28
+ if entry.trackNewIndex(index) {
29
+ duplicates.append(entry)
30
+ }
31
+ entries[id] = entry
32
+ newResults.append(NewRecord(entry: entry))
33
+ }
34
+
35
+ var oldResults = ContiguousArray<OldRecord>()
36
+ oldResults.reserveCapacity(old.count)
37
+
38
+ for index in old.indices {
39
+ let id = old[index].diffIdentifier
40
+ let entry = entries[id]
41
+ entry?.pushOldIndex(index)
42
+ oldResults.append(OldRecord(entry: entry))
43
+ }
44
+
45
+ for newIndex in new.indices {
46
+ let entry = newResults[newIndex].entry
47
+ if let oldIndex = entry.popOldIndex() {
48
+ let newItem = new[newIndex]
49
+ let oldItem = other[oldIndex]
50
+
51
+ if !oldItem.isDiffableItemEqual(to: newItem) {
52
+ entry.isUpdated = true
53
+ }
54
+
55
+ newResults[newIndex].correspondingOldIndex = oldIndex
56
+ oldResults[oldIndex].correspondingNewIndex = newIndex
57
+ }
58
+ }
59
+
60
+ var deletes = [Int]()
61
+ var deleteOffsets = [Int]()
62
+ deleteOffsets.reserveCapacity(old.count)
63
+ var runningDeleteOffset = 0
64
+
65
+ for index in old.indices {
66
+ deleteOffsets.append(runningDeleteOffset)
67
+
68
+ let record = oldResults[index]
69
+
70
+ if record.correspondingNewIndex == nil {
71
+ deletes.append(index)
72
+ runningDeleteOffset += 1
73
+ }
74
+ }
75
+
76
+ var inserts = [Int]()
77
+ var updates = [(Int, Int)]()
78
+ var moves = [(Int, Int)]()
79
+ var insertOffsets = [Int]()
80
+ insertOffsets.reserveCapacity(new.count)
81
+ var runningInsertOffset = 0
82
+
83
+ for index in new.indices {
84
+ insertOffsets.append(runningInsertOffset)
85
+
86
+ let record = newResults[index]
87
+
88
+ if let oldArrayIndex = record.correspondingOldIndex {
89
+ if record.entry.isUpdated {
90
+ updates.append((oldArrayIndex, index))
91
+ }
92
+
93
+ let insertOffset = insertOffsets[index]
94
+ let deleteOffset = deleteOffsets[oldArrayIndex]
95
+ if (oldArrayIndex - deleteOffset + insertOffset) != index {
96
+ moves.append((oldArrayIndex, index))
97
+ }
98
+
99
+ } else {
100
+ inserts.append(index)
101
+ runningInsertOffset += 1
102
+ }
103
+ }
104
+
105
+ EpoxyLogger.shared.assert(
106
+ old.count + inserts.count - deletes.count == new.count,
107
+ "Failed sanity check for old count with changes matching new count.")
108
+
109
+ return IndexChangeset(
110
+ inserts: inserts,
111
+ deletes: deletes,
112
+ updates: updates,
113
+ moves: moves,
114
+ newIndices: oldResults.map { $0.correspondingNewIndex },
115
+ duplicates: duplicates.map { $0.newIndices })
116
+ }
117
+
118
+ /// Diffs between two collections (eg. `Array`s) of `Diffable` items, and returns an `IndexPathChangeset`
119
+ /// representing the minimal set of changes to get from the other collection to this collection.
120
+ ///
121
+ /// - Parameters:
122
+ /// - from other: The collection of old data.
123
+ /// - fromSection: The section the other collection's data exists within. Defaults to `0`.
124
+ /// - toSection: The section this collection's data exists within. Defaults to `0`.
125
+ func makeIndexPathChangeset(
126
+ from other: Self,
127
+ fromSection: Int = 0,
128
+ toSection: Int = 0)
129
+ -> IndexPathChangeset
130
+ {
131
+ let indexChangeset = makeChangeset(from: other)
132
+
133
+ return IndexPathChangeset(
134
+ inserts: indexChangeset.inserts.map { index in
135
+ [toSection, index]
136
+ },
137
+ deletes: indexChangeset.deletes.map { index in
138
+ [fromSection, index]
139
+ },
140
+ updates: indexChangeset.updates.map { fromIndex, toIndex in
141
+ ([fromSection, fromIndex], [toSection, toIndex])
142
+ },
143
+ moves: indexChangeset.moves.map { fromIndex, toIndex in
144
+ ([fromSection, fromIndex], [toSection, toIndex])
145
+ },
146
+ duplicates: indexChangeset.duplicates.map { duplicate in
147
+ duplicate.map { index in
148
+ [toSection, index]
149
+ }
150
+ })
151
+ }
152
+
153
+ /// Diffs between two collections (e.g. `Array`s) of `Diffable` items, returning an
154
+ /// `IndexSetChangeset` representing the minimal set of changes to get from the other collection
155
+ /// to this collection.
156
+ ///
157
+ /// - Parameters:
158
+ /// - from other: The collection of old data.
159
+ func makeIndexSetChangeset(from other: Self) -> IndexSetChangeset {
160
+ let indexChangeset = makeChangeset(from: other)
161
+
162
+ return IndexSetChangeset(
163
+ inserts: .init(indexChangeset.inserts),
164
+ deletes: .init(indexChangeset.deletes),
165
+ updates: indexChangeset.updates,
166
+ moves: indexChangeset.moves,
167
+ newIndices: indexChangeset.newIndices,
168
+ duplicates: indexChangeset.duplicates.map { .init($0) })
169
+ }
170
+
171
+ }
172
+
173
+ extension Collection where Element: DiffableSection, Index == Int {
174
+ /// Diffs between two collections (e.g. `Array`s) of `DiffableSection` items, returning an
175
+ /// `SectionedChangeset` representing the minimal set of changes to get from the other collection
176
+ /// to this collection.
177
+ ///
178
+ /// - Parameters:
179
+ /// - from other: The collection of old data.
180
+ func makeSectionedChangeset(from other: Self) -> SectionedChangeset {
181
+ let sectionChangeset = makeIndexSetChangeset(from: other)
182
+ var itemChangeset = IndexPathChangeset()
183
+
184
+ for fromSectionIndex in other.indices {
185
+ guard let toSectionIndex = sectionChangeset.newIndices[fromSectionIndex] else {
186
+ continue
187
+ }
188
+
189
+ let fromItems = other[fromSectionIndex].diffableItems
190
+ let toItems = self[toSectionIndex].diffableItems
191
+
192
+ let itemIndexChangeset = toItems.makeIndexPathChangeset(
193
+ from: fromItems,
194
+ fromSection: fromSectionIndex,
195
+ toSection: toSectionIndex)
196
+
197
+ itemChangeset += itemIndexChangeset
198
+ }
199
+
200
+ return SectionedChangeset(sectionChangeset: sectionChangeset, itemChangeset: itemChangeset)
201
+ }
202
+ }
203
+
204
+ // MARK: - EpoxyEntry
205
+
206
+ /// A bookkeeping refrence type for the diffing algorithm.
207
+ private final class EpoxyEntry {
208
+
209
+ // MARK: Internal
210
+
211
+ private(set) var oldIndices = [Int]()
212
+ private(set) var newIndices = [Int]()
213
+ var isUpdated = false
214
+
215
+ /// Tracks an index from the new indices, returning `true` if this entry has previously tracked
216
+ /// a new index as a means to identify duplicates and `false` otherwise.
217
+ func trackNewIndex(_ index: Int) -> Bool {
218
+ let previouslyEmpty = newIndices.isEmpty
219
+
220
+ newIndices.append(index)
221
+
222
+ // We've encountered a duplicate, return true so we can track it.
223
+ if !previouslyEmpty, newIndices.count == 2 {
224
+ return true
225
+ }
226
+
227
+ return false
228
+ }
229
+
230
+ func pushOldIndex(_ index: Int) {
231
+ oldIndices.append(index)
232
+ }
233
+
234
+ func popOldIndex() -> Int? {
235
+ guard currentOldIndex < oldIndices.endIndex else {
236
+ return nil
237
+ }
238
+ defer {
239
+ currentOldIndex += 1
240
+ }
241
+ return oldIndices[currentOldIndex]
242
+ }
243
+
244
+ // MARK: Private
245
+
246
+ private var currentOldIndex = 0
247
+ }
248
+
249
+ // MARK: - OldRecord
250
+
251
+ /// A bookkeeping type for pairing up an old element with its new index.
252
+ private struct OldRecord {
253
+ var entry: EpoxyEntry?
254
+ var correspondingNewIndex: Int? = nil
255
+ }
256
+
257
+ // MARK: - NewRecord
258
+
259
+ /// A bookkeeping type for pairing up a new element with its old index.
260
+ private struct NewRecord {
261
+ var entry: EpoxyEntry
262
+ var correspondingOldIndex: Int? = nil
263
+ }
@@ -0,0 +1,18 @@
1
+ // Created by Laura Skelton on 5/11/17.
2
+ // Copyright © 2017 Airbnb. All rights reserved.
3
+
4
+ // MARK: - Diffable
5
+
6
+ /// A protocol that allows us to check identity and equality between items for the purposes of
7
+ /// diffing.
8
+ protocol Diffable {
9
+
10
+ /// Checks for equality between items when diffing.
11
+ ///
12
+ /// - Parameters:
13
+ /// - otherDiffableItem: The other item to check equality against while diffing.
14
+ func isDiffableItemEqual(to otherDiffableItem: Diffable) -> Bool
15
+
16
+ /// The identifier to use when checking identity while diffing.
17
+ var diffIdentifier: AnyHashable { get }
18
+ }
@@ -0,0 +1,16 @@
1
+ // Created by eric_horacek on 12/9/20.
2
+ // Copyright © 2020 Airbnb Inc. All rights reserved.
3
+
4
+ // MARK: - DiffableSection
5
+
6
+ /// A protocol that allows us to check identity and equality between sections of `Diffable` items
7
+ /// for the purposes of diffing.
8
+ protocol DiffableSection: Diffable {
9
+ /// The diffable items in this section.
10
+ associatedtype DiffableItems: Collection where
11
+ DiffableItems.Index == Int,
12
+ DiffableItems.Element: Diffable
13
+
14
+ /// The diffable items in this section.
15
+ var diffableItems: DiffableItems { get }
16
+ }