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
@@ -36,6 +36,7 @@ final class ValueProviderStore {
36
36
  properties. Supported properties are: \(supportedProperties.joined(separator: ", ")).
37
37
  """)
38
38
 
39
+ valueProviders.removeAll(where: { $0.keypath == keypath })
39
40
  valueProviders.append((keypath: keypath, valueProvider: valueProvider))
40
41
  }
41
42
 
@@ -72,9 +73,11 @@ final class ValueProviderStore {
72
73
 
73
74
  // Convert the type-erased keyframe values using this `CustomizableProperty`'s conversion closure
74
75
  let typedKeyframes = typeErasedKeyframes.compactMap { typeErasedKeyframe -> Keyframe<Value>? in
75
- guard let convertedValue = customizableProperty.conversion(typeErasedKeyframe.value) else {
76
+ guard let convertedValue = customizableProperty.conversion(typeErasedKeyframe.value, anyValueProvider) else {
76
77
  logger.assertionFailure("""
77
- Could not convert value of type \(type(of: typeErasedKeyframe.value)) to expected type \(Value.self)
78
+ Could not convert value of type \(type(of: typeErasedKeyframe.value)) from \(anyValueProvider) to expected type \(
79
+ Value
80
+ .self)
78
81
  """)
79
82
  return nil
80
83
  }
@@ -93,10 +96,9 @@ final class ValueProviderStore {
93
96
  // MARK: Private
94
97
 
95
98
  private let logger: LottieLogger
96
-
97
99
  private var valueProviders = [(keypath: AnimationKeypath, valueProvider: AnyValueProvider)]()
98
100
 
99
- /// Retrieves the most-recently-registered Value Provider that matches the given keypat
101
+ /// Retrieves the most-recently-registered Value Provider that matches the given keypath.
100
102
  private func valueProvider(for keypath: AnimationKeypath) -> AnyValueProvider? {
101
103
  // Find the last keypath matching the given keypath,
102
104
  // so we return the value provider that was registered most-recently
@@ -0,0 +1,263 @@
1
+ // Created by Laura Skelton on 11/25/16.
2
+ // Copyright © 2016 Airbnb. All rights reserved.
3
+
4
+ // MARK: - Collection
5
+
6
+ extension Collection where Element: Diffable, Index == Int {
7
+
8
+ /// Diffs two collections (e.g. `Array`s) of `Diffable` items, returning an `IndexChangeset`
9
+ /// representing the minimal set of changes to get from the other collection to this collection.
10
+ ///
11
+ /// - Parameters:
12
+ /// - from other: The collection of old data.
13
+ func makeChangeset(from other: Self) -> IndexChangeset {
14
+ // Arranging the elements contiguously prior to diffing improves performance by ~40%.
15
+ let new = ContiguousArray(self)
16
+ let old = ContiguousArray(other)
17
+
18
+ /// The entries in both this and the other collection, keyed by their `dataID`s.
19
+ var entries = [AnyHashable: EpoxyEntry](minimumCapacity: new.count)
20
+ var duplicates = [EpoxyEntry]()
21
+
22
+ var newResults = ContiguousArray<NewRecord>()
23
+ newResults.reserveCapacity(new.count)
24
+
25
+ for index in new.indices {
26
+ let id = new[index].diffIdentifier
27
+ let entry = entries[id, default: EpoxyEntry()]
28
+ if entry.trackNewIndex(index) {
29
+ duplicates.append(entry)
30
+ }
31
+ entries[id] = entry
32
+ newResults.append(NewRecord(entry: entry))
33
+ }
34
+
35
+ var oldResults = ContiguousArray<OldRecord>()
36
+ oldResults.reserveCapacity(old.count)
37
+
38
+ for index in old.indices {
39
+ let id = old[index].diffIdentifier
40
+ let entry = entries[id]
41
+ entry?.pushOldIndex(index)
42
+ oldResults.append(OldRecord(entry: entry))
43
+ }
44
+
45
+ for newIndex in new.indices {
46
+ let entry = newResults[newIndex].entry
47
+ if let oldIndex = entry.popOldIndex() {
48
+ let newItem = new[newIndex]
49
+ let oldItem = other[oldIndex]
50
+
51
+ if !oldItem.isDiffableItemEqual(to: newItem) {
52
+ entry.isUpdated = true
53
+ }
54
+
55
+ newResults[newIndex].correspondingOldIndex = oldIndex
56
+ oldResults[oldIndex].correspondingNewIndex = newIndex
57
+ }
58
+ }
59
+
60
+ var deletes = [Int]()
61
+ var deleteOffsets = [Int]()
62
+ deleteOffsets.reserveCapacity(old.count)
63
+ var runningDeleteOffset = 0
64
+
65
+ for index in old.indices {
66
+ deleteOffsets.append(runningDeleteOffset)
67
+
68
+ let record = oldResults[index]
69
+
70
+ if record.correspondingNewIndex == nil {
71
+ deletes.append(index)
72
+ runningDeleteOffset += 1
73
+ }
74
+ }
75
+
76
+ var inserts = [Int]()
77
+ var updates = [(Int, Int)]()
78
+ var moves = [(Int, Int)]()
79
+ var insertOffsets = [Int]()
80
+ insertOffsets.reserveCapacity(new.count)
81
+ var runningInsertOffset = 0
82
+
83
+ for index in new.indices {
84
+ insertOffsets.append(runningInsertOffset)
85
+
86
+ let record = newResults[index]
87
+
88
+ if let oldArrayIndex = record.correspondingOldIndex {
89
+ if record.entry.isUpdated {
90
+ updates.append((oldArrayIndex, index))
91
+ }
92
+
93
+ let insertOffset = insertOffsets[index]
94
+ let deleteOffset = deleteOffsets[oldArrayIndex]
95
+ if (oldArrayIndex - deleteOffset + insertOffset) != index {
96
+ moves.append((oldArrayIndex, index))
97
+ }
98
+
99
+ } else {
100
+ inserts.append(index)
101
+ runningInsertOffset += 1
102
+ }
103
+ }
104
+
105
+ EpoxyLogger.shared.assert(
106
+ old.count + inserts.count - deletes.count == new.count,
107
+ "Failed sanity check for old count with changes matching new count.")
108
+
109
+ return IndexChangeset(
110
+ inserts: inserts,
111
+ deletes: deletes,
112
+ updates: updates,
113
+ moves: moves,
114
+ newIndices: oldResults.map { $0.correspondingNewIndex },
115
+ duplicates: duplicates.map { $0.newIndices })
116
+ }
117
+
118
+ /// Diffs between two collections (eg. `Array`s) of `Diffable` items, and returns an `IndexPathChangeset`
119
+ /// representing the minimal set of changes to get from the other collection to this collection.
120
+ ///
121
+ /// - Parameters:
122
+ /// - from other: The collection of old data.
123
+ /// - fromSection: The section the other collection's data exists within. Defaults to `0`.
124
+ /// - toSection: The section this collection's data exists within. Defaults to `0`.
125
+ func makeIndexPathChangeset(
126
+ from other: Self,
127
+ fromSection: Int = 0,
128
+ toSection: Int = 0)
129
+ -> IndexPathChangeset
130
+ {
131
+ let indexChangeset = makeChangeset(from: other)
132
+
133
+ return IndexPathChangeset(
134
+ inserts: indexChangeset.inserts.map { index in
135
+ [toSection, index]
136
+ },
137
+ deletes: indexChangeset.deletes.map { index in
138
+ [fromSection, index]
139
+ },
140
+ updates: indexChangeset.updates.map { fromIndex, toIndex in
141
+ ([fromSection, fromIndex], [toSection, toIndex])
142
+ },
143
+ moves: indexChangeset.moves.map { fromIndex, toIndex in
144
+ ([fromSection, fromIndex], [toSection, toIndex])
145
+ },
146
+ duplicates: indexChangeset.duplicates.map { duplicate in
147
+ duplicate.map { index in
148
+ [toSection, index]
149
+ }
150
+ })
151
+ }
152
+
153
+ /// Diffs between two collections (e.g. `Array`s) of `Diffable` items, returning an
154
+ /// `IndexSetChangeset` representing the minimal set of changes to get from the other collection
155
+ /// to this collection.
156
+ ///
157
+ /// - Parameters:
158
+ /// - from other: The collection of old data.
159
+ func makeIndexSetChangeset(from other: Self) -> IndexSetChangeset {
160
+ let indexChangeset = makeChangeset(from: other)
161
+
162
+ return IndexSetChangeset(
163
+ inserts: .init(indexChangeset.inserts),
164
+ deletes: .init(indexChangeset.deletes),
165
+ updates: indexChangeset.updates,
166
+ moves: indexChangeset.moves,
167
+ newIndices: indexChangeset.newIndices,
168
+ duplicates: indexChangeset.duplicates.map { .init($0) })
169
+ }
170
+
171
+ }
172
+
173
+ extension Collection where Element: DiffableSection, Index == Int {
174
+ /// Diffs between two collections (e.g. `Array`s) of `DiffableSection` items, returning an
175
+ /// `SectionedChangeset` representing the minimal set of changes to get from the other collection
176
+ /// to this collection.
177
+ ///
178
+ /// - Parameters:
179
+ /// - from other: The collection of old data.
180
+ func makeSectionedChangeset(from other: Self) -> SectionedChangeset {
181
+ let sectionChangeset = makeIndexSetChangeset(from: other)
182
+ var itemChangeset = IndexPathChangeset()
183
+
184
+ for fromSectionIndex in other.indices {
185
+ guard let toSectionIndex = sectionChangeset.newIndices[fromSectionIndex] else {
186
+ continue
187
+ }
188
+
189
+ let fromItems = other[fromSectionIndex].diffableItems
190
+ let toItems = self[toSectionIndex].diffableItems
191
+
192
+ let itemIndexChangeset = toItems.makeIndexPathChangeset(
193
+ from: fromItems,
194
+ fromSection: fromSectionIndex,
195
+ toSection: toSectionIndex)
196
+
197
+ itemChangeset += itemIndexChangeset
198
+ }
199
+
200
+ return SectionedChangeset(sectionChangeset: sectionChangeset, itemChangeset: itemChangeset)
201
+ }
202
+ }
203
+
204
+ // MARK: - EpoxyEntry
205
+
206
+ /// A bookkeeping refrence type for the diffing algorithm.
207
+ private final class EpoxyEntry {
208
+
209
+ // MARK: Internal
210
+
211
+ private(set) var oldIndices = [Int]()
212
+ private(set) var newIndices = [Int]()
213
+ var isUpdated = false
214
+
215
+ /// Tracks an index from the new indices, returning `true` if this entry has previously tracked
216
+ /// a new index as a means to identify duplicates and `false` otherwise.
217
+ func trackNewIndex(_ index: Int) -> Bool {
218
+ let previouslyEmpty = newIndices.isEmpty
219
+
220
+ newIndices.append(index)
221
+
222
+ // We've encountered a duplicate, return true so we can track it.
223
+ if !previouslyEmpty, newIndices.count == 2 {
224
+ return true
225
+ }
226
+
227
+ return false
228
+ }
229
+
230
+ func pushOldIndex(_ index: Int) {
231
+ oldIndices.append(index)
232
+ }
233
+
234
+ func popOldIndex() -> Int? {
235
+ guard currentOldIndex < oldIndices.endIndex else {
236
+ return nil
237
+ }
238
+ defer {
239
+ currentOldIndex += 1
240
+ }
241
+ return oldIndices[currentOldIndex]
242
+ }
243
+
244
+ // MARK: Private
245
+
246
+ private var currentOldIndex = 0
247
+ }
248
+
249
+ // MARK: - OldRecord
250
+
251
+ /// A bookkeeping type for pairing up an old element with its new index.
252
+ private struct OldRecord {
253
+ var entry: EpoxyEntry?
254
+ var correspondingNewIndex: Int? = nil
255
+ }
256
+
257
+ // MARK: - NewRecord
258
+
259
+ /// A bookkeeping type for pairing up a new element with its old index.
260
+ private struct NewRecord {
261
+ var entry: EpoxyEntry
262
+ var correspondingOldIndex: Int? = nil
263
+ }
@@ -0,0 +1,18 @@
1
+ // Created by Laura Skelton on 5/11/17.
2
+ // Copyright © 2017 Airbnb. All rights reserved.
3
+
4
+ // MARK: - Diffable
5
+
6
+ /// A protocol that allows us to check identity and equality between items for the purposes of
7
+ /// diffing.
8
+ protocol Diffable {
9
+
10
+ /// Checks for equality between items when diffing.
11
+ ///
12
+ /// - Parameters:
13
+ /// - otherDiffableItem: The other item to check equality against while diffing.
14
+ func isDiffableItemEqual(to otherDiffableItem: Diffable) -> Bool
15
+
16
+ /// The identifier to use when checking identity while diffing.
17
+ var diffIdentifier: AnyHashable { get }
18
+ }
@@ -0,0 +1,16 @@
1
+ // Created by eric_horacek on 12/9/20.
2
+ // Copyright © 2020 Airbnb Inc. All rights reserved.
3
+
4
+ // MARK: - DiffableSection
5
+
6
+ /// A protocol that allows us to check identity and equality between sections of `Diffable` items
7
+ /// for the purposes of diffing.
8
+ protocol DiffableSection: Diffable {
9
+ /// The diffable items in this section.
10
+ associatedtype DiffableItems: Collection where
11
+ DiffableItems.Index == Int,
12
+ DiffableItems.Element: Diffable
13
+
14
+ /// The diffable items in this section.
15
+ var diffableItems: DiffableItems { get }
16
+ }
@@ -0,0 +1,187 @@
1
+ // Created by Laura Skelton on 11/25/16.
2
+ // Copyright © 2016 Airbnb. All rights reserved.
3
+
4
+ import Foundation
5
+
6
+ // MARK: - IndexChangeset
7
+
8
+ /// A set of inserts, deletes, updates, and moves that define the changes between two collections.
9
+ struct IndexChangeset {
10
+
11
+ // MARK: Lifecycle
12
+
13
+ init(
14
+ inserts: [Int] = [],
15
+ deletes: [Int] = [],
16
+ updates: [(old: Int, new: Int)] = [],
17
+ moves: [(old: Int, new: Int)] = [],
18
+ newIndices: [Int?] = [],
19
+ duplicates: [[Int]] = [])
20
+ {
21
+ self.inserts = inserts
22
+ self.deletes = deletes
23
+ self.updates = updates
24
+ self.moves = moves
25
+ self.newIndices = newIndices
26
+ self.duplicates = duplicates
27
+ }
28
+
29
+ // MARK: Internal
30
+
31
+ /// The inserted indices needed to get from the old collection to the new collection.
32
+ var inserts: [Int]
33
+
34
+ /// The deleted indices needed to get from the old collection to the new collection.
35
+ var deletes: [Int]
36
+
37
+ /// The updated indices needed to get from the old collection to the new collection.
38
+ var updates: [(old: Int, new: Int)]
39
+
40
+ /// The moved indices needed to get from the old collection to the new collection.
41
+ var moves: [(old: Int, new: Int)]
42
+
43
+ /// A record for each old collection item to its index (if any) is in the new collection.
44
+ ///
45
+ /// The indexes of this `Array` represent the indexes old collection, with elements of the
46
+ /// corresponding index of the same item in the new collection it exists, else `nil`.
47
+ var newIndices: [Int?]
48
+
49
+ /// A record of each element in the new collection that has an identical `diffIdentifier` with
50
+ /// another element in the same collection.
51
+ ///
52
+ /// Each element in the outer `Array` corresponds to a duplicated identifier, with each inner
53
+ /// `[Int]` containing the indexes that share a duplicate identifier in the new collection.
54
+ ///
55
+ /// While the diffing algorithm makes a best effort to handle duplicates, they can lead to
56
+ /// unexpected behavior since identity of elements cannot be established and should be avoided if
57
+ /// possible.
58
+ var duplicates: [[Int]]
59
+
60
+ /// Whether there are any inserts, deletes, moves, or updates in this changeset.
61
+ var isEmpty: Bool {
62
+ inserts.isEmpty && deletes.isEmpty && updates.isEmpty && moves.isEmpty
63
+ }
64
+ }
65
+
66
+ // MARK: - IndexPathChangeset
67
+
68
+ /// A set of inserts, deletes, updates, and moves that define the changes between two collections
69
+ /// with indexes stored as `IndexPath`s.
70
+ struct IndexPathChangeset {
71
+
72
+ // MARK: Lifecycle
73
+
74
+ init(
75
+ inserts: [IndexPath] = [],
76
+ deletes: [IndexPath] = [],
77
+ updates: [(old: IndexPath, new: IndexPath)] = [],
78
+ moves: [(old: IndexPath, new: IndexPath)] = [],
79
+ duplicates: [[IndexPath]] = [])
80
+ {
81
+ self.inserts = inserts
82
+ self.deletes = deletes
83
+ self.updates = updates
84
+ self.moves = moves
85
+ self.duplicates = duplicates
86
+ }
87
+
88
+ // MARK: Internal
89
+
90
+ /// The inserted `IndexPath`s needed to get from the old collection to the new collection.
91
+ var inserts: [IndexPath]
92
+
93
+ /// The deleted `IndexPath`s needed to get from the old collection to the new collection.
94
+ var deletes: [IndexPath]
95
+
96
+ /// The updated `IndexPath`s needed to get from the old collection to the new collection.
97
+ var updates: [(old: IndexPath, new: IndexPath)]
98
+
99
+ /// The moved `IndexPath`s needed to get from the old collection to the new collection.
100
+ var moves: [(old: IndexPath, new: IndexPath)]
101
+
102
+ /// A record for each element in the new collection that has an identical `diffIdentifier` with
103
+ /// another element in the same collection.
104
+ ///
105
+ /// Each element in the outer `Array` corresponds to a duplicated identifier, with each inner
106
+ /// `[IndexPath]` corresponding to the indexes that share a duplicate identifier in the new
107
+ /// collection.
108
+ ///
109
+ /// While the diffing algorithm makes a best effort to handle duplicates, they can lead to
110
+ /// unexpected behavior since identity of elements cannot be established and should be avoided if
111
+ /// possible.
112
+ var duplicates: [[IndexPath]]
113
+
114
+ /// Whether there are any inserts, deletes, moves, or updates in this changeset.
115
+ var isEmpty: Bool {
116
+ inserts.isEmpty && deletes.isEmpty && updates.isEmpty && moves.isEmpty
117
+ }
118
+
119
+ static func += (left: inout IndexPathChangeset, right: IndexPathChangeset) {
120
+ left.inserts += right.inserts
121
+ left.deletes += right.deletes
122
+ left.updates += right.updates
123
+ left.moves += right.moves
124
+ left.duplicates += right.duplicates
125
+ }
126
+ }
127
+
128
+ // MARK: - IndexSetChangeset
129
+
130
+ /// A set of inserts, deletes, updates, and moves that define the changes between two collections
131
+ /// with indexes stored as `IndexSet`.
132
+ struct IndexSetChangeset {
133
+
134
+ // MARK: Lifecycle
135
+
136
+ init(
137
+ inserts: IndexSet = [],
138
+ deletes: IndexSet = [],
139
+ updates: [(old: Int, new: Int)] = [],
140
+ moves: [(old: Int, new: Int)] = [],
141
+ newIndices: [Int?] = [],
142
+ duplicates: [IndexSet] = [])
143
+ {
144
+ self.inserts = inserts
145
+ self.deletes = deletes
146
+ self.updates = updates
147
+ self.moves = moves
148
+ self.newIndices = newIndices
149
+ self.duplicates = duplicates
150
+ }
151
+
152
+ // MARK: Internal
153
+
154
+ /// An `IndexSet` of inserts needed to get from the old collection to the new collection.
155
+ var inserts: IndexSet
156
+
157
+ /// An `IndexSet` of deletes needed to get from the old collection to the new collection.
158
+ var deletes: IndexSet
159
+
160
+ /// The updated indices needed to get from the old collection to the new collection.
161
+ var updates: [(old: Int, new: Int)]
162
+
163
+ /// The moved indices needed to get from the old collection to the new collection.
164
+ var moves: [(old: Int, new: Int)]
165
+
166
+ /// A record for each old collection item of what its index (if any) is in the new collection.
167
+ ///
168
+ /// The indexes of this `Array` represent the indexes old collection, with elements of the
169
+ /// corresponding index of the same item in the new collection it exists, else `nil`.
170
+ var newIndices: [Int?]
171
+
172
+ /// A record for each element in the new collection that has an identical `diffIdentifier` with
173
+ /// another element in the same collection.
174
+ ///
175
+ /// Each element in the `Array` corresponds to a duplicated identifier, with each `IndexSet`
176
+ /// containing the indexes that share a duplicate identifier in the new collection.
177
+ ///
178
+ /// While the diffing algorithm makes a best effort to handle duplicates, they can lead to
179
+ /// unexpected behavior since identity of elements cannot be established and should be avoided if
180
+ /// possible.
181
+ var duplicates: [IndexSet]
182
+
183
+ /// Whether there are any inserts, deletes, moves, or updates in this changeset.
184
+ var isEmpty: Bool {
185
+ inserts.isEmpty && deletes.isEmpty && updates.isEmpty && moves.isEmpty
186
+ }
187
+ }
@@ -0,0 +1,32 @@
1
+ // Created by Laura Skelton on 5/11/17.
2
+ // Copyright © 2017 Airbnb. All rights reserved.
3
+
4
+ /// A set of the minimum changes to get from one array of `DiffableSection`s to another, used for
5
+ /// diffing.
6
+ struct SectionedChangeset {
7
+
8
+ // MARK: Lifecycle
9
+
10
+ init(
11
+ sectionChangeset: IndexSetChangeset,
12
+ itemChangeset: IndexPathChangeset)
13
+ {
14
+ self.sectionChangeset = sectionChangeset
15
+ self.itemChangeset = itemChangeset
16
+ }
17
+
18
+ // MARK: Internal
19
+
20
+ /// A set of the minimum changes to get from one set of sections to another.
21
+ var sectionChangeset: IndexSetChangeset
22
+
23
+ /// A set of the minimum changes to get from one set of items to another, aggregated across all
24
+ /// sections.
25
+ var itemChangeset: IndexPathChangeset
26
+
27
+ /// Whether there are any inserts, deletes, moves, or updates in this changeset.
28
+ var isEmpty: Bool {
29
+ sectionChangeset.isEmpty && itemChangeset.isEmpty
30
+ }
31
+
32
+ }
@@ -0,0 +1,99 @@
1
+ // Created by eric_horacek on 12/9/20.
2
+ // Copyright © 2020 Airbnb Inc. All rights reserved.
3
+
4
+ /// A shared logger that allows consumers to intercept Epoxy assertions and warning messages to pipe
5
+ /// into their own logging systems.
6
+ final class EpoxyLogger {
7
+
8
+ // MARK: Lifecycle
9
+
10
+ init(
11
+ assert: @escaping Assert = { condition, message, file, line in
12
+ // If we default to `Swift.assert` directly with `assert: Assert = Swift.assert`,
13
+ // the call will unexpectedly not respect the -O flag and will crash in release
14
+ // https://github.com/apple/swift/issues/60249
15
+ Swift.assert(condition(), message(), file: file, line: line)
16
+ },
17
+ assertionFailure: @escaping AssertionFailure = { message, file, line in
18
+ // If we default to `Swift.assertionFailure` directly with
19
+ // `assertionFailure: AssertionFailure = Swift.assertionFailure`,
20
+ // the call will unexpectedly not respect the -O flag and will crash in release
21
+ // https://github.com/apple/swift/issues/60249
22
+ Swift.assertionFailure(message(), file: file, line: line)
23
+ },
24
+ warn: @escaping Warn = { message, _, _ in
25
+ #if DEBUG
26
+ // swiftlint:disable:next no_direct_standard_out_logs
27
+ print(message())
28
+ #endif
29
+ })
30
+ {
31
+ _assert = assert
32
+ _assertionFailure = assertionFailure
33
+ _warn = warn
34
+ }
35
+
36
+ // MARK: Internal
37
+
38
+ /// Logs that an assertion occurred.
39
+ typealias Assert = (
40
+ _ condition: @autoclosure () -> Bool,
41
+ _ message: @autoclosure () -> String,
42
+ _ fileID: StaticString,
43
+ _ line: UInt)
44
+ -> Void
45
+
46
+ /// Logs that an assertion failure occurred.
47
+ typealias AssertionFailure = (
48
+ _ message: @autoclosure () -> String,
49
+ _ fileID: StaticString,
50
+ _ line: UInt)
51
+ -> Void
52
+
53
+ /// Logs a warning message.
54
+ typealias Warn = (
55
+ _ message: @autoclosure () -> String,
56
+ _ fileID: StaticString,
57
+ _ line: UInt)
58
+ -> Void
59
+
60
+ /// The shared instance used to log Epoxy assertions and warnings.
61
+ ///
62
+ /// Set this to a new logger instance to intercept assertions and warnings logged by Epoxy.
63
+ static var shared = EpoxyLogger()
64
+
65
+ /// Logs that an assertion occurred.
66
+ func assert(
67
+ _ condition: @autoclosure () -> Bool,
68
+ _ message: @autoclosure () -> String = String(),
69
+ fileID: StaticString = #fileID,
70
+ line: UInt = #line)
71
+ {
72
+ _assert(condition(), message(), fileID, line)
73
+ }
74
+
75
+ /// Logs that an assertion failure occurred.
76
+ func assertionFailure(
77
+ _ message: @autoclosure () -> String = String(),
78
+ fileID: StaticString = #fileID,
79
+ line: UInt = #line)
80
+ {
81
+ _assertionFailure(message(), fileID, line)
82
+ }
83
+
84
+ /// Logs a warning message.
85
+ func warn(
86
+ _ message: @autoclosure () -> String = String(),
87
+ fileID: StaticString = #fileID,
88
+ line: UInt = #line)
89
+ {
90
+ _warn(message(), fileID, line)
91
+ }
92
+
93
+ // MARK: Private
94
+
95
+ private let _assert: Assert
96
+ private let _assertionFailure: AssertionFailure
97
+ private let _warn: Warn
98
+
99
+ }
@@ -0,0 +1,8 @@
1
+ // Created by eric_horacek on 12/15/20.
2
+ // Copyright © 2020 Airbnb Inc. All rights reserved.
3
+
4
+ /// An Epoxy model with an associated context type that's passed into callback closures.
5
+ protocol CallbackContextEpoxyModeled: EpoxyModeled {
6
+ /// A context type that's passed into callback closures.
7
+ associatedtype CallbackContext
8
+ }