lottie-ios 4.2.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) 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 +1542 -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 +346 -102
  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/DropShadowAnimation.swift +160 -0
  20. package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +83 -10
  21. package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +85 -79
  22. package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +7 -7
  23. package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +67 -6
  24. package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +2 -2
  25. package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +9 -0
  26. package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +1 -0
  27. package/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift +1 -1
  28. package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +2 -0
  29. package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +11 -13
  30. package/Sources/Private/CoreAnimation/Layers/SolidLayer.swift +20 -5
  31. package/Sources/Private/CoreAnimation/Layers/TextLayer.swift +13 -3
  32. package/Sources/Private/CoreAnimation/ValueProviderStore.swift +6 -4
  33. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Collection+Diff.swift +263 -0
  34. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Diffable.swift +18 -0
  35. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/DiffableSection.swift +16 -0
  36. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/IndexChangeset.swift +187 -0
  37. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/SectionedChangeset.swift +32 -0
  38. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Logging/EpoxyLogger.swift +99 -0
  39. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/CallbackContextEpoxyModeled.swift +8 -0
  40. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelArrayBuilder.swift +48 -0
  41. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelProperty.swift +158 -0
  42. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelStorage.swift +88 -0
  43. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModeled.swift +54 -0
  44. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/AnyEpoxyModelProperty.swift +29 -0
  45. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/ClassReference.swift +39 -0
  46. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/AnimatedProviding.swift +10 -0
  47. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DataIDProviding.swift +57 -0
  48. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidDisplayProviding.swift +41 -0
  49. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidEndDisplayingProviding.swift +41 -0
  50. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidSelectProviding.swift +36 -0
  51. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ErasedContentProviding.swift +49 -0
  52. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/MakeViewProviding.swift +60 -0
  53. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetBehaviorsProviding.swift +38 -0
  54. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetContentProviding.swift +38 -0
  55. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/StyleIDProviding.swift +37 -0
  56. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/TraitCollectionProviding.swift +14 -0
  57. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewDifferentiatorProviding.swift +34 -0
  58. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewProviding.swift +13 -0
  59. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/WillDisplayProviding.swift +41 -0
  60. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/ViewEpoxyModeled.swift +10 -0
  61. package/Sources/Private/EmbeddedLibraries/EpoxyCore/README.md +31 -0
  62. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingController.swift +46 -0
  63. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingView.swift +391 -0
  64. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIIntrinsicContentSizeInvalidator.swift +44 -0
  65. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUILayoutMargins.swift +51 -0
  66. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxyableView+SwiftUIView.swift +172 -0
  67. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/MeasuringViewRepresentable.swift +128 -0
  68. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift +452 -0
  69. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/SwiftUIView.swift +148 -0
  70. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIView+SwiftUIView.swift +40 -0
  71. package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIViewConfiguringSwiftUIView.swift +43 -0
  72. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/BehaviorsConfigurableView.swift +45 -0
  73. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ContentConfigurableView.swift +36 -0
  74. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/EpoxyableView.swift +5 -0
  75. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/StyledView.swift +42 -0
  76. package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ViewType.swift +51 -0
  77. package/Sources/Private/EmbeddedLibraries/README.md +27 -0
  78. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+BackingConfiguration.swift +4 -4
  79. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+MemoryFile.swift +2 -2
  80. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Writing.swift +4 -4
  81. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive.swift +8 -7
  82. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Compression.swift +5 -5
  83. package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/FileManager+ZIP.swift +4 -4
  84. package/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.swift +6 -0
  85. package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +3 -1
  86. package/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift +18 -5
  87. package/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift +31 -3
  88. package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +33 -8
  89. package/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift +8 -1
  90. package/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift +13 -4
  91. package/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift +2 -2
  92. package/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.swift +1 -0
  93. package/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift +4 -4
  94. package/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +1 -1
  95. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +3 -1
  96. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +2 -1
  97. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift +2 -1
  98. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +1 -1
  99. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +2 -2
  100. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +1 -1
  101. package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +1 -1
  102. package/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +6 -2
  103. package/Sources/Private/Model/Assets/Asset.swift +8 -0
  104. package/Sources/Private/Model/Assets/AssetLibrary.swift +2 -2
  105. package/Sources/Private/Model/Assets/ImageAsset.swift +3 -3
  106. package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +16 -2
  107. package/Sources/Private/Model/DotLottie/DotLottieImageProvider.swift +20 -7
  108. package/Sources/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +26 -0
  109. package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +8 -5
  110. package/Sources/Private/Model/LayerEffects/DropShadowEffect.swift +45 -0
  111. package/Sources/Private/Model/LayerEffects/EffectValues/ColorEffectValue.swift +38 -0
  112. package/Sources/Private/Model/LayerEffects/EffectValues/EffectValue.swift +98 -0
  113. package/Sources/Private/Model/LayerEffects/EffectValues/Vector1DEffectValue.swift +38 -0
  114. package/Sources/Private/Model/LayerEffects/LayerEffect.swift +103 -0
  115. package/Sources/Private/Model/LayerStyles/DropShadowStyle.swift +72 -0
  116. package/Sources/Private/Model/LayerStyles/LayerStyle.swift +85 -0
  117. package/Sources/Private/Model/Layers/LayerModel.swift +27 -0
  118. package/Sources/Private/Model/Objects/Marker.swift +1 -1
  119. package/Sources/Private/Model/Objects/Transform.swift +1 -2
  120. package/Sources/Private/Model/ShapeItems/GradientFill.swift +1 -1
  121. package/Sources/Private/Model/ShapeItems/GradientStroke.swift +2 -2
  122. package/Sources/Private/Model/ShapeItems/Merge.swift +1 -1
  123. package/Sources/Private/Model/ShapeItems/ShapeItem.swift +31 -26
  124. package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +0 -9
  125. package/Sources/Private/Model/ShapeItems/Star.swift +1 -1
  126. package/Sources/Private/Model/Text/Font.swift +2 -2
  127. package/Sources/Private/Model/Text/Glyph.swift +1 -1
  128. package/Sources/Private/RootAnimationLayer.swift +2 -2
  129. package/Sources/Private/Utility/Debugging/LayerDebugging.swift +3 -1
  130. package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +32 -2
  131. package/Sources/Private/Utility/Helpers/AnimationContext.swift +4 -2
  132. package/Sources/Private/Utility/Helpers/AnyEquatable.swift +24 -0
  133. package/Sources/Private/Utility/Helpers/Binding+Map.swift +18 -0
  134. package/Sources/Private/Utility/Helpers/View+ValueChanged.swift +20 -0
  135. package/Sources/Private/Utility/LottieAnimationSource.swift +41 -0
  136. package/Sources/Private/Utility/Primitives/BezierPath.swift +2 -2
  137. package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +2 -2
  138. package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +1 -1
  139. package/Sources/Public/Animation/LottieAnimation.swift +21 -2
  140. package/Sources/Public/Animation/LottieAnimationLayer.swift +1464 -0
  141. package/Sources/Public/Animation/LottieAnimationView.swift +258 -735
  142. package/Sources/Public/Animation/LottiePlaybackMode.swift +117 -0
  143. package/Sources/Public/Animation/LottieView.swift +462 -0
  144. package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +2 -1
  145. package/Sources/Public/AnimationCache/DefaultAnimationCache.swift +5 -6
  146. package/Sources/Public/Configuration/DecodingStrategy.swift +15 -0
  147. package/Sources/Public/Configuration/LottieConfiguration.swift +47 -0
  148. package/Sources/Public/Configuration/ReducedMotionOption.swift +114 -0
  149. package/Sources/Public/{LottieConfiguration.swift → Configuration/RenderingEngineOption.swift} +2 -57
  150. package/Sources/Public/{iOS → Controls}/AnimatedButton.swift +56 -13
  151. package/Sources/Public/{iOS → Controls}/AnimatedControl.swift +80 -8
  152. package/Sources/Public/{iOS → Controls}/AnimatedSwitch.swift +71 -31
  153. package/Sources/Public/Controls/LottieButton.swift +122 -0
  154. package/Sources/Public/Controls/LottieSwitch.swift +144 -0
  155. package/Sources/Public/Controls/LottieViewType.swift +79 -0
  156. package/Sources/Public/DotLottie/DotLottieConfiguration.swift +24 -0
  157. package/Sources/Public/DotLottie/DotLottieFile.swift +21 -6
  158. package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +79 -0
  159. package/Sources/Public/DynamicProperties/AnimationKeypath.swift +11 -1
  160. package/Sources/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +14 -0
  161. package/Sources/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +13 -0
  162. package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +22 -5
  163. package/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +14 -0
  164. package/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +13 -0
  165. package/Sources/Public/FontProvider/AnimationFontProvider.swift +8 -0
  166. package/Sources/Public/ImageProvider/AnimationImageProvider.swift +24 -0
  167. package/Sources/Public/Keyframes/Keyframe.swift +6 -0
  168. package/Sources/Public/Primitives/Vectors.swift +2 -2
  169. package/Sources/Public/TextProvider/AnimationTextProvider.swift +79 -6
  170. package/Sources/Public/iOS/AnimationSubview.swift +1 -1
  171. package/Sources/Public/iOS/BundleImageProvider.swift +16 -2
  172. package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +2 -2
  173. package/Sources/Public/iOS/FilepathImageProvider.swift +22 -3
  174. package/Sources/Public/iOS/LottieAnimationViewBase.swift +7 -1
  175. package/Sources/Public/macOS/BundleImageProvider.macOS.swift +15 -1
  176. package/Sources/Public/macOS/FilepathImageProvider.macOS.swift +21 -2
  177. package/lottie-ios.podspec +2 -1
  178. package/package.json +1 -1
  179. package/Sources/Private/Model/DotLottie/DotLottieConfiguration.swift +0 -15
  180. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Helpers.swift +0 -0
  181. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Progress.swift +0 -0
  182. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Reading.swift +0 -0
  183. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ReadingDeprecated.swift +0 -0
  184. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+WritingDeprecated.swift +0 -0
  185. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ZIP64.swift +0 -0
  186. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+CompressionDeprecated.swift +0 -0
  187. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Serialization.swift +0 -0
  188. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+Serialization.swift +0 -0
  189. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+ZIP64.swift +0 -0
  190. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry.swift +0 -0
  191. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/README.md +0 -0
  192. /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/URL+ZIP.swift +0 -0
@@ -16,10 +16,12 @@ final class PreCompositionLayer: CompositionLayer {
16
16
  precomp: PreCompLayerModel,
17
17
  asset: PrecompAsset,
18
18
  layerImageProvider: LayerImageProvider,
19
- textProvider: AnimationTextProvider,
19
+ layerTextProvider: LayerTextProvider,
20
+ textProvider: AnimationKeypathTextProvider,
20
21
  fontProvider: AnimationFontProvider,
21
22
  assetLibrary: AssetLibrary?,
22
- frameRate: CGFloat)
23
+ frameRate: CGFloat,
24
+ rootAnimationLayer: MainThreadAnimationLayer?)
23
25
  {
24
26
  animationLayers = []
25
27
  if let keyframes = precomp.timeRemapping?.keyframes {
@@ -36,11 +38,14 @@ final class PreCompositionLayer: CompositionLayer {
36
38
  let layers = asset.layers.initializeCompositionLayers(
37
39
  assetLibrary: assetLibrary,
38
40
  layerImageProvider: layerImageProvider,
41
+ layerTextProvider: layerTextProvider,
39
42
  textProvider: textProvider,
40
43
  fontProvider: fontProvider,
41
- frameRate: frameRate)
44
+ frameRate: frameRate,
45
+ rootAnimationLayer: rootAnimationLayer)
42
46
 
43
47
  var imageLayers = [ImageCompositionLayer]()
48
+ var textLayers = [TextCompositionLayer]()
44
49
 
45
50
  var mattedLayer: CompositionLayer? = nil
46
51
 
@@ -50,6 +55,9 @@ final class PreCompositionLayer: CompositionLayer {
50
55
  if let imageLayer = layer as? ImageCompositionLayer {
51
56
  imageLayers.append(imageLayer)
52
57
  }
58
+ if let textLayer = layer as? TextCompositionLayer {
59
+ textLayers.append(textLayer)
60
+ }
53
61
  if let matte = mattedLayer {
54
62
  /// The previous layer requires this layer to be its matte
55
63
  matte.matteLayer = layer
@@ -69,6 +77,7 @@ final class PreCompositionLayer: CompositionLayer {
69
77
  childKeypaths.append(contentsOf: layers)
70
78
 
71
79
  layerImageProvider.addImageLayers(imageLayers)
80
+ layerTextProvider.addTextLayers(textLayers)
72
81
  }
73
82
 
74
83
  override init(layer: Any) {
@@ -107,12 +116,16 @@ final class PreCompositionLayer: CompositionLayer {
107
116
  } else {
108
117
  localFrame = (frame - startFrame) / timeStretch
109
118
  }
110
- animationLayers.forEach { $0.displayWithFrame(frame: localFrame, forceUpdates: forceUpdates) }
119
+ for animationLayer in animationLayers {
120
+ animationLayer.displayWithFrame(frame: localFrame, forceUpdates: forceUpdates)
121
+ }
111
122
  }
112
123
 
113
124
  override func updateRenderScale() {
114
125
  super.updateRenderScale()
115
- animationLayers.forEach { $0.renderScale = renderScale }
126
+ for animationLayer in animationLayers {
127
+ animationLayer.renderScale = renderScale
128
+ }
116
129
  }
117
130
 
118
131
  // MARK: Fileprivate
@@ -47,7 +47,12 @@ final class TextCompositionLayer: CompositionLayer {
47
47
 
48
48
  // MARK: Lifecycle
49
49
 
50
- init(textLayer: TextLayerModel, textProvider: AnimationTextProvider, fontProvider: AnimationFontProvider) {
50
+ init(
51
+ textLayer: TextLayerModel,
52
+ textProvider: AnimationKeypathTextProvider,
53
+ fontProvider: AnimationFontProvider,
54
+ rootAnimationLayer: MainThreadAnimationLayer?)
55
+ {
51
56
  var rootNode: TextAnimatorNode?
52
57
  for animator in textLayer.animators {
53
58
  rootNode = TextAnimatorNode(parentNode: rootNode, textAnimator: animator)
@@ -57,6 +62,7 @@ final class TextCompositionLayer: CompositionLayer {
57
62
 
58
63
  self.textProvider = textProvider
59
64
  self.fontProvider = fontProvider
65
+ self.rootAnimationLayer = rootAnimationLayer
60
66
 
61
67
  super.init(layer: textLayer, size: .zero)
62
68
  contentsLayer.addSublayer(self.textLayer)
@@ -92,8 +98,18 @@ final class TextCompositionLayer: CompositionLayer {
92
98
  let textDocument: KeyframeInterpolator<TextDocument>?
93
99
 
94
100
  let textLayer = CoreTextRenderLayer()
95
- var textProvider: AnimationTextProvider
101
+ var textProvider: AnimationKeypathTextProvider
96
102
  var fontProvider: AnimationFontProvider
103
+ weak var rootAnimationLayer: MainThreadAnimationLayer?
104
+
105
+ lazy var fullAnimationKeypath: AnimationKeypath = {
106
+ // Individual layers don't know their full keypaths, so we have to delegate
107
+ // to the `MainThreadAnimationLayer` to search the layer hierarchy and find
108
+ // the full keypath (which includes this layer's parent layers)
109
+ rootAnimationLayer?.keypath(for: self)
110
+ // If that failed for some reason, just use the last path component (which we do have here)
111
+ ?? AnimationKeypath(keypath: keypathName)
112
+ }()
97
113
 
98
114
  override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) {
99
115
  guard let textDocument = textDocument else { return }
@@ -108,11 +124,23 @@ final class TextCompositionLayer: CompositionLayer {
108
124
 
109
125
  // Get Text Attributes
110
126
  let text = textDocument.value(frame: frame) as! TextDocument
127
+
128
+ // Prior to Lottie 4.3.0 the Main Thread rendering engine always just used `LegacyAnimationTextProvider`
129
+ // and called it with the `keypathName` (only the last path component of the full keypath).
130
+ // Starting in Lottie 4.3.0 we use `AnimationKeypathTextProvider` instead if implemented.
131
+ let textString: String
132
+ if let keypathTextValue = textProvider.text(for: fullAnimationKeypath, sourceText: text.text) {
133
+ textString = keypathTextValue
134
+ } else if let legacyTextProvider = textProvider as? LegacyAnimationTextProvider {
135
+ textString = legacyTextProvider.textFor(keypathName: keypathName, sourceText: text.text)
136
+ } else {
137
+ textString = text.text
138
+ }
139
+
111
140
  let strokeColor = rootNode?.textOutputNode.strokeColor ?? text.strokeColorData?.cgColorValue
112
141
  let strokeWidth = rootNode?.textOutputNode.strokeWidth ?? CGFloat(text.strokeWidth ?? 0)
113
142
  let tracking = (CGFloat(text.fontSize) * (rootNode?.textOutputNode.tracking ?? CGFloat(text.tracking))) / 1000.0
114
143
  let matrix = rootNode?.textOutputNode.xform ?? CATransform3DIdentity
115
- let textString = textProvider.textFor(keypathName: keypathName, sourceText: text.text)
116
144
  let ctFont = fontProvider.fontFor(family: text.fontFamily, size: CGFloat(text.fontSize))
117
145
 
118
146
  // Set all of the text layer options
@@ -21,7 +21,7 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
21
21
  init(
22
22
  animation: LottieAnimation,
23
23
  imageProvider: AnimationImageProvider,
24
- textProvider: AnimationTextProvider,
24
+ textProvider: AnimationKeypathTextProvider,
25
25
  fontProvider: AnimationFontProvider,
26
26
  maskAnimationToBounds: Bool,
27
27
  logger: LottieLogger)
@@ -37,9 +37,11 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
37
37
  let layers = animation.layers.initializeCompositionLayers(
38
38
  assetLibrary: animation.assetLibrary,
39
39
  layerImageProvider: layerImageProvider,
40
+ layerTextProvider: layerTextProvider,
40
41
  textProvider: textProvider,
41
42
  fontProvider: fontProvider,
42
- frameRate: CGFloat(animation.framerate))
43
+ frameRate: CGFloat(animation.framerate),
44
+ rootAnimationLayer: self)
43
45
 
44
46
  var imageLayers = [ImageCompositionLayer]()
45
47
  var textLayers = [TextCompositionLayer]()
@@ -139,7 +141,9 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
139
141
  if respectAnimationFrameRate {
140
142
  newFrame = floor(newFrame)
141
143
  }
142
- animationLayers.forEach { $0.displayWithFrame(frame: newFrame, forceUpdates: false) }
144
+ for animationLayer in animationLayers {
145
+ animationLayer.displayWithFrame(frame: newFrame, forceUpdates: forceDisplayUpdateOnEachFrame)
146
+ }
143
147
  }
144
148
 
145
149
  // MARK: Internal
@@ -147,8 +151,16 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
147
151
  /// The animatable Current Frame Property
148
152
  @NSManaged var currentFrame: CGFloat
149
153
 
150
- /// The parent `LottieAnimationView` that manages this layer
151
- weak var animationView: LottieAnimationView?
154
+ /// The parent `LottieAnimationLayer` that manages this layer
155
+ weak var lottieAnimationLayer: LottieAnimationLayer?
156
+
157
+ /// Whether or not to use `forceDisplayUpdate()` when rendering each individual frame.
158
+ /// - The main thread rendering engine implements optimizations to decrease the amount
159
+ /// of properties that have to be re-rendered on each frame. There are some cases
160
+ /// where this can result in bugs / incorrect behavior, so we allow it to be disabled.
161
+ /// - Forcing a full render on every frame will decrease performance, and is not recommended
162
+ /// except as a workaround to a bug in the main thread rendering engine.
163
+ var forceDisplayUpdateOnEachFrame = false
152
164
 
153
165
  var animationLayers: ContiguousArray<CompositionLayer>
154
166
 
@@ -175,11 +187,13 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
175
187
 
176
188
  var renderScale: CGFloat = 1 {
177
189
  didSet {
178
- animationLayers.forEach { $0.renderScale = renderScale }
190
+ for animationLayer in animationLayers {
191
+ animationLayer.renderScale = renderScale
192
+ }
179
193
  }
180
194
  }
181
195
 
182
- var textProvider: AnimationTextProvider {
196
+ var textProvider: AnimationKeypathTextProvider {
183
197
  get { layerTextProvider.textProvider }
184
198
  set { layerTextProvider.textProvider = newValue }
185
199
  }
@@ -199,7 +213,9 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
199
213
 
200
214
  /// Forces the view to update its drawing.
201
215
  func forceDisplayUpdate() {
202
- animationLayers.forEach { $0.displayWithFrame(frame: currentFrame, forceUpdates: true) }
216
+ for animationLayer in animationLayers {
217
+ animationLayer.displayWithFrame(frame: currentFrame, forceUpdates: true)
218
+ }
203
219
  }
204
220
 
205
221
  func logHierarchyKeypaths() {
@@ -258,6 +274,15 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
258
274
  return nil
259
275
  }
260
276
 
277
+ func keypath(for layerToFind: CALayer) -> AnimationKeypath? {
278
+ for layer in animationLayers {
279
+ if let foundKeypath = layer.keypath(for: layerToFind) {
280
+ return foundKeypath
281
+ }
282
+ }
283
+ return nil
284
+ }
285
+
261
286
  func animatorNodes(for keypath: AnimationKeypath) -> [AnimatorNode]? {
262
287
  var results = [AnimatorNode]()
263
288
  for layer in animationLayers {
@@ -3,6 +3,7 @@
3
3
 
4
4
  import CoreGraphics
5
5
  import Foundation
6
+ import QuartzCore
6
7
 
7
8
  // MARK: - CachedImageProvider
8
9
 
@@ -35,6 +36,11 @@ private final class CachedImageProvider: AnimationImageProvider {
35
36
 
36
37
  let imageCache: NSCache<NSString, CGImage> = .init()
37
38
  let imageProvider: AnimationImageProvider
39
+
40
+ func contentsGravity(for asset: ImageAsset) -> CALayerContentsGravity {
41
+ imageProvider.contentsGravity(for: asset)
42
+ }
43
+
38
44
  }
39
45
 
40
46
  extension AnimationImageProvider {
@@ -42,6 +48,7 @@ extension AnimationImageProvider {
42
48
  /// It wraps the current provider as image loader, and uses `NSCache` to cache the images for resue.
43
49
  /// The cache will be reset when the `animation` is reset.
44
50
  var cachedImageProvider: AnimationImageProvider {
45
- CachedImageProvider(imageProvider: self)
51
+ guard cacheEligible else { return self }
52
+ return CachedImageProvider(imageProvider: self)
46
53
  }
47
54
  }
@@ -13,9 +13,12 @@ extension Array where Element == LayerModel {
13
13
  func initializeCompositionLayers(
14
14
  assetLibrary: AssetLibrary?,
15
15
  layerImageProvider: LayerImageProvider,
16
- textProvider: AnimationTextProvider,
16
+ layerTextProvider: LayerTextProvider,
17
+ textProvider: AnimationKeypathTextProvider,
17
18
  fontProvider: AnimationFontProvider,
18
- frameRate: CGFloat) -> [CompositionLayer]
19
+ frameRate: CGFloat,
20
+ rootAnimationLayer: MainThreadAnimationLayer?)
21
+ -> [CompositionLayer]
19
22
  {
20
23
  var compositionLayers = [CompositionLayer]()
21
24
  var layerMap = [Int : CompositionLayer]()
@@ -45,10 +48,12 @@ extension Array where Element == LayerModel {
45
48
  precomp: precompLayer,
46
49
  asset: precompAsset,
47
50
  layerImageProvider: layerImageProvider,
51
+ layerTextProvider: layerTextProvider,
48
52
  textProvider: textProvider,
49
53
  fontProvider: fontProvider,
50
54
  assetLibrary: assetLibrary,
51
- frameRate: frameRate)
55
+ frameRate: frameRate,
56
+ rootAnimationLayer: rootAnimationLayer)
52
57
  compositionLayers.append(precompContainer)
53
58
  layerMap[layer.index] = precompContainer
54
59
  } else if
@@ -62,7 +67,11 @@ extension Array where Element == LayerModel {
62
67
  compositionLayers.append(imageContainer)
63
68
  layerMap[layer.index] = imageContainer
64
69
  } else if let textLayer = layer as? TextLayerModel {
65
- let textContainer = TextCompositionLayer(textLayer: textLayer, textProvider: textProvider, fontProvider: fontProvider)
70
+ let textContainer = TextCompositionLayer(
71
+ textLayer: textLayer,
72
+ textProvider: textProvider,
73
+ fontProvider: fontProvider,
74
+ rootAnimationLayer: rootAnimationLayer)
66
75
  compositionLayers.append(textContainer)
67
76
  layerMap[layer.index] = textContainer
68
77
  } else {
@@ -34,8 +34,8 @@ final class LayerFontProvider {
34
34
  }
35
35
 
36
36
  func reloadTexts() {
37
- textLayers.forEach {
38
- $0.fontProvider = fontProvider
37
+ for textLayer in textLayers {
38
+ textLayer.fontProvider = fontProvider
39
39
  }
40
40
  }
41
41
  }
@@ -47,6 +47,7 @@ final class LayerImageProvider {
47
47
  for imageLayer in imageLayers {
48
48
  if let asset = imageAssets[imageLayer.imageReferenceID] {
49
49
  imageLayer.image = imageProvider.imageForAsset(asset: asset)
50
+ imageLayer.imageContentsGravity = imageProvider.contentsGravity(for: asset)
50
51
  }
51
52
  }
52
53
  }
@@ -12,7 +12,7 @@ final class LayerTextProvider {
12
12
 
13
13
  // MARK: Lifecycle
14
14
 
15
- init(textProvider: AnimationTextProvider) {
15
+ init(textProvider: AnimationKeypathTextProvider) {
16
16
  self.textProvider = textProvider
17
17
  textLayers = []
18
18
  reloadTexts()
@@ -22,7 +22,7 @@ final class LayerTextProvider {
22
22
 
23
23
  private(set) var textLayers: [TextCompositionLayer]
24
24
 
25
- var textProvider: AnimationTextProvider {
25
+ var textProvider: AnimationKeypathTextProvider {
26
26
  didSet {
27
27
  reloadTexts()
28
28
  }
@@ -33,8 +33,8 @@ final class LayerTextProvider {
33
33
  }
34
34
 
35
35
  func reloadTexts() {
36
- textLayers.forEach {
37
- $0.textProvider = textProvider
36
+ for textLayer in textLayers {
37
+ textLayer.textProvider = textProvider
38
38
  }
39
39
  }
40
40
  }
@@ -36,7 +36,7 @@ extension NodePropertyMap {
36
36
 
37
37
  /// Rebuilds only the local nodes that have an update for the frame
38
38
  func updateNodeProperties(frame: CGFloat) {
39
- properties.forEach { property in
39
+ for property in properties {
40
40
  property.update(frame: frame)
41
41
  }
42
42
  }
@@ -133,7 +133,9 @@ final class TrimPathNode: AnimatorNode {
133
133
 
134
134
  /// First get the total length of all paths.
135
135
  var totalLength: CGFloat = 0
136
- upstreamPaths.forEach { totalLength = totalLength + $0.totalLength }
136
+ for upstreamPath in upstreamPaths {
137
+ totalLength = totalLength + upstreamPath.totalLength
138
+ }
137
139
 
138
140
  /// Now determine the start and end cut lengths
139
141
  let startLength = startPosition * totalLength
@@ -82,7 +82,8 @@ private final class GradientFillLayer: CALayer {
82
82
  bitsPerComponent: 8,
83
83
  bytesPerRow: ctx.width,
84
84
  space: maskColorSpace,
85
- bitmapInfo: 0) else { return }
85
+ bitmapInfo: 0)
86
+ else { return }
86
87
  let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: CGFloat(maskContext.height))
87
88
  maskContext.concatenate(flipVertical)
88
89
  maskContext.concatenate(ctx.ctm)
@@ -104,7 +104,8 @@ final class LegacyGradientFillRenderer: PassThroughOutputNode, Renderable {
104
104
  bitsPerComponent: 8,
105
105
  bytesPerRow: inContext.width,
106
106
  space: maskColorSpace,
107
- bitmapInfo: 0) else { return }
107
+ bitmapInfo: 0)
108
+ else { return }
108
109
  let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: CGFloat(maskContext.height))
109
110
  maskContext.concatenate(flipVertical)
110
111
  maskContext.concatenate(inContext.ctm)
@@ -27,7 +27,7 @@ final class GradientFillProperties: NodePropertyMap, KeypathSearchable {
27
27
  PropertyName.opacity.rawValue : opacity,
28
28
  "Start Point" : startPoint,
29
29
  "End Point" : endPoint,
30
- "Colors" : colors,
30
+ PropertyName.gradientColors.rawValue : colors,
31
31
  ]
32
32
  properties = Array(keypathProperties.values)
33
33
  }
@@ -47,8 +47,8 @@ final class GradientStrokeProperties: NodePropertyMap, KeypathSearchable {
47
47
  PropertyName.opacity.rawValue : opacity,
48
48
  "Start Point" : startPoint,
49
49
  "End Point" : endPoint,
50
- "Colors" : colors,
51
- "Stroke Width" : width,
50
+ PropertyName.gradientColors.rawValue : colors,
51
+ PropertyName.strokeWidth.rawValue : width,
52
52
  "Dashes" : dashPattern,
53
53
  "Dash Phase" : dashPhase,
54
54
  ]
@@ -38,7 +38,7 @@ final class StrokeNodeProperties: NodePropertyMap, KeypathSearchable {
38
38
  keypathProperties = [
39
39
  PropertyName.opacity.rawValue : opacity,
40
40
  PropertyName.color.rawValue : color,
41
- "Stroke Width" : width,
41
+ PropertyName.strokeWidth.rawValue : width,
42
42
  "Dashes" : dashPattern,
43
43
  "Dash Phase" : dashPhase,
44
44
  ]
@@ -99,7 +99,7 @@ final class TextAnimatorNodeProperties: NodePropertyMap, KeypathSearchable {
99
99
 
100
100
  if let keyframeGroup = textAnimator.strokeWidth {
101
101
  strokeWidth = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes))
102
- properties["Stroke Width"] = strokeWidth
102
+ properties[PropertyName.strokeWidth.rawValue] = strokeWidth
103
103
  } else {
104
104
  strokeWidth = nil
105
105
  }
@@ -56,7 +56,9 @@ class ShapeContainerLayer: CALayer {
56
56
  rebuildContents(forFrame: forFrame)
57
57
  }
58
58
  guard isHidden == false else { return }
59
- renderLayers.forEach { $0.markRenderUpdates(forFrame: forFrame) }
59
+ for renderLayer in renderLayers {
60
+ renderLayer.markRenderUpdates(forFrame: forFrame)
61
+ }
60
62
  }
61
63
 
62
64
  func hasRenderUpdate(forFrame _: CGFloat) -> Bool {
@@ -69,7 +71,9 @@ class ShapeContainerLayer: CALayer {
69
71
 
70
72
  func updateRenderScale() {
71
73
  contentsScale = renderScale
72
- renderLayers.forEach { $0.renderScale = renderScale }
74
+ for renderLayer in renderLayers {
75
+ renderLayer.renderScale = renderScale
76
+ }
73
77
  }
74
78
 
75
79
  }
@@ -7,6 +7,8 @@
7
7
 
8
8
  import Foundation
9
9
 
10
+ // MARK: - Asset
11
+
10
12
  public class Asset: Codable, DictionaryInitializable {
11
13
 
12
14
  // MARK: Lifecycle
@@ -41,3 +43,9 @@ public class Asset: Codable, DictionaryInitializable {
41
43
  case id
42
44
  }
43
45
  }
46
+
47
+ // MARK: Sendable
48
+
49
+ /// Since `Asset` isn't `final`, we have to use `@unchecked Sendable` instead of `Sendable.`
50
+ /// All `Asset` subclasses are immutable `Sendable` values.
51
+ extension Asset: @unchecked Sendable { }
@@ -7,7 +7,7 @@
7
7
 
8
8
  import Foundation
9
9
 
10
- final class AssetLibrary: Codable, AnyInitializable {
10
+ final class AssetLibrary: Codable, AnyInitializable, Sendable {
11
11
 
12
12
  // MARK: Lifecycle
13
13
 
@@ -44,7 +44,7 @@ final class AssetLibrary: Codable, AnyInitializable {
44
44
  var decodedAssets = [String : Asset]()
45
45
  var imageAssets = [String : ImageAsset]()
46
46
  var precompAssets = [String : PrecompAsset]()
47
- try dictionaries.forEach { dictionary in
47
+ for dictionary in dictionaries {
48
48
  if dictionary[PrecompAsset.CodingKeys.layers.rawValue] != nil {
49
49
  let asset = try PrecompAsset(dictionary: dictionary)
50
50
  decodedAssets[asset.id] = asset
@@ -70,7 +70,7 @@ extension Data {
70
70
  ///
71
71
  /// Returns nil when the input is not recognized as valid Data URL.
72
72
  /// - parameter imageAsset: The image asset that contains Data URL.
73
- internal init?(imageAsset: ImageAsset) {
73
+ init?(imageAsset: ImageAsset) {
74
74
  self.init(dataString: imageAsset.name)
75
75
  }
76
76
 
@@ -79,7 +79,7 @@ extension Data {
79
79
  /// Returns nil when the input is not recognized as valid Data URL.
80
80
  /// - parameter dataString: The data string to parse.
81
81
  /// - parameter options: Options for the string parsing. Default value is `[]`.
82
- internal init?(dataString: String, options: DataURLReadOptions = []) {
82
+ init?(dataString: String, options: DataURLReadOptions = []) {
83
83
  let trimmedDataString = dataString.trimmingCharacters(in: .whitespacesAndNewlines)
84
84
  guard
85
85
  dataString.hasPrefix("data:"),
@@ -103,7 +103,7 @@ extension Data {
103
103
 
104
104
  // MARK: Internal
105
105
 
106
- internal struct DataURLReadOptions: OptionSet {
106
+ struct DataURLReadOptions: OptionSet {
107
107
  let rawValue: Int
108
108
 
109
109
  /// Will read Data URL using Data(contentsOf:)
@@ -7,6 +7,8 @@
7
7
 
8
8
  import Foundation
9
9
 
10
+ // MARK: - DotLottieAnimation
11
+
10
12
  struct DotLottieAnimation: Codable {
11
13
  /// Id of Animation
12
14
  var id: String
@@ -21,11 +23,16 @@ struct DotLottieAnimation: Codable {
21
23
  var direction: Int? = 1
22
24
 
23
25
  /// mode - "bounce" | "normal"
24
- var mode: String? = "normal"
26
+ var mode: DotLottieAnimationMode? = .normal
25
27
 
26
28
  /// Loop mode for animation
27
29
  var loopMode: LottieLoopMode {
28
- mode == "bounce" ? .autoReverse : ((loop ?? false) ? .loop : .playOnce)
30
+ switch mode {
31
+ case .bounce:
32
+ return .autoReverse
33
+ case .normal, nil:
34
+ return (loop ?? false) ? .loop : .playOnce
35
+ }
29
36
  }
30
37
 
31
38
  /// Animation speed
@@ -41,3 +48,10 @@ struct DotLottieAnimation: Codable {
41
48
  return try LottieAnimation.from(data: data)
42
49
  }
43
50
  }
51
+
52
+ // MARK: - DotLottieAnimationMode
53
+
54
+ enum DotLottieAnimationMode: String, Codable {
55
+ case normal
56
+ case bounce
57
+ }
@@ -6,9 +6,9 @@
6
6
  //
7
7
 
8
8
  import Foundation
9
- #if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst)
9
+ #if canImport(UIKit)
10
10
  import UIKit
11
- #elseif os(macOS)
11
+ #elseif canImport(AppKit)
12
12
  import AppKit
13
13
  #endif
14
14
 
@@ -51,23 +51,36 @@ class DotLottieImageProvider: AnimationImageProvider {
51
51
  private var images = [String: CGImage]()
52
52
 
53
53
  private func loadImages() {
54
- filepath.urls.forEach {
54
+ for url in filepath.urls {
55
55
  #if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst)
56
56
  if
57
- let data = try? Data(contentsOf: $0),
57
+ let data = try? Data(contentsOf: url),
58
58
  let image = UIImage(data: data)?.cgImage
59
59
  {
60
- images[$0.lastPathComponent] = image
60
+ images[url.lastPathComponent] = image
61
61
  }
62
62
  #elseif os(macOS)
63
63
  if
64
- let data = try? Data(contentsOf: $0),
64
+ let data = try? Data(contentsOf: url),
65
65
  let image = NSImage(data: data)?.lottie_CGImage
66
66
  {
67
- images[$0.lastPathComponent] = image
67
+ images[url.lastPathComponent] = image
68
68
  }
69
69
  #endif
70
70
  }
71
71
  }
72
72
 
73
73
  }
74
+
75
+ // MARK: Hashable
76
+
77
+ extension DotLottieImageProvider: Hashable {
78
+ static func ==(_ lhs: DotLottieImageProvider, _ rhs: DotLottieImageProvider) -> Bool {
79
+ lhs.filepath == rhs.filepath
80
+ }
81
+
82
+ func hash(into hasher: inout Hasher) {
83
+ hasher.combine(filepath)
84
+ }
85
+
86
+ }
@@ -41,4 +41,30 @@ extension KeyedDecodingContainer {
41
41
  }
42
42
  return list
43
43
  }
44
+
45
+ /// Decode a heterogeneous list of objects for a given family if the given key is present.
46
+ /// - Parameters:
47
+ /// - heterogeneousType: The decodable type of the list.
48
+ /// - family: The ClassFamily enum for the type family.
49
+ /// - key: The CodingKey to look up the list in the current container.
50
+ /// - Returns: The resulting list of heterogeneousType elements.
51
+ func decodeIfPresent<T: Decodable, U: ClassFamily>(_: [T].Type, ofFamily family: U.Type, forKey key: K) throws -> [T]? {
52
+ var container: UnkeyedDecodingContainer
53
+ do {
54
+ container = try nestedUnkeyedContainer(forKey: key)
55
+ } catch {
56
+ return nil
57
+ }
58
+
59
+ var list = [T]()
60
+ var tmpContainer = container
61
+ while !container.isAtEnd {
62
+ let typeContainer = try container.nestedContainer(keyedBy: Discriminator.self)
63
+ let family: U = try typeContainer.decode(U.self, forKey: U.discriminator)
64
+ if let type = family.getType() as? T.Type {
65
+ list.append(try tmpContainer.decode(type))
66
+ }
67
+ }
68
+ return list
69
+ }
44
70
  }