lottie-ios 3.2.2 → 3.4.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 (316) hide show
  1. package/.github/actions/setup/action.yml +32 -0
  2. package/.github/issue_template.md +6 -23
  3. package/.github/workflows/main.yml +98 -0
  4. package/.spi.yml +6 -0
  5. package/.swift-version +1 -0
  6. package/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +7 -0
  7. package/.swiftpm/xcode/package.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  8. package/.swiftpm/xcode/xcuserdata/{brandonwithrow.xcuserdatad → cal.xcuserdatad}/xcschemes/xcschememanagement.plist +25 -15
  9. package/.swiftpm/xcode/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  10. package/Gemfile +4 -0
  11. package/Gemfile.lock +102 -0
  12. package/Lottie.xcodeproj/project.pbxproj +2003 -1972
  13. package/Lottie.xcodeproj/project.xcworkspace/contents.xcworkspacedata +1 -1
  14. package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  15. package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_iOS.xcscheme → Lottie (iOS).xcscheme } +15 -18
  16. package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_macOS.xcscheme → Lottie (macOS).xcscheme } +7 -20
  17. package/Lottie.xcodeproj/xcshareddata/xcschemes/{Lottie_tvOS.xcscheme → Lottie (tvOS).xcscheme } +5 -18
  18. package/Lottie.xcodeproj/xcuserdata/cal.xcuserdatad/xcschemes/xcschememanagement.plist +37 -0
  19. package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +24 -0
  20. package/Lottie.xcworkspace/contents.xcworkspacedata +10 -0
  21. package/Lottie.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  22. package/Lottie.xcworkspace/xcshareddata/swiftpm/Package.resolved +115 -0
  23. package/Lottie.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  24. package/{Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad → Lottie.xcworkspace/xcuserdata/cal.xcuserdatad}/xcdebugger/Breakpoints_v2.xcbkptlist +2 -2
  25. package/Lottie.xcworkspace/xcuserdata/cal.xcuserdatad/xcdebugger/Expressions.xcexplist +153 -0
  26. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  27. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +39 -0
  28. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +25 -0
  29. package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +30 -0
  30. package/Package.resolved +88 -0
  31. package/Package.swift +10 -15
  32. package/README.md +40 -60
  33. package/Rakefile +121 -0
  34. package/Sources/Private/CoreAnimation/Animations/CAAnimation+TimingConfiguration.swift +81 -0
  35. package/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift +448 -0
  36. package/Sources/Private/CoreAnimation/Animations/CombinedShapeAnimation.swift +53 -0
  37. package/Sources/Private/CoreAnimation/Animations/CustomPathAnimation.swift +41 -0
  38. package/Sources/Private/CoreAnimation/Animations/EllipseAnimation.swift +28 -0
  39. package/Sources/Private/CoreAnimation/Animations/GradientAnimations.swift +210 -0
  40. package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +229 -0
  41. package/Sources/Private/CoreAnimation/Animations/OpacityAnimation.swift +52 -0
  42. package/Sources/Private/CoreAnimation/Animations/RectangleAnimation.swift +31 -0
  43. package/Sources/Private/CoreAnimation/Animations/ShapeAnimation.swift +246 -0
  44. package/Sources/Private/CoreAnimation/Animations/StarAnimation.swift +95 -0
  45. package/Sources/Private/CoreAnimation/Animations/StrokeAnimation.swift +70 -0
  46. package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +205 -0
  47. package/Sources/Private/CoreAnimation/Animations/VisibilityAnimation.swift +37 -0
  48. package/Sources/Private/CoreAnimation/CompatibilityTracker.swift +130 -0
  49. package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +492 -0
  50. package/Sources/Private/CoreAnimation/Extensions/CALayer+fillBounds.swift +35 -0
  51. package/Sources/Private/CoreAnimation/Extensions/KeyframeGroup+exactlyOneKeyframe.swift +37 -0
  52. package/Sources/Private/CoreAnimation/Extensions/Keyframes+combinedIfPossible.swift +73 -0
  53. package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +83 -0
  54. package/Sources/Private/CoreAnimation/Layers/BaseAnimationLayer.swift +33 -0
  55. package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +87 -0
  56. package/Sources/Private/CoreAnimation/Layers/CALayer+setupLayerHierarchy.swift +131 -0
  57. package/Sources/Private/CoreAnimation/Layers/GradientRenderLayer.swift +94 -0
  58. package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +79 -0
  59. package/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift +60 -0
  60. package/Sources/Private/CoreAnimation/Layers/MaskCompositionLayer.swift +138 -0
  61. package/Sources/Private/CoreAnimation/Layers/PreCompLayer.swift +140 -0
  62. package/Sources/Private/CoreAnimation/Layers/ShapeItemLayer.swift +302 -0
  63. package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +319 -0
  64. package/Sources/Private/CoreAnimation/Layers/SolidLayer.swift +47 -0
  65. package/Sources/Private/CoreAnimation/Layers/TextLayer.swift +91 -0
  66. package/Sources/Private/CoreAnimation/Layers/TransformLayer.swift +11 -0
  67. package/Sources/Private/CoreAnimation/ValueProviderStore.swift +139 -0
  68. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/CompositionLayer.swift +93 -85
  69. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/ImageCompositionLayer.swift +24 -21
  70. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/MaskContainerLayer.swift +75 -54
  71. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/NullCompositionLayer.swift +5 -5
  72. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/PreCompositionLayer.swift +61 -44
  73. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/ShapeCompositionLayer.swift +22 -20
  74. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/SolidCompositionLayer.swift +26 -17
  75. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/CompLayers/TextCompositionLayer.swift +42 -36
  76. package/{lottie-swift/src/Private/LayerContainers/AnimationContainer.swift → Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift} +200 -140
  77. package/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift +47 -0
  78. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/CompositionLayersInitializer.swift +33 -24
  79. package/{lottie-swift/src/Private/LayerContainers/Utility/TextLayer.swift → Sources/Private/MainThread/LayerContainers/Utility/CoreTextRenderLayer.swift} +120 -106
  80. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/InvertedMatteLayer.swift +25 -24
  81. package/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift +41 -0
  82. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/LayerImageProvider.swift +20 -16
  83. package/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift +40 -0
  84. package/{lottie-swift/src/Private → Sources/Private/MainThread}/LayerContainers/Utility/LayerTransformNode.swift +82 -67
  85. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Extensions/ItemsExtension.swift +4 -4
  86. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/NodeProperty.swift +29 -21
  87. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift +16 -10
  88. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift +6 -6
  89. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift +5 -5
  90. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +10 -8
  91. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueContainer.swift +25 -21
  92. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift +23 -17
  93. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift +125 -108
  94. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift +22 -17
  95. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +110 -79
  96. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift +22 -16
  97. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift +19 -15
  98. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift +22 -20
  99. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift +22 -23
  100. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +242 -0
  101. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift +30 -24
  102. package/{lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift → Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift} +93 -66
  103. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift +23 -19
  104. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +139 -0
  105. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +170 -0
  106. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/PathNodes/RectNode.swift +95 -58
  107. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift +42 -29
  108. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/PathNodes/StarNode.swift +108 -69
  109. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +155 -0
  110. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift +51 -37
  111. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +54 -42
  112. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +65 -57
  113. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +84 -58
  114. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +138 -119
  115. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Protocols/AnimatorNode.swift +80 -79
  116. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Protocols/PathNode.swift +5 -3
  117. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/Protocols/RenderNode.swift +22 -17
  118. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +27 -24
  119. package/{lottie-swift/src/Private → Sources/Private/MainThread}/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift +34 -25
  120. package/Sources/Private/Model/Animation.swift +160 -0
  121. package/Sources/Private/Model/Assets/Asset.swift +43 -0
  122. package/{lottie-swift/src → Sources}/Private/Model/Assets/AssetLibrary.swift +40 -13
  123. package/Sources/Private/Model/Assets/ImageAsset.swift +112 -0
  124. package/{lottie-swift/src → Sources}/Private/Model/Assets/PrecompAsset.swift +20 -10
  125. package/Sources/Private/Model/DictionaryInitializable.swift +67 -0
  126. package/Sources/Private/Model/Extensions/Bundle.swift +34 -0
  127. package/{lottie-swift/src → Sources}/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +8 -4
  128. package/Sources/Private/Model/Keyframes/KeyframeData.swift +113 -0
  129. package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +197 -0
  130. package/{lottie-swift/src → Sources}/Private/Model/Layers/ImageLayerModel.swift +21 -11
  131. package/Sources/Private/Model/Layers/LayerModel.swift +228 -0
  132. package/{lottie-swift/src → Sources}/Private/Model/Layers/PreCompLayerModel.swift +39 -22
  133. package/{lottie-swift/src → Sources}/Private/Model/Layers/ShapeLayerModel.swift +23 -12
  134. package/{lottie-swift/src → Sources}/Private/Model/Layers/SolidLayerModel.swift +31 -19
  135. package/{lottie-swift/src → Sources}/Private/Model/Layers/TextLayerModel.swift +33 -19
  136. package/Sources/Private/Model/Objects/DashPattern.swift +44 -0
  137. package/Sources/Private/Model/Objects/Marker.swift +33 -0
  138. package/Sources/Private/Model/Objects/Mask.swift +80 -0
  139. package/Sources/Private/Model/Objects/Transform.swift +179 -0
  140. package/Sources/Private/Model/ShapeItems/Ellipse.swift +75 -0
  141. package/Sources/Private/Model/ShapeItems/Fill.swift +74 -0
  142. package/Sources/Private/Model/ShapeItems/GradientFill.swift +124 -0
  143. package/Sources/Private/Model/ShapeItems/GradientStroke.swift +186 -0
  144. package/Sources/Private/Model/ShapeItems/Group.swift +48 -0
  145. package/{lottie-swift/src → Sources}/Private/Model/ShapeItems/Merge.swift +29 -11
  146. package/Sources/Private/Model/ShapeItems/Rectangle.swift +73 -0
  147. package/Sources/Private/Model/ShapeItems/Repeater.swift +136 -0
  148. package/Sources/Private/Model/ShapeItems/Shape.swift +56 -0
  149. package/Sources/Private/Model/ShapeItems/ShapeItem.swift +163 -0
  150. package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +136 -0
  151. package/Sources/Private/Model/ShapeItems/Star.swift +132 -0
  152. package/Sources/Private/Model/ShapeItems/Stroke.swift +102 -0
  153. package/Sources/Private/Model/ShapeItems/Trim.swift +78 -0
  154. package/Sources/Private/Model/Text/Font.swift +61 -0
  155. package/{lottie-swift/src → Sources}/Private/Model/Text/Glyph.swift +63 -39
  156. package/Sources/Private/Model/Text/TextAnimator.swift +164 -0
  157. package/Sources/Private/Model/Text/TextDocument.swift +123 -0
  158. package/Sources/Private/RootAnimationLayer.swift +51 -0
  159. package/{lottie-swift/src → Sources}/Private/Utility/Debugging/AnimatorNodeDebugging.swift +7 -7
  160. package/{lottie-swift/src → Sources}/Private/Utility/Debugging/LayerDebugging.swift +72 -48
  161. package/Sources/Private/Utility/Debugging/TestHelpers.swift +10 -0
  162. package/{lottie-swift/src → Sources}/Private/Utility/Extensions/AnimationKeypathExtension.swift +69 -59
  163. package/Sources/Private/Utility/Extensions/BlendMode+Filter.swift +31 -0
  164. package/Sources/Private/Utility/Extensions/CGColor+RGB.swift +22 -0
  165. package/{lottie-swift/src → Sources}/Private/Utility/Extensions/CGFloatExtensions.swift +70 -67
  166. package/Sources/Private/Utility/Extensions/DataExtension.swift +27 -0
  167. package/Sources/Private/Utility/Extensions/MathKit.swift +450 -0
  168. package/Sources/Private/Utility/Extensions/StringExtensions.swift +38 -0
  169. package/{lottie-swift/src → Sources}/Private/Utility/Helpers/AnimationContext.swift +42 -16
  170. package/Sources/Private/Utility/Interpolatable/InterpolatableExtensions.swift +134 -0
  171. package/{lottie-swift/src → Sources}/Private/Utility/Interpolatable/KeyframeExtensions.swift +13 -10
  172. package/Sources/Private/Utility/Interpolatable/KeyframeGroup+Extensions.swift +59 -0
  173. package/{lottie-swift/src → Sources}/Private/Utility/Primitives/BezierPath.swift +229 -143
  174. package/Sources/Private/Utility/Primitives/CGPointExtension.swift +35 -0
  175. package/{lottie-swift/src → Sources}/Private/Utility/Primitives/ColorExtension.swift +46 -14
  176. package/{lottie-swift/src → Sources}/Private/Utility/Primitives/CompoundBezierPath.swift +58 -49
  177. package/Sources/Private/Utility/Primitives/CurveVertex.swift +184 -0
  178. package/Sources/Private/Utility/Primitives/PathElement.swift +75 -0
  179. package/Sources/Private/Utility/Primitives/UnitBezier.swift +115 -0
  180. package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +341 -0
  181. package/Sources/Public/Animation/AnimationPublic.swift +266 -0
  182. package/Sources/Public/Animation/AnimationView.swift +1335 -0
  183. package/Sources/Public/Animation/AnimationViewInitializers.swift +109 -0
  184. package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +22 -0
  185. package/{lottie-swift/src → Sources}/Public/AnimationCache/LRUAnimationCache.swift +22 -18
  186. package/Sources/Public/DynamicProperties/AnimationKeypath.swift +49 -0
  187. package/Sources/Public/DynamicProperties/AnyValueProvider.swift +132 -0
  188. package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +54 -36
  189. package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +40 -36
  190. package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +123 -0
  191. package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +40 -35
  192. package/{lottie-swift/src → Sources}/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +40 -35
  193. package/{lottie-swift/src → Sources}/Public/FontProvider/AnimationFontProvider.swift +15 -8
  194. package/Sources/Public/ImageProvider/AnimationImageProvider.swift +21 -0
  195. package/Sources/Public/Keyframes/Interpolatable.swift +253 -0
  196. package/Sources/Public/Keyframes/Keyframe.swift +92 -0
  197. package/Sources/Public/Logging/LottieLogger.swift +137 -0
  198. package/Sources/Public/LottieConfiguration.swift +143 -0
  199. package/{lottie-swift/src → Sources}/Public/Primitives/AnimationTime.swift +1 -1
  200. package/{lottie-swift/src → Sources}/Public/Primitives/Color.swift +10 -6
  201. package/{lottie-swift/src → Sources}/Public/Primitives/Vectors.swift +13 -12
  202. package/Sources/Public/TextProvider/AnimationTextProvider.swift +53 -0
  203. package/{lottie-swift/src → Sources}/Public/iOS/AnimatedButton.swift +42 -26
  204. package/{lottie-swift/src → Sources}/Public/iOS/AnimatedControl.swift +100 -91
  205. package/{lottie-swift/src → Sources}/Public/iOS/AnimatedSwitch.swift +128 -94
  206. package/{lottie-swift/src → Sources}/Public/iOS/AnimationSubview.swift +3 -3
  207. package/Sources/Public/iOS/AnimationViewBase.swift +78 -0
  208. package/{lottie-swift/src → Sources}/Public/iOS/BundleImageProvider.swift +36 -34
  209. package/{lottie-swift/src → Sources}/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift +4 -0
  210. package/{lottie-swift/src → Sources}/Public/iOS/Compatibility/CompatibleAnimationView.swift +38 -29
  211. package/{lottie-swift/src → Sources}/Public/iOS/FilepathImageProvider.swift +24 -21
  212. package/{lottie-swift/src → Sources}/Public/iOS/UIColorExtension.swift +4 -4
  213. package/{lottie-swift/src/Public/MacOS/AnimationSubview.swift → Sources/Public/macOS/AnimationSubview.macOS.swift} +4 -5
  214. package/{lottie-swift/src/Public/MacOS/LottieView.swift → Sources/Public/macOS/AnimationViewBase.macOS.swift} +58 -50
  215. package/{lottie-swift/src/Public/MacOS/BundleImageProvider.swift → Sources/Public/macOS/BundleImageProvider.macOS.swift} +31 -32
  216. package/Sources/Public/macOS/FilepathImageProvider.macOS.swift +67 -0
  217. package/Tests/AnimationKeypathTests.swift +94 -0
  218. package/Tests/AutomaticEngineTests.swift +57 -0
  219. package/Tests/BundleTests.swift +25 -0
  220. package/Tests/DataURLTests.swift +64 -0
  221. package/Tests/ParsingTests.swift +43 -0
  222. package/Tests/PerformanceTests.swift +215 -0
  223. package/Tests/SnapshotConfiguration.swift +153 -0
  224. package/Tests/SnapshotTests.swift +265 -0
  225. package/Tests/Utils/Bundle+Module.swift +30 -0
  226. package/Tests/Utils/HardcodedFontProvider.swift +19 -0
  227. package/Tests/Utils/HardcodedImageProvider.swift +23 -0
  228. package/Tests/Utils/Snapshotting+presentationLayer.swift +47 -0
  229. package/Tests/ValueProvidersTests.swift +27 -0
  230. package/lottie-ios.podspec +11 -12
  231. package/package.json +1 -1
  232. package/script/test-carthage/Cartfile +1 -0
  233. package/script/test-carthage/Cartfile.resolved +1 -0
  234. package/script/test-carthage/CarthageTest/AppDelegate.swift +26 -0
  235. package/script/test-carthage/CarthageTest/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
  236. package/script/test-carthage/CarthageTest/Assets.xcassets/AppIcon.appiconset/Contents.json +98 -0
  237. package/script/test-carthage/CarthageTest/Assets.xcassets/Contents.json +6 -0
  238. package/script/test-carthage/CarthageTest/Base.lproj/LaunchScreen.storyboard +25 -0
  239. package/script/test-carthage/CarthageTest/Base.lproj/Main.storyboard +24 -0
  240. package/script/test-carthage/CarthageTest/Info.plist +66 -0
  241. package/script/test-carthage/CarthageTest/SceneDelegate.swift +10 -0
  242. package/script/test-carthage/CarthageTest/ViewController.swift +15 -0
  243. package/script/test-carthage/CarthageTest-macOS/AppDelegate.swift +7 -0
  244. package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
  245. package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json +58 -0
  246. package/script/test-carthage/CarthageTest-macOS/Assets.xcassets/Contents.json +6 -0
  247. package/script/test-carthage/CarthageTest-macOS/Base.lproj/Main.storyboard +717 -0
  248. package/script/test-carthage/CarthageTest-macOS/CarthageTest_macOS.entitlements +10 -0
  249. package/script/test-carthage/CarthageTest-macOS/ViewController.swift +15 -0
  250. package/script/test-carthage/CarthageTest.xcodeproj/project.pbxproj +532 -0
  251. package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  252. package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  253. package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  254. package/script/test-carthage/CarthageTest.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  255. package/script/test-carthage/CarthageTest.xcodeproj/xcuserdata/cal.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  256. package/script/test-carthage/CarthageTest.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +19 -0
  257. package/script/test-carthage/Mintfile +1 -0
  258. package/script/test-spm/LottieSPM.xcworkspace/contents.xcworkspacedata +7 -0
  259. package/script/test-spm/LottieSPM.xcworkspace/xcuserdata/cal.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  260. package/script/test-spm/Mintfile +1 -0
  261. package/.swiftpm/xcode/package.xcworkspace/xcuserdata/brandonwithrow.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  262. package/Lottie/Info.plist +0 -22
  263. package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/brandonwithrow.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  264. package/Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad/xcschemes/LottieLibraryMacOS.xcscheme +0 -80
  265. package/Lottie.xcodeproj/xcuserdata/brandonwithrow.xcuserdatad/xcschemes/xcschememanagement.plist +0 -57
  266. package/lottie-swift/Assets/.gitkeep +0 -0
  267. package/lottie-swift/src/Private/LayerContainers/Utility/LayerFontProvider.swift +0 -37
  268. package/lottie-swift/src/Private/LayerContainers/Utility/LayerTextProvider.swift +0 -36
  269. package/lottie-swift/src/Private/Model/Animation.swift +0 -107
  270. package/lottie-swift/src/Private/Model/Assets/Asset.swift +0 -27
  271. package/lottie-swift/src/Private/Model/Assets/ImageAsset.swift +0 -48
  272. package/lottie-swift/src/Private/Model/Keyframes/Keyframe.swift +0 -128
  273. package/lottie-swift/src/Private/Model/Keyframes/KeyframeGroup.swift +0 -108
  274. package/lottie-swift/src/Private/Model/Layers/LayerModel.swift +0 -150
  275. package/lottie-swift/src/Private/Model/Objects/DashPattern.swift +0 -24
  276. package/lottie-swift/src/Private/Model/Objects/Marker.swift +0 -23
  277. package/lottie-swift/src/Private/Model/Objects/Mask.swift +0 -48
  278. package/lottie-swift/src/Private/Model/Objects/Transform.swift +0 -105
  279. package/lottie-swift/src/Private/Model/ShapeItems/Ellipse.swift +0 -50
  280. package/lottie-swift/src/Private/Model/ShapeItems/FillI.swift +0 -49
  281. package/lottie-swift/src/Private/Model/ShapeItems/GradientFill.swift +0 -86
  282. package/lottie-swift/src/Private/Model/ShapeItems/GradientStroke.swift +0 -125
  283. package/lottie-swift/src/Private/Model/ShapeItems/Group.swift +0 -32
  284. package/lottie-swift/src/Private/Model/ShapeItems/Rectangle.swift +0 -50
  285. package/lottie-swift/src/Private/Model/ShapeItems/Repeater.swift +0 -80
  286. package/lottie-swift/src/Private/Model/ShapeItems/Shape.swift +0 -37
  287. package/lottie-swift/src/Private/Model/ShapeItems/ShapeItem.swift +0 -95
  288. package/lottie-swift/src/Private/Model/ShapeItems/ShapeTransform.swift +0 -68
  289. package/lottie-swift/src/Private/Model/ShapeItems/Star.swift +0 -86
  290. package/lottie-swift/src/Private/Model/ShapeItems/Stroke.swift +0 -67
  291. package/lottie-swift/src/Private/Model/ShapeItems/Trim.swift +0 -53
  292. package/lottie-swift/src/Private/Model/Text/Font.swift +0 -35
  293. package/lottie-swift/src/Private/Model/Text/TextAnimator.swift +0 -99
  294. package/lottie-swift/src/Private/Model/Text/TextDocument.swift +0 -70
  295. package/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +0 -109
  296. package/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +0 -132
  297. package/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift +0 -141
  298. package/lottie-swift/src/Private/Utility/Extensions/MathKit.swift +0 -539
  299. package/lottie-swift/src/Private/Utility/Extensions/StringExtensions.swift +0 -32
  300. package/lottie-swift/src/Private/Utility/Interpolatable/Interpolatable.swift +0 -18
  301. package/lottie-swift/src/Private/Utility/Interpolatable/InterpolatableExtensions.swift +0 -170
  302. package/lottie-swift/src/Private/Utility/Primitives/CurveVertex.swift +0 -177
  303. package/lottie-swift/src/Private/Utility/Primitives/PathElement.swift +0 -68
  304. package/lottie-swift/src/Private/Utility/Primitives/VectorsExtensions.swift +0 -218
  305. package/lottie-swift/src/Public/Animation/AnimationPublic.swift +0 -196
  306. package/lottie-swift/src/Public/Animation/AnimationView.swift +0 -1006
  307. package/lottie-swift/src/Public/Animation/AnimationViewInitializers.swift +0 -83
  308. package/lottie-swift/src/Public/AnimationCache/AnimationCacheProvider.swift +0 -24
  309. package/lottie-swift/src/Public/DynamicProperties/AnimationKeypath.swift +0 -46
  310. package/lottie-swift/src/Public/DynamicProperties/AnyValueProvider.swift +0 -29
  311. package/lottie-swift/src/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +0 -114
  312. package/lottie-swift/src/Public/ImageProvider/AnimationImageProvider.swift +0 -23
  313. package/lottie-swift/src/Public/MacOS/FilepathImageProvider.swift +0 -67
  314. package/lottie-swift/src/Public/TextProvider/AnimationTextProvider.swift +0 -39
  315. package/lottie-swift/src/Public/iOS/LottieView.swift +0 -62
  316. package/lottie-swift-testing.podspec +0 -32
@@ -0,0 +1,67 @@
1
+ //
2
+ // FilepathImageProvider.swift
3
+ // lottie-swift
4
+ //
5
+ // Created by Brandon Withrow on 2/1/19.
6
+ //
7
+
8
+ #if os(macOS)
9
+ import AppKit
10
+
11
+ /// An `AnimationImageProvider` that provides images by name from a specific filepath.
12
+ public class FilepathImageProvider: AnimationImageProvider {
13
+
14
+ // MARK: Lifecycle
15
+
16
+ /// Initializes an image provider with a specific filepath.
17
+ ///
18
+ /// - Parameter filepath: The absolute filepath containing the images.
19
+ ///
20
+ public init(filepath: String) {
21
+ self.filepath = URL(fileURLWithPath: filepath)
22
+ }
23
+
24
+ public init(filepath: URL) {
25
+ self.filepath = filepath
26
+ }
27
+
28
+ // MARK: Public
29
+
30
+ public func imageForAsset(asset: ImageAsset) -> CGImage? {
31
+ if
32
+ asset.name.hasPrefix("data:"),
33
+ let url = URL(string: asset.name),
34
+ let data = try? Data(contentsOf: url),
35
+ let image = NSImage(data: data)
36
+ {
37
+ return image.lottie_CGImage
38
+ }
39
+
40
+ let directPath = filepath.appendingPathComponent(asset.name).path
41
+ if FileManager.default.fileExists(atPath: directPath) {
42
+ return NSImage(contentsOfFile: directPath)?.lottie_CGImage
43
+ }
44
+
45
+ let pathWithDirectory = filepath.appendingPathComponent(asset.directory).appendingPathComponent(asset.name).path
46
+ if FileManager.default.fileExists(atPath: pathWithDirectory) {
47
+ return NSImage(contentsOfFile: pathWithDirectory)?.lottie_CGImage
48
+ }
49
+
50
+ LottieLogger.shared.assertionFailure("Could not find image \"\(asset.name)\" in bundle")
51
+ return nil
52
+ }
53
+
54
+ // MARK: Internal
55
+
56
+ let filepath: URL
57
+ }
58
+
59
+ extension NSImage {
60
+ @nonobjc
61
+ var lottie_CGImage: CGImage? {
62
+ guard let imageData = tiffRepresentation else { return nil }
63
+ guard let sourceData = CGImageSourceCreateWithData(imageData as CFData, nil) else { return nil }
64
+ return CGImageSourceCreateImageAtIndex(sourceData, 0, nil)
65
+ }
66
+ }
67
+ #endif
@@ -0,0 +1,94 @@
1
+ // Created by Cal Stephens on 1/24/22.
2
+ // Copyright © 2022 Airbnb Inc. All rights reserved.
3
+
4
+ import SnapshotTesting
5
+ import XCTest
6
+
7
+ @testable import Lottie
8
+
9
+ final class AnimationKeypathTests: XCTestCase {
10
+
11
+ // MARK: Internal
12
+
13
+ func testKeypathMatches() {
14
+ let keypath = AnimationKeypath(keypath: "Layer.Shape Group.Stroke 1.Color")
15
+
16
+ XCTAssertTrue(keypath.matches("Layer.Shape Group.Stroke 1.Color"))
17
+ XCTAssertTrue(keypath.matches("**.Color"))
18
+ XCTAssertTrue(keypath.matches("**.Stroke 1.Color"))
19
+ XCTAssertTrue(keypath.matches("**.Shape Group.Stroke 1.Color"))
20
+ XCTAssertTrue(keypath.matches("Layer.**.Color"))
21
+ XCTAssertTrue(keypath.matches("Layer.Shape Group.*.Color"))
22
+ XCTAssertTrue(keypath.matches("Layer.*.*.Color"))
23
+
24
+ XCTAssertFalse(keypath.matches("Layer.*.Color"))
25
+ XCTAssertFalse(keypath.matches("**.Layer.Shape Group.Stroke 1.Color"))
26
+ XCTAssertFalse(keypath.matches("*.Layer.Shape Group.Stroke 1.Color"))
27
+ XCTAssertFalse(keypath.matches("Layer.Shape Group.Stroke 1.Color.*"))
28
+ XCTAssertFalse(keypath.matches("Layer.Shape Group.Stroke 1.Color.**"))
29
+ }
30
+
31
+ func testLayerForKeypath() {
32
+ let animationView = AnimationView(
33
+ animation: Samples.animation(named: "Boat_Loader"),
34
+ configuration: LottieConfiguration(renderingEngine: .mainThread))
35
+
36
+ XCTAssertNotNil(animationView.animationLayer?.layer(for: "Success.FishComplete.Fish1Tail 7"))
37
+ XCTAssertNotNil(animationView.animationLayer?.layer(for: "Success.FishComplete"))
38
+ XCTAssertNotNil(animationView.animationLayer?.layer(for: "Success"))
39
+ XCTAssertNotNil(animationView.animationLayer?.layer(for: "Success.*.Fish1Tail 7"))
40
+ }
41
+
42
+ func testMainThreadEngineKeypathLogging() {
43
+ snapshotHierarchyKeypaths(
44
+ animationName: "Switch",
45
+ configuration: LottieConfiguration(renderingEngine: .mainThread))
46
+ }
47
+
48
+ func testCoreAnimationEngineKeypathLogging() {
49
+ snapshotHierarchyKeypaths(
50
+ animationName: "Switch",
51
+ configuration: LottieConfiguration(renderingEngine: .coreAnimation))
52
+ }
53
+
54
+ /// The Core Animation engine supports a subset of the keypaths supported by the Main Thread engine.
55
+ /// All keypaths that are supported in the Core Animation engine should also be supported by the Main Thread engine.
56
+ func testCoreAnimationEngineKeypathCompatibility() {
57
+ let mainThreadKeypaths = Set(hierarchyKeypaths(animationName: "Switch", configuration: .init(renderingEngine: .mainThread)))
58
+ let coreAnimationKeypaths = hierarchyKeypaths(animationName: "Switch", configuration: .init(renderingEngine: .coreAnimation))
59
+
60
+ for coreAnimationKeypath in coreAnimationKeypaths {
61
+ XCTAssert(mainThreadKeypaths.contains(coreAnimationKeypath))
62
+ }
63
+ }
64
+
65
+ // MARK: Private
66
+
67
+ private func snapshotHierarchyKeypaths(
68
+ animationName: String,
69
+ configuration: LottieConfiguration,
70
+ function: String = #function,
71
+ line: UInt = #line)
72
+ {
73
+ let hierarchyKeypaths = hierarchyKeypaths(animationName: animationName, configuration: configuration)
74
+
75
+ assertSnapshot(
76
+ matching: hierarchyKeypaths.sorted().joined(separator: "\n"),
77
+ as: .description,
78
+ named: animationName,
79
+ testName: function,
80
+ line: line)
81
+ }
82
+
83
+ private func hierarchyKeypaths(animationName: String, configuration: LottieConfiguration) -> [String] {
84
+ var printedMessages = [String]()
85
+ let logger = LottieLogger(info: { message in
86
+ printedMessages.append(message())
87
+ })
88
+
89
+ let animationView = SnapshotConfiguration.makeAnimationView(for: animationName, configuration: configuration, logger: logger)
90
+ animationView?.logHierarchyKeypaths()
91
+ return Array(printedMessages[1...])
92
+ }
93
+
94
+ }
@@ -0,0 +1,57 @@
1
+ // Created by Cal Stephens on 5/2/22.
2
+ // Copyright © 2022 Airbnb Inc. All rights reserved.
3
+
4
+ import SnapshotTesting
5
+ import UIKit
6
+ import XCTest
7
+
8
+ @testable import Lottie
9
+
10
+ final class AutomaticEngineTests: XCTestCase {
11
+
12
+ /// Snapshot tests for whether or not each sample animation supports the Core Animation engine
13
+ func testAutomaticEngineDetection() throws {
14
+ for sampleAnimationName in Samples.sampleAnimationNames {
15
+ guard let animation = Samples.animation(named: sampleAnimationName) else { continue }
16
+
17
+ var compatibilityIssues = [CompatibilityIssue]()
18
+
19
+ let animationLayer = try XCTUnwrap(CoreAnimationLayer(
20
+ animation: animation,
21
+ imageProvider: BundleImageProvider(bundle: Bundle.main, searchPath: nil),
22
+ fontProvider: DefaultFontProvider(),
23
+ compatibilityTrackerMode: .track,
24
+ logger: .shared))
25
+
26
+ animationLayer.didSetUpAnimation = { issues in
27
+ compatibilityIssues = issues
28
+ }
29
+
30
+ animationLayer.bounds = CGRect(origin: .zero, size: animation.size)
31
+ animationLayer.layoutIfNeeded()
32
+ animationLayer.display()
33
+
34
+ let compatibilityReport: String
35
+ if compatibilityIssues.isEmpty {
36
+ compatibilityReport = "Supports Core Animation engine"
37
+ } else {
38
+ compatibilityReport = "Does not support Core Animation engine. Encountered compatibility issues:\n"
39
+ + compatibilityIssues.map { $0.description }.joined(separator: "\n")
40
+ }
41
+
42
+ assertSnapshot(
43
+ matching: compatibilityReport,
44
+ as: .description,
45
+ named: sampleAnimationName)
46
+ }
47
+ }
48
+
49
+ override func setUp() {
50
+ LottieLogger.shared = .printToConsole
51
+ }
52
+
53
+ override func tearDown() {
54
+ LottieLogger.shared = LottieLogger()
55
+ }
56
+
57
+ }
@@ -0,0 +1,25 @@
1
+ //
2
+ // BundleTests.swift
3
+ // LottieTests
4
+ //
5
+ // Created by Marcelo Fabri on 5/5/22.
6
+ //
7
+
8
+ import XCTest
9
+
10
+ @testable import Lottie
11
+
12
+ final class BundleTests: XCTestCase {
13
+
14
+ var bundle: Bundle { .module }
15
+
16
+ func testGetAnimationDataWithSuffix() throws {
17
+ let data = try bundle.getAnimationData("HamburgerArrow.json", subdirectory: "Samples")
18
+ XCTAssertNotNil(data)
19
+ }
20
+
21
+ func testGetAnimationDataWithoutSuffix() throws {
22
+ let data = try bundle.getAnimationData("HamburgerArrow", subdirectory: "Samples")
23
+ XCTAssertNotNil(data)
24
+ }
25
+ }
@@ -0,0 +1,64 @@
1
+ // Created by Nicholas Mata on 2/23/22.
2
+ // Copyright © 2022 Airbnb Inc. All rights reserved.
3
+
4
+ import Foundation
5
+ import XCTest
6
+
7
+ @testable import Lottie
8
+
9
+ // MARK: - DataURLTests
10
+
11
+ // Tests are based on implementation found here
12
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
13
+ final class DataURLTests: XCTestCase {
14
+
15
+ let red5x5 =
16
+ "%89%50%4e%47%0d%0a%1a%0a%00%00%00%0d%49%48%44%52%00%00%00%05%00%00%00%05%08%06%00%00%00%8d%6f%26%e5%00%00%00%12%49%44%41%54%78%da%63%fc%cf%c0%00%44%a8%80%91%06%82%00%5c%65%09%fc%86%fe%00%b0%00%00%00%00%49%45%4e%44%ae%42%60%82"
17
+
18
+ let red5x5Base64 = "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAEklEQVR42mP8z8AARKiAkQaCAFxlCfyG/gCwAAAAAElFTkSuQmCC"
19
+
20
+ func testValidDataURL() {
21
+ let dataString = "data:image/png;base64,\(red5x5Base64)"
22
+
23
+ let data = Data(dataString: dataString)
24
+ XCTAssertNotNil(data, "Data should not be nil if valid base64 string")
25
+ let image = UIImage(data: data!)
26
+ XCTAssertNotNil(image, "Should be valid image")
27
+
28
+ // Since legacy options will print nil host logs
29
+ let legacyData = Data(dataString: dataString, options: .legacy)
30
+ XCTAssertNotNil(legacyData, "Data should not be nil if valid base64 string")
31
+ let legacyImage = UIImage(data: legacyData!)
32
+ XCTAssertNotNil(legacyImage, "Should be valid image")
33
+
34
+ XCTAssertEqual(data, legacyData)
35
+ }
36
+
37
+ func testValidDataURLWithoutBase64() {
38
+ let dataString = "data:image/png,\(red5x5)"
39
+ // Since ;base64 is missing still prints nil host warnings.
40
+ // If we can figure out how to turn red5x5 into Data properly
41
+ // like Data(contentsOf:) does then we can avoid the warning.
42
+ let data = Data(dataString: dataString)
43
+ XCTAssertNotNil(data, "Data should not be nil since format is valid data URL")
44
+
45
+ let image = UIImage(data: data!)
46
+ XCTAssertNotNil(image, "Should be valid image. Since missing ';base64' the data is valid just not base64 encoded")
47
+ }
48
+
49
+ func testInvalidDataURLWithBadBase64() {
50
+ let dataString = "data:image/png;base64,INVALIDBASE64"
51
+
52
+ let data = Data(dataString: dataString)
53
+ let legacyData = Data(dataString: dataString, options: .legacy)
54
+ XCTAssertNil(data, "Data should be nil because 'INVALIDBASE64' is not valid base64 string.")
55
+ XCTAssertNil(legacyData, "Data should be nil because 'INVALIDBASE64' is not valid base64 string.")
56
+ }
57
+
58
+ func testInvalidDataURL() {
59
+ let dataString = "ImageAssetName"
60
+
61
+ let data = Data(dataString: dataString)
62
+ XCTAssertNil(data, "Data should be nil as valid Data URL starts with 'data:'")
63
+ }
64
+ }
@@ -0,0 +1,43 @@
1
+ //
2
+ // ParsingTests.swift
3
+ // Lottie
4
+ //
5
+ // Created by Marcelo Fabri on 5/5/22.
6
+ //
7
+
8
+ import Difference
9
+ import Foundation
10
+ import Lottie
11
+ import XCTest
12
+
13
+ // MARK: - ParsingTests
14
+
15
+ final class ParsingTests: XCTestCase {
16
+
17
+ func testParsingIsTheSameForBothImplementations() throws {
18
+ for url in Samples.sampleAnimationURLs {
19
+ do {
20
+ let data = try Data(contentsOf: url)
21
+ let codableAnimation = try Animation.from(data: data, strategy: .codable)
22
+ let dictAnimation = try Animation.from(data: data, strategy: .dictionaryBased)
23
+
24
+ XCTAssertNoDiff(codableAnimation, dictAnimation)
25
+ } catch {
26
+ XCTFail("Error for \(url.lastPathComponent): \(error)")
27
+ }
28
+ }
29
+ }
30
+ }
31
+
32
+ func XCTAssertNoDiff<T>(
33
+ _ expected: @autoclosure () throws -> T,
34
+ _ received: @autoclosure () throws -> T,
35
+ file: StaticString = #filePath,
36
+ line: UInt = #line) rethrows
37
+ {
38
+ let expected = try expected()
39
+ let received = try received()
40
+ let diff = diff(expected, received)
41
+ let isEqual = diff.isEmpty || diff.allSatisfy(\.isEmpty)
42
+ XCTAssertTrue(isEqual, "Found difference for \n" + diff.joined(separator: ", "), file: file, line: line)
43
+ }
@@ -0,0 +1,215 @@
1
+ // Created by Cal Stephens on 2/14/22.
2
+ // Copyright © 2022 Airbnb Inc. All rights reserved.
3
+
4
+ import Foundation
5
+ import XCTest
6
+
7
+ @testable import Lottie
8
+
9
+ // MARK: - PerformanceTests
10
+
11
+ final class PerformanceTests: XCTestCase {
12
+
13
+ // MARK: Internal
14
+
15
+ func testAnimationViewSetup_simpleAnimation() {
16
+ // Compare the performance of displaying this simple animation in the two animation engines
17
+ let ratio = compareEngineSetupPerformance(
18
+ of: .mainThread,
19
+ with: .coreAnimation,
20
+ for: simpleAnimation,
21
+ iterations: 2000)
22
+
23
+ // This is basically a snapshot test for the performance of the Core Animation engine
24
+ // compared to the Main Thread engine. Currently, the Core Animation engine is
25
+ // about the same speed as the Main Thread engine in this example.
26
+ XCTAssertEqual(ratio, 1.0, accuracy: 0.35)
27
+ }
28
+
29
+ func testAnimationViewSetup_complexAnimation() {
30
+ // Compare the performance of displaying this simple animation in the two animation engines
31
+ let ratio = compareEngineSetupPerformance(
32
+ of: .mainThread,
33
+ with: .coreAnimation,
34
+ for: complexAnimation,
35
+ iterations: 500)
36
+
37
+ // The Core Animation engine is currently about 1.5x slower than the
38
+ // Main Thread engine in this example.
39
+ XCTAssertEqual(ratio, 1.5, accuracy: 0.6)
40
+ }
41
+
42
+ func testAnimationViewSetup_automaticEngine() {
43
+ // Compare the performance of displaying this simple animation with the core animation engine
44
+ // vs with the automatic engine option
45
+ let ratio = compareEngineSetupPerformance(
46
+ of: .coreAnimation,
47
+ with: .automatic,
48
+ for: simpleAnimation,
49
+ iterations: 2000)
50
+
51
+ // The automatic engine option should have the same performance as the core animation engine,
52
+ // when rendering an animation supported by the CA engine.
53
+ XCTAssertEqual(ratio, 1.0, accuracy: 0.2)
54
+ }
55
+
56
+ func testAnimationViewScrubbing_simpleAnimation() {
57
+ let ratio = compareEngineScrubbingPerformance(for: simpleAnimation, iterations: 2000)
58
+ XCTAssertEqual(ratio, 0.01, accuracy: 0.01)
59
+ }
60
+
61
+ func testAnimationViewScrubbing_complexAnimation() {
62
+ let ratio = compareEngineScrubbingPerformance(for: complexAnimation, iterations: 2000)
63
+ XCTAssertEqual(ratio, 0.01, accuracy: 0.01)
64
+ }
65
+
66
+ func testParsing_simpleAnimation() throws {
67
+ let data = try XCTUnwrap(Bundle.module.getAnimationData("loading_dots_1", subdirectory: "Samples/LottieFiles"))
68
+ let ratio = try compareDeserializationPerformance(data: data, iterations: 2000)
69
+ XCTAssertEqual(ratio, 2, accuracy: 0.55)
70
+ }
71
+
72
+ func testParsing_complexAnimation() throws {
73
+ let data = try XCTUnwrap(Bundle.module.getAnimationData("LottieLogo2", subdirectory: "Samples"))
74
+ let ratio = try compareDeserializationPerformance(data: data, iterations: 500)
75
+ XCTAssertEqual(ratio, 1.7, accuracy: 0.6)
76
+ }
77
+
78
+ override func setUp() {
79
+ TestHelpers.performanceTestsAreRunning = true
80
+ }
81
+
82
+ override func tearDown() {
83
+ TestHelpers.performanceTestsAreRunning = false
84
+ }
85
+
86
+ // MARK: Private
87
+
88
+ private let simpleAnimation = Animation.named(
89
+ "loading_dots_1",
90
+ bundle: .module,
91
+ subdirectory: "Samples/LottieFiles")!
92
+
93
+ private let complexAnimation = Animation.named(
94
+ "LottieLogo2",
95
+ bundle: .module,
96
+ subdirectory: "Samples")!
97
+
98
+ /// Compares initializing the given animation with the two given engines,
99
+ /// and returns the ratio of how much slower engine B is than engine A.
100
+ private func compareEngineSetupPerformance(
101
+ of engineA: RenderingEngineOption,
102
+ with engineB: RenderingEngineOption,
103
+ for animation: Animation,
104
+ iterations: Int)
105
+ -> Double
106
+ {
107
+ let engineAPerformance = measurePerformance {
108
+ for _ in 0..<iterations {
109
+ // Each animation setup needs to be wrapped in its own `CATransaction`
110
+ // in order for the layers to be deallocated immediately. Otherwise
111
+ // the layers aren't deallocated until the end of the test run,
112
+ // which causes memory usage to grow unbounded.
113
+ CATransaction.begin()
114
+ let animationView = setupAnimationView(with: animation, configuration: .init(renderingEngine: engineA))
115
+ // Call `display()` on the layer to make sure any pending setup occurs immediately
116
+ animationView.animationLayer!.display()
117
+ CATransaction.commit()
118
+ }
119
+ }
120
+
121
+ LottieLogger.shared.info("\(engineA) engine took \(engineAPerformance) seconds")
122
+
123
+ let engineBPerformance = measurePerformance {
124
+ for _ in 0..<iterations {
125
+ // Each animation setup needs to be wrapped in its own `CATransaction`
126
+ // in order for the layers to be deallocated immediately. Otherwise
127
+ // the layers aren't deallocated until the end of the test run,
128
+ // which causes memory usage to grow unbounded.
129
+ CATransaction.begin()
130
+ let animationView = setupAnimationView(with: animation, configuration: .init(renderingEngine: engineB))
131
+ // Call `display()` on the layer to make sure any pending setup occurs immediately
132
+ animationView.animationLayer!.display()
133
+ CATransaction.commit()
134
+ }
135
+ }
136
+
137
+ LottieLogger.shared.info("\(engineB) engine took \(engineBPerformance) seconds")
138
+
139
+ let ratio = engineBPerformance / engineAPerformance
140
+ LottieLogger.shared.info("\(engineB) engine took \(ratio)x as long as \(engineA) engine")
141
+ return ratio
142
+ }
143
+
144
+ /// Compares performance of scrubbing the given animation with both the Main Thread and Core Animation engine,
145
+ /// and returns the ratio of how much slower the Core Animation is than the Main Thread engine
146
+ private func compareEngineScrubbingPerformance(for animation: Animation, iterations: Int) -> Double {
147
+ let mainThreadAnimationView = setupAnimationView(with: animation, configuration: .init(renderingEngine: .mainThread))
148
+ let mainThreadEnginePerformance = measurePerformance {
149
+ for i in 0..<iterations {
150
+ mainThreadAnimationView.currentProgress = Double(i) / Double(iterations)
151
+
152
+ // Since the main thread engine only re-renders in `display()`, which is normally called by Core Animation,
153
+ // we have to invoke it manually. This triggers the same code path that would happen when scrubbing in
154
+ // a real use case.
155
+ mainThreadAnimationView.animationLayer!.display()
156
+ }
157
+ }
158
+
159
+ LottieLogger.shared.info("Main thread engine took \(mainThreadEnginePerformance) seconds")
160
+
161
+ let coreAnimationView = setupAnimationView(with: animation, configuration: .init(renderingEngine: .coreAnimation))
162
+ let coreAnimationEnginePerformance = measurePerformance {
163
+ for i in 0..<iterations {
164
+ coreAnimationView.currentProgress = Double(i) / Double(iterations)
165
+
166
+ // Call `display()` on the layer to make sure any pending setup occurs immediately
167
+ coreAnimationView.animationLayer!.display()
168
+ }
169
+ }
170
+
171
+ LottieLogger.shared.info("Core Animation engine took \(coreAnimationEnginePerformance) seconds")
172
+
173
+ let ratio = coreAnimationEnginePerformance / mainThreadEnginePerformance
174
+ LottieLogger.shared.info("Core Animation engine took \(ratio)x as long as the Main Thread engine")
175
+ return ratio
176
+ }
177
+
178
+ private func compareDeserializationPerformance(data: Data, iterations: Int) throws -> Double {
179
+ let codablePerformance = try measurePerformance {
180
+ for _ in 0..<iterations {
181
+ _ = try Animation.from(data: data, strategy: .codable)
182
+ }
183
+ }
184
+
185
+ LottieLogger.shared.info("Codable deserialization took \(codablePerformance) seconds")
186
+
187
+ let dictPerformance = try measurePerformance {
188
+ for _ in 0..<iterations {
189
+ _ = try Animation.from(data: data, strategy: .dictionaryBased)
190
+ }
191
+ }
192
+
193
+ LottieLogger.shared.info("DictionaryBased deserialization took \(dictPerformance) seconds")
194
+ let ratio = codablePerformance / dictPerformance
195
+ LottieLogger.shared.info("Codable deserialization took \(ratio)x as long as DictionaryBased")
196
+ return ratio
197
+ }
198
+
199
+ @discardableResult
200
+ private func setupAnimationView(with animation: Animation, configuration: LottieConfiguration) -> AnimationView {
201
+ let animationView = AnimationView(animation: animation, configuration: configuration)
202
+ animationView.frame.size = CGSize(width: animation.width, height: animation.height)
203
+ animationView.layoutIfNeeded()
204
+ return animationView
205
+ }
206
+
207
+ private func measurePerformance(_ block : () throws -> Void) rethrows -> TimeInterval {
208
+ let start = DispatchTime.now()
209
+ try block()
210
+ let end = DispatchTime.now()
211
+ let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds
212
+ return Double(nanoTime) / 1_000_000_000
213
+ }
214
+
215
+ }