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.
- package/.github/workflows/main.yml +117 -11
- package/Gemfile +1 -0
- package/Gemfile.lock +5 -0
- package/Lottie.xcodeproj/project.pbxproj +1542 -194
- package/Lottie.xcodeproj/project.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (iOS).xcscheme +1 -1
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (macOS).xcscheme +1 -1
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (tvOS).xcscheme +4 -5
- package/Lottie.xcodeproj/xcshareddata/xcschemes/Lottie (visionOS).xcscheme +66 -0
- package/Lottie.xcodeproj/xcuserdata/calstephens.xcuserdatad/xcschemes/xcschememanagement.plist +18 -0
- package/Lottie.xcworkspace/xcshareddata/swiftpm/Package.resolved +2 -11
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +346 -102
- package/Lottie.xcworkspace/xcuserdata/calstephens.xcuserdatad/xcdebugger/Expressions.xcexplist +16 -5
- package/Package.resolved +2 -2
- package/Package.swift +16 -9
- package/README.md +2 -2
- package/Rakefile +122 -25
- package/Sources/Private/CoreAnimation/Animations/DropShadowAnimation.swift +160 -0
- package/Sources/Private/CoreAnimation/Animations/LayerProperty.swift +83 -10
- package/Sources/Private/CoreAnimation/Animations/TransformAnimations.swift +85 -79
- package/Sources/Private/CoreAnimation/CoreAnimationLayer.swift +7 -7
- package/Sources/Private/CoreAnimation/Extensions/Keyframes+combined.swift +67 -6
- package/Sources/Private/CoreAnimation/Layers/AnimationLayer.swift +2 -2
- package/Sources/Private/CoreAnimation/Layers/BaseCompositionLayer.swift +9 -0
- package/Sources/Private/CoreAnimation/Layers/ImageLayer.swift +1 -0
- package/Sources/Private/CoreAnimation/Layers/LayerModel+makeAnimationLayer.swift +1 -1
- package/Sources/Private/CoreAnimation/Layers/RepeaterLayer.swift +2 -0
- package/Sources/Private/CoreAnimation/Layers/ShapeLayer.swift +11 -13
- package/Sources/Private/CoreAnimation/Layers/SolidLayer.swift +20 -5
- package/Sources/Private/CoreAnimation/Layers/TextLayer.swift +13 -3
- package/Sources/Private/CoreAnimation/ValueProviderStore.swift +6 -4
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Collection+Diff.swift +263 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/Diffable.swift +18 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/DiffableSection.swift +16 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/IndexChangeset.swift +187 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Diffing/SectionedChangeset.swift +32 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Logging/EpoxyLogger.swift +99 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/CallbackContextEpoxyModeled.swift +8 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelArrayBuilder.swift +48 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelProperty.swift +158 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModelStorage.swift +88 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/EpoxyModeled.swift +54 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/AnyEpoxyModelProperty.swift +29 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Internal/ClassReference.swift +39 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/AnimatedProviding.swift +10 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DataIDProviding.swift +57 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidDisplayProviding.swift +41 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidEndDisplayingProviding.swift +41 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/DidSelectProviding.swift +36 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ErasedContentProviding.swift +49 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/MakeViewProviding.swift +60 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetBehaviorsProviding.swift +38 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/SetContentProviding.swift +38 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/StyleIDProviding.swift +37 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/TraitCollectionProviding.swift +14 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewDifferentiatorProviding.swift +34 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/ViewProviding.swift +13 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/Providers/WillDisplayProviding.swift +41 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Model/ViewEpoxyModeled.swift +10 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/README.md +31 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingController.swift +46 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIHostingView.swift +391 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUIIntrinsicContentSizeInvalidator.swift +44 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxySwiftUILayoutMargins.swift +51 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/EpoxyableView+SwiftUIView.swift +172 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/MeasuringViewRepresentable.swift +128 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/LayoutUtilities/SwiftUIMeasurementContainer.swift +452 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/SwiftUIView.swift +148 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIView+SwiftUIView.swift +40 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/SwiftUI/UIViewConfiguringSwiftUIView.swift +43 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/BehaviorsConfigurableView.swift +45 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ContentConfigurableView.swift +36 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/EpoxyableView.swift +5 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/StyledView.swift +42 -0
- package/Sources/Private/EmbeddedLibraries/EpoxyCore/Views/ViewType.swift +51 -0
- package/Sources/Private/EmbeddedLibraries/README.md +27 -0
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+BackingConfiguration.swift +4 -4
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+MemoryFile.swift +2 -2
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Writing.swift +4 -4
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive.swift +8 -7
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Compression.swift +5 -5
- package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/FileManager+ZIP.swift +4 -4
- package/Sources/Private/MainThread/LayerContainers/CompLayers/ImageCompositionLayer.swift +6 -0
- package/Sources/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.swift +3 -1
- package/Sources/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.swift +18 -5
- package/Sources/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.swift +31 -3
- package/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +33 -8
- package/Sources/Private/MainThread/LayerContainers/Utility/CachedImageProvider.swift +8 -1
- package/Sources/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.swift +13 -4
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerFontProvider.swift +2 -2
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerImageProvider.swift +1 -0
- package/Sources/Private/MainThread/LayerContainers/Utility/LayerTextProvider.swift +4 -4
- package/Sources/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +3 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +2 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/Renderables/LegacyGradientFillRenderer.swift +2 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +2 -2
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +1 -1
- package/Sources/Private/MainThread/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +6 -2
- package/Sources/Private/Model/Assets/Asset.swift +8 -0
- package/Sources/Private/Model/Assets/AssetLibrary.swift +2 -2
- package/Sources/Private/Model/Assets/ImageAsset.swift +3 -3
- package/Sources/Private/Model/DotLottie/DotLottieAnimation.swift +16 -2
- package/Sources/Private/Model/DotLottie/DotLottieImageProvider.swift +20 -7
- package/Sources/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +26 -0
- package/Sources/Private/Model/Keyframes/KeyframeGroup.swift +8 -5
- package/Sources/Private/Model/LayerEffects/DropShadowEffect.swift +45 -0
- package/Sources/Private/Model/LayerEffects/EffectValues/ColorEffectValue.swift +38 -0
- package/Sources/Private/Model/LayerEffects/EffectValues/EffectValue.swift +98 -0
- package/Sources/Private/Model/LayerEffects/EffectValues/Vector1DEffectValue.swift +38 -0
- package/Sources/Private/Model/LayerEffects/LayerEffect.swift +103 -0
- package/Sources/Private/Model/LayerStyles/DropShadowStyle.swift +72 -0
- package/Sources/Private/Model/LayerStyles/LayerStyle.swift +85 -0
- package/Sources/Private/Model/Layers/LayerModel.swift +27 -0
- package/Sources/Private/Model/Objects/Marker.swift +1 -1
- package/Sources/Private/Model/Objects/Transform.swift +1 -2
- package/Sources/Private/Model/ShapeItems/GradientFill.swift +1 -1
- package/Sources/Private/Model/ShapeItems/GradientStroke.swift +2 -2
- package/Sources/Private/Model/ShapeItems/Merge.swift +1 -1
- package/Sources/Private/Model/ShapeItems/ShapeItem.swift +31 -26
- package/Sources/Private/Model/ShapeItems/ShapeTransform.swift +0 -9
- package/Sources/Private/Model/ShapeItems/Star.swift +1 -1
- package/Sources/Private/Model/Text/Font.swift +2 -2
- package/Sources/Private/Model/Text/Glyph.swift +1 -1
- package/Sources/Private/RootAnimationLayer.swift +2 -2
- package/Sources/Private/Utility/Debugging/LayerDebugging.swift +3 -1
- package/Sources/Private/Utility/Extensions/AnimationKeypathExtension.swift +32 -2
- package/Sources/Private/Utility/Helpers/AnimationContext.swift +4 -2
- package/Sources/Private/Utility/Helpers/AnyEquatable.swift +24 -0
- package/Sources/Private/Utility/Helpers/Binding+Map.swift +18 -0
- package/Sources/Private/Utility/Helpers/View+ValueChanged.swift +20 -0
- package/Sources/Private/Utility/LottieAnimationSource.swift +41 -0
- package/Sources/Private/Utility/Primitives/BezierPath.swift +2 -2
- package/Sources/Private/Utility/Primitives/BezierPathRoundExtension.swift +2 -2
- package/Sources/Private/Utility/Primitives/VectorsExtensions.swift +1 -1
- package/Sources/Public/Animation/LottieAnimation.swift +21 -2
- package/Sources/Public/Animation/LottieAnimationLayer.swift +1464 -0
- package/Sources/Public/Animation/LottieAnimationView.swift +258 -735
- package/Sources/Public/Animation/LottiePlaybackMode.swift +117 -0
- package/Sources/Public/Animation/LottieView.swift +462 -0
- package/Sources/Public/AnimationCache/AnimationCacheProvider.swift +2 -1
- package/Sources/Public/AnimationCache/DefaultAnimationCache.swift +5 -6
- package/Sources/Public/Configuration/DecodingStrategy.swift +15 -0
- package/Sources/Public/Configuration/LottieConfiguration.swift +47 -0
- package/Sources/Public/Configuration/ReducedMotionOption.swift +114 -0
- package/Sources/Public/{LottieConfiguration.swift → Configuration/RenderingEngineOption.swift} +2 -57
- package/Sources/Public/{iOS → Controls}/AnimatedButton.swift +56 -13
- package/Sources/Public/{iOS → Controls}/AnimatedControl.swift +80 -8
- package/Sources/Public/{iOS → Controls}/AnimatedSwitch.swift +71 -31
- package/Sources/Public/Controls/LottieButton.swift +122 -0
- package/Sources/Public/Controls/LottieSwitch.swift +144 -0
- package/Sources/Public/Controls/LottieViewType.swift +79 -0
- package/Sources/Public/DotLottie/DotLottieConfiguration.swift +24 -0
- package/Sources/Public/DotLottie/DotLottieFile.swift +21 -6
- package/Sources/Public/DotLottie/DotLottieFileHelpers.swift +79 -0
- package/Sources/Public/DynamicProperties/AnimationKeypath.swift +11 -1
- package/Sources/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +14 -0
- package/Sources/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +13 -0
- package/Sources/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +22 -5
- package/Sources/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +14 -0
- package/Sources/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +13 -0
- package/Sources/Public/FontProvider/AnimationFontProvider.swift +8 -0
- package/Sources/Public/ImageProvider/AnimationImageProvider.swift +24 -0
- package/Sources/Public/Keyframes/Keyframe.swift +6 -0
- package/Sources/Public/Primitives/Vectors.swift +2 -2
- package/Sources/Public/TextProvider/AnimationTextProvider.swift +79 -6
- package/Sources/Public/iOS/AnimationSubview.swift +1 -1
- package/Sources/Public/iOS/BundleImageProvider.swift +16 -2
- package/Sources/Public/iOS/Compatibility/CompatibleAnimationView.swift +2 -2
- package/Sources/Public/iOS/FilepathImageProvider.swift +22 -3
- package/Sources/Public/iOS/LottieAnimationViewBase.swift +7 -1
- package/Sources/Public/macOS/BundleImageProvider.macOS.swift +15 -1
- package/Sources/Public/macOS/FilepathImageProvider.macOS.swift +21 -2
- package/lottie-ios.podspec +2 -1
- package/package.json +1 -1
- package/Sources/Private/Model/DotLottie/DotLottieConfiguration.swift +0 -15
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Helpers.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Progress.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+Reading.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ReadingDeprecated.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+WritingDeprecated.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Archive+ZIP64.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+CompressionDeprecated.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Data+Serialization.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+Serialization.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry+ZIP64.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/Entry.swift +0 -0
- /package/Sources/Private/{Model/DotLottie → EmbeddedLibraries}/ZipFoundation/README.md +0 -0
- /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 \(
|
|
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
|
|
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
|
+
}
|