react-native-list 1.0.0 → 2.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +190 -32
- package/ReactNativeList.podspec +39 -0
- package/android/CMakeLists.txt +48 -0
- package/android/build.gradle +151 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/JHybridUiListModule.cpp +192 -0
- package/android/src/main/cpp/JHybridUiListModule.h +50 -0
- package/android/src/main/cpp/cpp-adapter.cpp +12 -0
- package/android/src/main/java/com/hannojg/reactnativelist/ReactNativeListPackage.kt +27 -0
- package/android/src/main/java/com/margelo/nitro/reactnativelist/HybridNativeListDataSource.kt +146 -0
- package/android/src/main/java/com/margelo/nitro/reactnativelist/HybridNativeListLayout.kt +86 -0
- package/android/src/main/java/com/margelo/nitro/reactnativelist/HybridUiListModule.kt +116 -0
- package/android/src/main/java/com/margelo/nitro/reactnativelist/HybridUiListView.kt +410 -0
- package/android/src/main/java/com/margelo/nitro/reactnativelist/HybridViewHolder.kt +9 -0
- package/android/src/main/java/com/margelo/nitro/reactnativelist/NativeListAdapter.kt +217 -0
- package/ios/DataSource/HybridNativeListDataSource.swift +213 -0
- package/ios/HybridObjects/HybridUiListModule.swift +49 -0
- package/ios/HybridObjects/HybridViewHolder.swift +16 -0
- package/ios/Layout/HybridNativeListLayout.swift +128 -0
- package/ios/Utils/ErrorUtils.h +26 -0
- package/ios/Utils/HybridIOSWorkletsModuleProxyHolder.swift +10 -0
- package/ios/Utils/SurfaceHelper.h +20 -0
- package/ios/Utils/SurfaceHelper.mm +144 -0
- package/ios/Utils/SurfacePresenterRegistry.h +17 -0
- package/ios/Utils/SurfacePresenterRegistry.m +31 -0
- package/ios/Utils/TurboModuleInstaller.h +18 -0
- package/ios/Utils/TurboModuleInstaller.mm +267 -0
- package/ios/Views/HostCell.swift +216 -0
- package/ios/Views/HybridUiListView.swift +695 -0
- package/metro/RendererProxyThreadSwitch.js +66 -0
- package/metro-config.d.ts +1 -0
- package/metro-config.js +52 -0
- package/nitro.json +47 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/ReactNativeList+autolinking.cmake +99 -0
- package/nitrogen/generated/android/ReactNativeList+autolinking.gradle +27 -0
- package/nitrogen/generated/android/ReactNativeListOnLoad.cpp +156 -0
- package/nitrogen/generated/android/ReactNativeListOnLoad.hpp +34 -0
- package/nitrogen/generated/android/c++/JFunc_bool_NativeListItem_NativeListItem.hpp +83 -0
- package/nitrogen/generated/android/c++/JFunc_bool_double_NativeListItem_double.hpp +83 -0
- package/nitrogen/generated/android/c++/JFunc_double_std__string.hpp +78 -0
- package/nitrogen/generated/android/c++/JHybridIOSWorkletsModuleProxyHolderSpec.cpp +49 -0
- package/nitrogen/generated/android/c++/JHybridIOSWorkletsModuleProxyHolderSpec.hpp +63 -0
- package/nitrogen/generated/android/c++/JHybridNativeLinearListLayoutSpec.cpp +63 -0
- package/nitrogen/generated/android/c++/JHybridNativeLinearListLayoutSpec.hpp +65 -0
- package/nitrogen/generated/android/c++/JHybridNativeListDataSourceSpec.cpp +101 -0
- package/nitrogen/generated/android/c++/JHybridNativeListDataSourceSpec.hpp +70 -0
- package/nitrogen/generated/android/c++/JHybridNativeListLayoutSpec.cpp +49 -0
- package/nitrogen/generated/android/c++/JHybridNativeListLayoutSpec.hpp +63 -0
- package/nitrogen/generated/android/c++/JHybridUiListModuleSpec.cpp +65 -0
- package/nitrogen/generated/android/c++/JHybridUiListModuleSpec.hpp +64 -0
- package/nitrogen/generated/android/c++/JHybridUiListViewSpec.cpp +92 -0
- package/nitrogen/generated/android/c++/JHybridUiListViewSpec.hpp +67 -0
- package/nitrogen/generated/android/c++/JHybridViewHolderSpec.cpp +49 -0
- package/nitrogen/generated/android/c++/JHybridViewHolderSpec.hpp +63 -0
- package/nitrogen/generated/android/c++/JNativeItemSizeEstimate.hpp +61 -0
- package/nitrogen/generated/android/c++/JNativeLinearListLayoutConfig.hpp +81 -0
- package/nitrogen/generated/android/c++/JNativeLinearListLayoutIOSConfig.hpp +59 -0
- package/nitrogen/generated/android/c++/JNativeListItem.hpp +76 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_HybridIOSWorkletsModuleProxyHolderSpec.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_HybridIOSWorkletsModuleProxyHolderSpec.hpp +72 -0
- package/nitrogen/generated/android/c++/views/JHybridUiListViewStateUpdater.cpp +53 -0
- package/nitrogen/generated/android/c++/views/JHybridUiListViewStateUpdater.hpp +49 -0
- package/nitrogen/generated/android/c++/views/JHybridViewHolderStateUpdater.cpp +53 -0
- package/nitrogen/generated/android/c++/views/JHybridViewHolderStateUpdater.hpp +49 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/Func_bool_NativeListItem_NativeListItem.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/Func_bool_double_NativeListItem_double.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/Func_double_std__string.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridIOSWorkletsModuleProxyHolderSpec.kt +52 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridNativeLinearListLayoutSpec.kt +54 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridNativeListDataSourceSpec.kt +87 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridNativeListLayoutSpec.kt +52 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridUiListModuleSpec.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridUiListViewSpec.kt +76 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/HybridViewHolderSpec.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/NativeItemSizeEstimate.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/NativeLinearListLayoutConfig.kt +76 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/NativeLinearListLayoutIOSConfig.kt +51 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/NativeListItem.kt +71 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/ReactNativeListOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/Variant_NullType_HybridIOSWorkletsModuleProxyHolderSpec.kt +62 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/views/HybridUiListViewManager.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/views/HybridUiListViewStateUpdater.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/views/HybridViewHolderManager.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativelist/views/HybridViewHolderStateUpdater.kt +23 -0
- package/nitrogen/generated/ios/ReactNativeList+autolinking.rb +62 -0
- package/nitrogen/generated/ios/ReactNativeList-Swift-Cxx-Bridge.cpp +162 -0
- package/nitrogen/generated/ios/ReactNativeList-Swift-Cxx-Bridge.hpp +368 -0
- package/nitrogen/generated/ios/ReactNativeList-Swift-Cxx-Umbrella.hpp +92 -0
- package/nitrogen/generated/ios/ReactNativeListAutolinking.mm +83 -0
- package/nitrogen/generated/ios/ReactNativeListAutolinking.swift +86 -0
- package/nitrogen/generated/ios/c++/HybridIOSWorkletsModuleProxyHolderSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridIOSWorkletsModuleProxyHolderSpecSwift.hpp +75 -0
- package/nitrogen/generated/ios/c++/HybridNativeLinearListLayoutSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNativeLinearListLayoutSpecSwift.hpp +92 -0
- package/nitrogen/generated/ios/c++/HybridNativeListDataSourceSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNativeListDataSourceSpecSwift.hpp +132 -0
- package/nitrogen/generated/ios/c++/HybridNativeListLayoutSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNativeListLayoutSpecSwift.hpp +75 -0
- package/nitrogen/generated/ios/c++/HybridUiListModuleSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridUiListModuleSpecSwift.hpp +93 -0
- package/nitrogen/generated/ios/c++/HybridUiListViewSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridUiListViewSpecSwift.hpp +121 -0
- package/nitrogen/generated/ios/c++/HybridViewHolderSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridViewHolderSpecSwift.hpp +75 -0
- package/nitrogen/generated/ios/c++/views/HybridUiListViewComponent.mm +118 -0
- package/nitrogen/generated/ios/c++/views/HybridViewHolderComponent.mm +118 -0
- package/nitrogen/generated/ios/swift/Func_bool_NativeListItem_NativeListItem.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_bool_double_NativeListItem_double.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_double_std__string.swift +47 -0
- package/nitrogen/generated/ios/swift/HybridIOSWorkletsModuleProxyHolderSpec.swift +55 -0
- package/nitrogen/generated/ios/swift/HybridIOSWorkletsModuleProxyHolderSpec_cxx.swift +128 -0
- package/nitrogen/generated/ios/swift/HybridNativeLinearListLayoutSpec.swift +55 -0
- package/nitrogen/generated/ios/swift/HybridNativeLinearListLayoutSpec_cxx.swift +140 -0
- package/nitrogen/generated/ios/swift/HybridNativeListDataSourceSpec.swift +62 -0
- package/nitrogen/generated/ios/swift/HybridNativeListDataSourceSpec_cxx.swift +222 -0
- package/nitrogen/generated/ios/swift/HybridNativeListLayoutSpec.swift +55 -0
- package/nitrogen/generated/ios/swift/HybridNativeListLayoutSpec_cxx.swift +128 -0
- package/nitrogen/generated/ios/swift/HybridUiListModuleSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridUiListModuleSpec_cxx.swift +175 -0
- package/nitrogen/generated/ios/swift/HybridUiListViewSpec.swift +59 -0
- package/nitrogen/generated/ios/swift/HybridUiListViewSpec_cxx.swift +227 -0
- package/nitrogen/generated/ios/swift/HybridViewHolderSpec.swift +55 -0
- package/nitrogen/generated/ios/swift/HybridViewHolderSpec_cxx.swift +147 -0
- package/nitrogen/generated/ios/swift/NativeItemSizeEstimate.swift +60 -0
- package/nitrogen/generated/ios/swift/NativeLinearListLayoutConfig.swift +60 -0
- package/nitrogen/generated/ios/swift/NativeLinearListLayoutIOSConfig.swift +35 -0
- package/nitrogen/generated/ios/swift/NativeListItem.swift +75 -0
- package/nitrogen/generated/ios/swift/Variant_NullType__any_HybridIOSWorkletsModuleProxyHolderSpec_.swift +30 -0
- package/nitrogen/generated/shared/c++/HybridIOSWorkletsModuleProxyHolderSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridIOSWorkletsModuleProxyHolderSpec.hpp +62 -0
- package/nitrogen/generated/shared/c++/HybridNativeLinearListLayoutSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridNativeLinearListLayoutSpec.hpp +67 -0
- package/nitrogen/generated/shared/c++/HybridNativeListDataSourceSpec.cpp +28 -0
- package/nitrogen/generated/shared/c++/HybridNativeListDataSourceSpec.hpp +72 -0
- package/nitrogen/generated/shared/c++/HybridNativeListLayoutSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridNativeListLayoutSpec.hpp +62 -0
- package/nitrogen/generated/shared/c++/HybridUiListModuleSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridUiListModuleSpec.hpp +68 -0
- package/nitrogen/generated/shared/c++/HybridUiListViewSpec.cpp +25 -0
- package/nitrogen/generated/shared/c++/HybridUiListViewSpec.hpp +79 -0
- package/nitrogen/generated/shared/c++/HybridUiManagerHelperSpec.cpp +23 -0
- package/nitrogen/generated/shared/c++/HybridUiManagerHelperSpec.hpp +65 -0
- package/nitrogen/generated/shared/c++/HybridViewHolderSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridViewHolderSpec.hpp +62 -0
- package/nitrogen/generated/shared/c++/NativeItemSizeEstimate.hpp +87 -0
- package/nitrogen/generated/shared/c++/NativeLinearListLayoutConfig.hpp +105 -0
- package/nitrogen/generated/shared/c++/NativeLinearListLayoutIOSConfig.hpp +85 -0
- package/nitrogen/generated/shared/c++/NativeListItem.hpp +101 -0
- package/nitrogen/generated/shared/c++/views/HybridUiListViewComponent.cpp +72 -0
- package/nitrogen/generated/shared/c++/views/HybridUiListViewComponent.hpp +109 -0
- package/nitrogen/generated/shared/c++/views/HybridViewHolderComponent.cpp +72 -0
- package/nitrogen/generated/shared/c++/views/HybridViewHolderComponent.hpp +109 -0
- package/nitrogen/generated/shared/json/UiListViewConfig.json +9 -0
- package/nitrogen/generated/shared/json/ViewHolderConfig.json +9 -0
- package/package.json +152 -5
- package/react-native.config.js +16 -0
- package/src/ListDataSource.ts +232 -0
- package/src/ListLayout.ts +95 -0
- package/src/UiListModule.ts +5 -0
- package/src/hooks/useChangeEffect.ts +50 -0
- package/src/index.tsx +49 -0
- package/src/privateGlobals.ts +20 -0
- package/src/renderer/fabric/RenderHelper.ts +29 -0
- package/src/renderer/fabric/UiManagerHelper.ts +5 -0
- package/src/renderer/react/ReactFabricMirror.bundle.js +1984 -0
- package/src/renderer/react/ReactFabricMirror.ts +766 -0
- package/src/renderer/react/ReactFabricRenderer.ts +11 -0
- package/src/specs/IOSWorkletsModuleProxyHolder.nitro.ts +6 -0
- package/src/specs/NativeLinearListLayout.nitro.ts +23 -0
- package/src/specs/NativeListDataSource.nitro.ts +28 -0
- package/src/specs/NativeListLayout.nitro.ts +6 -0
- package/src/specs/UIListModule.nitro.ts +13 -0
- package/src/specs/UIManagerHelper.nitro.ts +34 -0
- package/src/specs/UiListView.nitro.ts +31 -0
- package/src/specs/ViewHolder.nitro.ts +11 -0
- package/src/views/List.tsx +525 -0
- package/src/views/UiListHostComponent.ts +8 -0
- package/FillRateHelper.js +0 -179
- package/FlatList.js +0 -494
- package/LICENSE.md +0 -31
- package/ListView/__mocks__/ListViewMock.js +0 -55
- package/MetroListView.js +0 -166
- package/SectionList.js +0 -291
- package/ViewabilityHelper.js +0 -260
- package/VirtualizeUtils.js +0 -163
- package/VirtualizedList.js +0 -861
- package/VirtualizedSectionList.js +0 -397
- package/__flowtests__/FlatList-flowtest.js +0 -90
- package/__flowtests__/SectionList-flowtest.js +0 -89
- package/__tests__/FillRateHelper-test.js +0 -106
- package/__tests__/FlatList-test.js +0 -64
- package/__tests__/SectionList-test.js +0 -67
- package/__tests__/ViewabilityHelper-test.js +0 -359
- package/__tests__/VirtualizeUtils-test.js +0 -74
- package/__tests__/__snapshots__/FlatList-test.js.snap +0 -251
- package/__tests__/__snapshots__/SectionList-test.js.snap +0 -283
- package/index.js +0 -5
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HybridUiListView.swift
|
|
3
|
+
// ReactNativeList
|
|
4
|
+
//
|
|
5
|
+
// Created by Hanno Gödecke on 14.02.26.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import DifferenceKit
|
|
9
|
+
import Foundation
|
|
10
|
+
import NitroModules
|
|
11
|
+
import UIKit
|
|
12
|
+
|
|
13
|
+
final class CollectionViewDataSourceProxy: NSObject, UICollectionViewDataSource {
|
|
14
|
+
weak var owner: HybridUiListView?
|
|
15
|
+
|
|
16
|
+
init(owner: HybridUiListView) {
|
|
17
|
+
self.owner = owner
|
|
18
|
+
super.init()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func numberOfSections(in collectionView: UICollectionView) -> Int {
|
|
22
|
+
return owner?.numberOfSections(in: collectionView) ?? 0
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func collectionView(
|
|
26
|
+
_ collectionView: UICollectionView,
|
|
27
|
+
numberOfItemsInSection section: Int
|
|
28
|
+
) -> Int {
|
|
29
|
+
return owner?.collectionView(collectionView, numberOfItemsInSection: section) ?? 0
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func collectionView(
|
|
33
|
+
_ collectionView: UICollectionView,
|
|
34
|
+
cellForItemAt indexPath: IndexPath
|
|
35
|
+
) -> UICollectionViewCell {
|
|
36
|
+
guard let owner else {
|
|
37
|
+
return UICollectionViewCell()
|
|
38
|
+
}
|
|
39
|
+
return owner.collectionView(collectionView, cellForItemAt: indexPath)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
final class CollectionViewDelegateProxy: NSObject, UICollectionViewDelegateFlowLayout {
|
|
44
|
+
weak var owner: HybridUiListView?
|
|
45
|
+
|
|
46
|
+
init(owner: HybridUiListView) {
|
|
47
|
+
self.owner = owner
|
|
48
|
+
super.init()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func collectionView(
|
|
52
|
+
_ collectionView: UICollectionView,
|
|
53
|
+
layout collectionViewLayout: UICollectionViewLayout,
|
|
54
|
+
sizeForItemAt indexPath: IndexPath
|
|
55
|
+
) -> CGSize {
|
|
56
|
+
guard let owner else {
|
|
57
|
+
return .zero
|
|
58
|
+
}
|
|
59
|
+
return owner.collectionView(
|
|
60
|
+
collectionView,
|
|
61
|
+
layout: collectionViewLayout,
|
|
62
|
+
sizeForItemAt: indexPath
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private struct HostedFabricViewRestorePoint {
|
|
68
|
+
let view: UIView
|
|
69
|
+
let originalSuperview: UIView
|
|
70
|
+
let originalIndex: Int
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
class HybridUiListView : HybridUiListViewSpec {
|
|
74
|
+
let view: UIView
|
|
75
|
+
|
|
76
|
+
private var collectionView: UICollectionView?
|
|
77
|
+
private var collectionDataSourceProxy: CollectionViewDataSourceProxy?
|
|
78
|
+
private var collectionDelegateProxy: CollectionViewDelegateProxy?
|
|
79
|
+
private var dataSource: HybridNativeListDataSource?
|
|
80
|
+
private var layoutProvider: NativeListLayoutProviding = HybridNativeLinearListLayout()
|
|
81
|
+
private var registeredReuseIdentifiers = Set<String>()
|
|
82
|
+
private var measuredContentSizeByItemKey: [String: CGSize] = [:]
|
|
83
|
+
private var hasScheduledLayoutInvalidation = false
|
|
84
|
+
private let measuredSizeTolerance: CGFloat = 0.5
|
|
85
|
+
private var rendererSurfaceId: ReactTag?
|
|
86
|
+
// Fabric unmount asserts that a child is still mounted under its original parent.
|
|
87
|
+
// Cells temporarily host those views, so teardown must restore the parent first.
|
|
88
|
+
private var fabricRestorePointsByReactTag: [ReactTag: HostedFabricViewRestorePoint] = [:]
|
|
89
|
+
private let hostedCells = NSHashTable<HostCell>.weakObjects()
|
|
90
|
+
|
|
91
|
+
private var createViewCallback: ((_ type: String) -> Double)?
|
|
92
|
+
private var updateViewCallback: ((_ reactTag: Double, _ item: NativeListItem, _ index: Double) -> Bool)?
|
|
93
|
+
|
|
94
|
+
override init() {
|
|
95
|
+
view = UIView(frame: .zero)
|
|
96
|
+
super.init()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func getSurfaceId() throws -> Double {
|
|
100
|
+
if let rendererSurfaceId {
|
|
101
|
+
return Double(rendererSurfaceId)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
var createdSurfaceId: ReactTag?
|
|
105
|
+
var capturedError: Error?
|
|
106
|
+
|
|
107
|
+
let createSurface = {
|
|
108
|
+
do {
|
|
109
|
+
let surfaceId = try SurfaceHelper.createExternalSurface()
|
|
110
|
+
createdSurfaceId = ReactTag(truncating: surfaceId)
|
|
111
|
+
} catch {
|
|
112
|
+
capturedError = error
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if Thread.isMainThread {
|
|
117
|
+
createSurface()
|
|
118
|
+
} else {
|
|
119
|
+
DispatchQueue.main.sync(execute: createSurface)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if let capturedError {
|
|
123
|
+
let message = String(describing: capturedError)
|
|
124
|
+
throw RuntimeError.error(withMessage: message)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
guard let createdSurfaceId else {
|
|
128
|
+
throw RuntimeError.error(withMessage: "Could not create renderer surface.")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
rendererSurfaceId = createdSurfaceId
|
|
132
|
+
return Double(createdSurfaceId)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
func disposeRendererSurface() throws {
|
|
136
|
+
let surfaceId = rendererSurfaceId
|
|
137
|
+
rendererSurfaceId = nil
|
|
138
|
+
createViewCallback = nil
|
|
139
|
+
updateViewCallback = nil
|
|
140
|
+
|
|
141
|
+
var capturedError: Error?
|
|
142
|
+
|
|
143
|
+
let disposeSurface = {
|
|
144
|
+
self.dataSource?.observer = nil
|
|
145
|
+
self.dataSource = nil
|
|
146
|
+
self.collectionView?.dataSource = nil
|
|
147
|
+
self.collectionView?.delegate = nil
|
|
148
|
+
// RCTFabricSurface.stop() commits an empty tree. Let Fabric perform that cleanup
|
|
149
|
+
// after the UIKit hierarchy matches Fabric's expected parent-child structure again (otherwise we crash).
|
|
150
|
+
self.restoreFabricViewHierarchy()
|
|
151
|
+
self.collectionView?.removeFromSuperview()
|
|
152
|
+
self.collectionView = nil
|
|
153
|
+
self.collectionDataSourceProxy = nil
|
|
154
|
+
self.collectionDelegateProxy = nil
|
|
155
|
+
self.registeredReuseIdentifiers.removeAll()
|
|
156
|
+
self.measuredContentSizeByItemKey.removeAll()
|
|
157
|
+
self.hasScheduledLayoutInvalidation = false
|
|
158
|
+
|
|
159
|
+
guard let surfaceId else {
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
do {
|
|
164
|
+
_ = try SurfaceHelper.releaseExternalSurface(surfaceId)
|
|
165
|
+
} catch {
|
|
166
|
+
capturedError = error
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if Thread.isMainThread {
|
|
171
|
+
disposeSurface()
|
|
172
|
+
} else {
|
|
173
|
+
DispatchQueue.main.sync(execute: disposeSurface)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if let capturedError {
|
|
177
|
+
let message = String(describing: capturedError)
|
|
178
|
+
throw RuntimeError.error(withMessage: message)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
func onDropView() {
|
|
183
|
+
do {
|
|
184
|
+
try disposeRendererSurface()
|
|
185
|
+
} catch {
|
|
186
|
+
print("Failed to dispose list renderer surface: \(error)")
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
func setListCallbacks(
|
|
191
|
+
uiListModule: any HybridUiListModuleSpec,
|
|
192
|
+
createView: @escaping (String) -> Double,
|
|
193
|
+
updateView: @escaping (Double, NativeListItem, Double) -> Bool
|
|
194
|
+
) throws {
|
|
195
|
+
createViewCallback = createView
|
|
196
|
+
updateViewCallback = updateView
|
|
197
|
+
runOnMain { [weak self] in
|
|
198
|
+
self?.configureCollectionViewIfNeeded()
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
func setDataSource(dataSource nextDataSource: any HybridNativeListDataSourceSpec) throws {
|
|
203
|
+
guard let concreteDataSource = nextDataSource as? HybridNativeListDataSource else {
|
|
204
|
+
throw RuntimeError.error(withMessage: "NativeListDataSource must be created by react-native-list.")
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
runOnMain { [weak self] in
|
|
208
|
+
guard let self else { return }
|
|
209
|
+
self.dataSource?.observer = nil
|
|
210
|
+
self.dataSource = concreteDataSource
|
|
211
|
+
concreteDataSource.observer = self
|
|
212
|
+
self.configureCollectionViewIfNeeded()
|
|
213
|
+
self.retainMeasuredContent(in: concreteDataSource)
|
|
214
|
+
self.collectionView?.collectionViewLayout.invalidateLayout()
|
|
215
|
+
self.collectionView?.reloadData()
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
func setLayout(layout: any HybridNativeListLayoutSpec) throws {
|
|
220
|
+
guard let nextLayout = layout as? NativeListLayoutProviding else {
|
|
221
|
+
throw RuntimeError.error(withMessage: "NativeListLayout must provide a platform layout.")
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
runOnMain { [weak self] in
|
|
225
|
+
guard let self else { return }
|
|
226
|
+
layoutProvider = nextLayout
|
|
227
|
+
configureCollectionViewIfNeeded()
|
|
228
|
+
let collectionViewLayout = nextLayout.makeCollectionViewLayout(owner: self)
|
|
229
|
+
collectionView?.setCollectionViewLayout(collectionViewLayout, animated: false)
|
|
230
|
+
updateVisibleCellContentLayouts()
|
|
231
|
+
collectionView?.collectionViewLayout.invalidateLayout()
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private func configureCollectionViewIfNeeded() {
|
|
236
|
+
guard collectionView == nil else { return }
|
|
237
|
+
|
|
238
|
+
let layout = layoutProvider.makeCollectionViewLayout(owner: self)
|
|
239
|
+
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
|
240
|
+
collectionView.backgroundColor = .clear
|
|
241
|
+
collectionView.isOpaque = false
|
|
242
|
+
collectionView.translatesAutoresizingMaskIntoConstraints = false
|
|
243
|
+
|
|
244
|
+
view.isOpaque = false
|
|
245
|
+
view.addSubview(collectionView)
|
|
246
|
+
NSLayoutConstraint.activate([
|
|
247
|
+
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
|
|
248
|
+
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
249
|
+
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
250
|
+
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
|
251
|
+
])
|
|
252
|
+
|
|
253
|
+
let dataSourceProxy = CollectionViewDataSourceProxy(owner: self)
|
|
254
|
+
let delegateProxy = CollectionViewDelegateProxy(owner: self)
|
|
255
|
+
collectionDataSourceProxy = dataSourceProxy
|
|
256
|
+
collectionDelegateProxy = delegateProxy
|
|
257
|
+
collectionView.dataSource = dataSourceProxy
|
|
258
|
+
collectionView.delegate = delegateProxy
|
|
259
|
+
self.collectionView = collectionView
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private func makeView(type: String) throws -> (UIView, ReactTag) {
|
|
263
|
+
guard let createViewCallback else {
|
|
264
|
+
throw RuntimeError.error(withMessage: "Can only call makeView after setListCallbacks.")
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let rawViewTag = createViewCallback(type)
|
|
268
|
+
let viewTag = ReactTag(rawViewTag)
|
|
269
|
+
let resolvedView = try SurfaceHelper.getViewByTag(viewTag)
|
|
270
|
+
try recordFabricRestorePoint(for: resolvedView, viewTag: viewTag)
|
|
271
|
+
resolvedView.removeFromSuperview()
|
|
272
|
+
return (resolvedView, viewTag)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private func recordFabricRestorePoint(for resolvedView: UIView, viewTag: ReactTag) throws {
|
|
276
|
+
guard let originalSuperview = resolvedView.superview else {
|
|
277
|
+
throw RuntimeError.error(
|
|
278
|
+
withMessage: "Fabric view \(viewTag) must have a superview before being hosted by the list."
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
guard let originalIndex = originalSuperview.subviews.firstIndex(of: resolvedView) else {
|
|
283
|
+
throw RuntimeError.error(
|
|
284
|
+
withMessage: "Fabric view \(viewTag) must be present in its Fabric parent before being hosted by the list."
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let restorePoint = HostedFabricViewRestorePoint(
|
|
289
|
+
view: resolvedView,
|
|
290
|
+
originalSuperview: originalSuperview,
|
|
291
|
+
originalIndex: originalIndex
|
|
292
|
+
)
|
|
293
|
+
fabricRestorePointsByReactTag[viewTag] = restorePoint
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private func restoreFabricViewHierarchy() {
|
|
297
|
+
detachHostedCells()
|
|
298
|
+
|
|
299
|
+
let restorePoints = fabricRestorePointsByReactTag.values.sorted { firstPoint, secondPoint in
|
|
300
|
+
return firstPoint.originalIndex < secondPoint.originalIndex
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
for restorePoint in restorePoints {
|
|
304
|
+
restoreFabricView(restorePoint)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
fabricRestorePointsByReactTag.removeAll()
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private func detachHostedCells() {
|
|
311
|
+
let cells = hostedCells.allObjects
|
|
312
|
+
for cell in cells {
|
|
313
|
+
cell.detachHostedView()
|
|
314
|
+
}
|
|
315
|
+
hostedCells.removeAllObjects()
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private func restoreFabricView(_ restorePoint: HostedFabricViewRestorePoint) {
|
|
319
|
+
let hostedView = restorePoint.view
|
|
320
|
+
let originalSuperview = restorePoint.originalSuperview
|
|
321
|
+
|
|
322
|
+
if hostedView.superview === originalSuperview {
|
|
323
|
+
let currentIndex = originalSuperview.subviews.firstIndex(of: hostedView)
|
|
324
|
+
if currentIndex == restorePoint.originalIndex {
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
hostedView.removeFromSuperview()
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
precondition(hostedView.superview == nil, "Fabric view must be detached before restoring it.")
|
|
331
|
+
|
|
332
|
+
let subviewCount = originalSuperview.subviews.count
|
|
333
|
+
let insertionIndex: Int
|
|
334
|
+
if restorePoint.originalIndex <= subviewCount {
|
|
335
|
+
insertionIndex = restorePoint.originalIndex
|
|
336
|
+
} else {
|
|
337
|
+
insertionIndex = subviewCount
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
originalSuperview.insertSubview(hostedView, at: insertionIndex)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
private func measure(view: UIView) -> CGSize? {
|
|
344
|
+
// Fabric has already applied layout metrics after updateView returns.
|
|
345
|
+
// Asking UIKit Auto Layout to resolve this root can collapse it back to zero.
|
|
346
|
+
let measuredWidth = [view.bounds.width, view.frame.width]
|
|
347
|
+
.filter { $0.isFinite && $0 > 0 }
|
|
348
|
+
.max()
|
|
349
|
+
let measuredHeight = [view.bounds.height, view.frame.height]
|
|
350
|
+
.filter { $0.isFinite && $0 > 0 }
|
|
351
|
+
.max()
|
|
352
|
+
|
|
353
|
+
guard let measuredWidth, let measuredHeight else {
|
|
354
|
+
return nil
|
|
355
|
+
}
|
|
356
|
+
return CGSize(width: measuredWidth, height: measuredHeight)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private func resolvedContentSize(for item: NativeListItem) -> CGSize {
|
|
360
|
+
let measuredSize = measuredContentSizeByItemKey[item.key]
|
|
361
|
+
|
|
362
|
+
// UICollectionViewFlowLayout asks for item sizes before cells are created.
|
|
363
|
+
// If JS did not provide a dimension and this item has not been measured yet,
|
|
364
|
+
// use a viewport-based estimate for the first pass. Once cellForItemAt binds
|
|
365
|
+
// real content, we measure the Fabric-applied frame and future queries use it.
|
|
366
|
+
let width: CGFloat
|
|
367
|
+
if let itemWidth = item.width {
|
|
368
|
+
width = CGFloat(itemWidth)
|
|
369
|
+
} else if let measuredWidth = measuredSize?.width {
|
|
370
|
+
width = measuredWidth
|
|
371
|
+
} else {
|
|
372
|
+
let collectionViewWidth = collectionView?.bounds.width ?? 0
|
|
373
|
+
width = layoutProvider.estimatedContentWidth(
|
|
374
|
+
collectionViewWidth: collectionViewWidth,
|
|
375
|
+
viewWidth: view.bounds.width
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
let height: CGFloat
|
|
380
|
+
if let itemHeight = item.height {
|
|
381
|
+
height = CGFloat(itemHeight)
|
|
382
|
+
} else if let measuredHeight = measuredSize?.height {
|
|
383
|
+
height = measuredHeight
|
|
384
|
+
} else {
|
|
385
|
+
let collectionViewHeight = collectionView?.bounds.height ?? 0
|
|
386
|
+
height = layoutProvider.estimatedContentHeight(
|
|
387
|
+
collectionViewHeight: collectionViewHeight,
|
|
388
|
+
viewHeight: view.bounds.height
|
|
389
|
+
)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return CGSize(width: width, height: height)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
private func resolvedLayoutSize(for item: NativeListItem) -> CGSize {
|
|
396
|
+
let contentSize = resolvedContentSize(for: item)
|
|
397
|
+
return layoutProvider.layoutSize(contentSize: contentSize)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private func resolvedHostedContentSize(for item: NativeListItem) -> CGSize {
|
|
401
|
+
let layoutSize = resolvedLayoutSize(for: item)
|
|
402
|
+
let contentInset = layoutProvider.itemContentInset()
|
|
403
|
+
let width = layoutSize.width - contentInset.left - contentInset.right
|
|
404
|
+
let height = layoutSize.height - contentInset.top - contentInset.bottom
|
|
405
|
+
return CGSize(width: width, height: height)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
private func captureMeasuredContentSize(for item: NativeListItem, view: UIView) -> Bool {
|
|
409
|
+
guard let measuredSize = measure(view: view) else {
|
|
410
|
+
return false
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
let width = item.width.map { CGFloat($0) } ?? measuredSize.width
|
|
414
|
+
let height = item.height.map { CGFloat($0) } ?? measuredSize.height
|
|
415
|
+
let nextSize = CGSize(width: width, height: height)
|
|
416
|
+
let previousSize = measuredContentSizeByItemKey[item.key]
|
|
417
|
+
|
|
418
|
+
if let previousSize {
|
|
419
|
+
let widthDelta = abs(previousSize.width - nextSize.width)
|
|
420
|
+
let heightDelta = abs(previousSize.height - nextSize.height)
|
|
421
|
+
// Fabric can report tiny fractional differences for the same rendered row.
|
|
422
|
+
// Ignoring sub-point churn avoids full layout invalidations for visual no-ops.
|
|
423
|
+
if widthDelta < measuredSizeTolerance && heightDelta < measuredSizeTolerance {
|
|
424
|
+
return false
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
measuredContentSizeByItemKey[item.key] = nextSize
|
|
429
|
+
return true
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
private func needsMeasuredContentSize(for item: NativeListItem) -> Bool {
|
|
433
|
+
return item.width == nil || item.height == nil
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private func ensureReuseRegistered(for type: String) {
|
|
437
|
+
guard !registeredReuseIdentifiers.contains(type) else { return }
|
|
438
|
+
|
|
439
|
+
collectionView?.register(HostCell.self, forCellWithReuseIdentifier: type)
|
|
440
|
+
registeredReuseIdentifiers.insert(type)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private func reuseIdentifier(for item: NativeListItem) -> String {
|
|
444
|
+
return item.type
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private func installHostedContent(
|
|
448
|
+
in cell: HostCell,
|
|
449
|
+
item: NativeListItem,
|
|
450
|
+
contentSize: CGSize
|
|
451
|
+
) throws {
|
|
452
|
+
if cell.hostedContentView != nil {
|
|
453
|
+
guard cell.itemType == item.type else {
|
|
454
|
+
throw RuntimeError.error(
|
|
455
|
+
withMessage:
|
|
456
|
+
"CollectionView supplied cell for type '\(cell.itemType ?? "<nil>")' " +
|
|
457
|
+
"to item type '\(item.type)'."
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
cell.bind(itemKey: item.key)
|
|
461
|
+
cell.updateContentLayout(
|
|
462
|
+
contentSize: contentSize,
|
|
463
|
+
contentInset: layoutProvider.itemContentInset()
|
|
464
|
+
)
|
|
465
|
+
return
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
let result = try makeView(type: item.type)
|
|
469
|
+
cell.install(
|
|
470
|
+
view: result.0,
|
|
471
|
+
contentSize: contentSize,
|
|
472
|
+
contentInset: layoutProvider.itemContentInset(),
|
|
473
|
+
itemKey: item.key,
|
|
474
|
+
itemType: item.type
|
|
475
|
+
)
|
|
476
|
+
cell.reactTag = result.1
|
|
477
|
+
hostedCells.add(cell)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
private func retainMeasuredContent(in dataSource: HybridNativeListDataSource) {
|
|
481
|
+
let activeKeys = dataSource.itemsForPremeasurement().map { item in
|
|
482
|
+
item.key
|
|
483
|
+
}
|
|
484
|
+
let activeKeySet = Set(activeKeys)
|
|
485
|
+
measuredContentSizeByItemKey = measuredContentSizeByItemKey.filter { entry in
|
|
486
|
+
return activeKeySet.contains(entry.key)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private func updateVisibleCellContentLayouts() {
|
|
491
|
+
guard let collectionView, let dataSource else { return }
|
|
492
|
+
|
|
493
|
+
let visibleCells = collectionView.visibleCells
|
|
494
|
+
for visibleCell in visibleCells {
|
|
495
|
+
guard let cell = visibleCell as? HostCell else { continue }
|
|
496
|
+
guard let indexPath = collectionView.indexPath(for: cell) else { continue }
|
|
497
|
+
|
|
498
|
+
let item = dataSource.itemForCollectionViewQuery(at: indexPath.item)
|
|
499
|
+
let contentSize = resolvedHostedContentSize(for: item)
|
|
500
|
+
cell.updateContentLayout(
|
|
501
|
+
contentSize: contentSize,
|
|
502
|
+
contentInset: layoutProvider.itemContentInset()
|
|
503
|
+
)
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
private func runOnMain(_ block: @escaping () -> Void) {
|
|
508
|
+
if Thread.isMainThread {
|
|
509
|
+
block()
|
|
510
|
+
} else {
|
|
511
|
+
DispatchQueue.main.async(execute: block)
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private func scheduleLayoutInvalidation() {
|
|
516
|
+
if hasScheduledLayoutInvalidation {
|
|
517
|
+
return
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
hasScheduledLayoutInvalidation = true
|
|
521
|
+
|
|
522
|
+
// Perf optimization: Several visible cells can discover their real size in the same UI tick.
|
|
523
|
+
// Coalesce those updates so FlowLayout only recalculates once.
|
|
524
|
+
DispatchQueue.main.async { [weak self] in
|
|
525
|
+
guard let self else { return }
|
|
526
|
+
self.hasScheduledLayoutInvalidation = false
|
|
527
|
+
self.collectionView?.collectionViewLayout.invalidateLayout()
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
func numberOfSections(in collectionView: UICollectionView) -> Int {
|
|
532
|
+
return 1
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
func collectionView(
|
|
536
|
+
_ collectionView: UICollectionView,
|
|
537
|
+
numberOfItemsInSection section: Int
|
|
538
|
+
) -> Int {
|
|
539
|
+
guard let dataSource else { return 0 }
|
|
540
|
+
return Int((try? dataSource.getCount()) ?? 0)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
func layoutSizeForItem(at index: Int) -> CGSize {
|
|
544
|
+
guard let dataSource else { return .zero }
|
|
545
|
+
let item = dataSource.itemForCollectionViewQuery(at: index)
|
|
546
|
+
return resolvedLayoutSize(for: item)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
func collectionView(
|
|
550
|
+
_ collectionView: UICollectionView,
|
|
551
|
+
layout collectionViewLayout: UICollectionViewLayout,
|
|
552
|
+
sizeForItemAt indexPath: IndexPath
|
|
553
|
+
) -> CGSize {
|
|
554
|
+
guard let dataSource else {
|
|
555
|
+
return .zero
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
let item = dataSource.itemForCollectionViewQuery(at: indexPath.item)
|
|
559
|
+
return resolvedLayoutSize(for: item)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
func collectionView(
|
|
563
|
+
_ collectionView: UICollectionView,
|
|
564
|
+
cellForItemAt indexPath: IndexPath
|
|
565
|
+
) -> UICollectionViewCell {
|
|
566
|
+
guard let dataSource else {
|
|
567
|
+
return UICollectionViewCell()
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
let item = dataSource.itemForCollectionViewQuery(at: indexPath.item)
|
|
571
|
+
let reuseIdentifier = reuseIdentifier(for: item)
|
|
572
|
+
ensureReuseRegistered(for: reuseIdentifier)
|
|
573
|
+
|
|
574
|
+
let cell = collectionView.dequeueReusableCell(
|
|
575
|
+
withReuseIdentifier: reuseIdentifier,
|
|
576
|
+
for: indexPath
|
|
577
|
+
) as! HostCell
|
|
578
|
+
|
|
579
|
+
let contentSize = resolvedHostedContentSize(for: item)
|
|
580
|
+
|
|
581
|
+
do {
|
|
582
|
+
try installHostedContent(in: cell, item: item, contentSize: contentSize)
|
|
583
|
+
} catch {
|
|
584
|
+
print("Failed to create list item view: \(error)")
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if let reactTag = cell.reactTag {
|
|
588
|
+
let width = contentSize.width
|
|
589
|
+
let height: CGFloat?
|
|
590
|
+
if item.height != nil {
|
|
591
|
+
height = contentSize.height
|
|
592
|
+
} else {
|
|
593
|
+
height = nil
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
cell.prepareForMeasurement(width: width, height: height)
|
|
597
|
+
|
|
598
|
+
_ = updateViewCallback?(Double(reactTag), item, Double(indexPath.item))
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if let hostedView = cell.hostedContentView, needsMeasuredContentSize(for: item) {
|
|
602
|
+
let didMeasureNewSize = captureMeasuredContentSize(for: item, view: hostedView)
|
|
603
|
+
let measuredContentSize = resolvedHostedContentSize(for: item)
|
|
604
|
+
cell.updateContentLayout(
|
|
605
|
+
contentSize: measuredContentSize,
|
|
606
|
+
contentInset: layoutProvider.itemContentInset()
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
if didMeasureNewSize {
|
|
610
|
+
scheduleLayoutInvalidation()
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return cell
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
extension HybridUiListView: NativeListDataSourceObserver {
|
|
619
|
+
func dataSourceDidReload(
|
|
620
|
+
_ dataSource: HybridNativeListDataSource,
|
|
621
|
+
animated: Bool,
|
|
622
|
+
changeset: StagedChangeset<[DiffableListItem]>?
|
|
623
|
+
) {
|
|
624
|
+
runOnMain { [weak self] in
|
|
625
|
+
guard let self else { return }
|
|
626
|
+
configureCollectionViewIfNeeded()
|
|
627
|
+
retainMeasuredContent(in: dataSource)
|
|
628
|
+
|
|
629
|
+
guard animated, let collectionView, let changeset else {
|
|
630
|
+
collectionView?.reloadData()
|
|
631
|
+
return
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
collectionView.reload(using: changeset) { nextItems in
|
|
635
|
+
dataSource.replaceWrappedItemsFromCollectionView(nextItems)
|
|
636
|
+
collectionView.collectionViewLayout.invalidateLayout()
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
func dataSourceDidInsert(_ dataSource: HybridNativeListDataSource, index: Int) {
|
|
642
|
+
runOnMain { [weak self] in
|
|
643
|
+
guard let self else { return }
|
|
644
|
+
let item = dataSource.item(at: index)
|
|
645
|
+
let reuseIdentifier = reuseIdentifier(for: item)
|
|
646
|
+
ensureReuseRegistered(for: reuseIdentifier)
|
|
647
|
+
let indexPath = IndexPath(item: index, section: 0)
|
|
648
|
+
let indexPaths = [indexPath]
|
|
649
|
+
collectionView?.insertItems(at: indexPaths)
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
func dataSourceDidUpdate(
|
|
654
|
+
_ dataSource: HybridNativeListDataSource,
|
|
655
|
+
index: Int,
|
|
656
|
+
previousItem: NativeListItem
|
|
657
|
+
) {
|
|
658
|
+
runOnMain { [weak self] in
|
|
659
|
+
guard let self else { return }
|
|
660
|
+
let item = dataSource.item(at: index)
|
|
661
|
+
if previousItem.key != item.key {
|
|
662
|
+
measuredContentSizeByItemKey[previousItem.key] = nil
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
let reuseIdentifier = reuseIdentifier(for: item)
|
|
666
|
+
ensureReuseRegistered(for: reuseIdentifier)
|
|
667
|
+
let indexPath = IndexPath(item: index, section: 0)
|
|
668
|
+
let indexPaths = [indexPath]
|
|
669
|
+
collectionView?.reloadItems(at: indexPaths)
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
func dataSourceDidRemove(
|
|
674
|
+
_ dataSource: HybridNativeListDataSource,
|
|
675
|
+
index: Int,
|
|
676
|
+
removedItem: NativeListItem
|
|
677
|
+
) {
|
|
678
|
+
runOnMain { [weak self] in
|
|
679
|
+
guard let self else { return }
|
|
680
|
+
measuredContentSizeByItemKey[removedItem.key] = nil
|
|
681
|
+
let indexPath = IndexPath(item: index, section: 0)
|
|
682
|
+
let indexPaths = [indexPath]
|
|
683
|
+
collectionView?.deleteItems(at: indexPaths)
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
func dataSourceDidMove(_ dataSource: HybridNativeListDataSource, fromIndex: Int, toIndex: Int) {
|
|
688
|
+
runOnMain { [weak self] in
|
|
689
|
+
guard let self else { return }
|
|
690
|
+
let sourceIndexPath = IndexPath(item: fromIndex, section: 0)
|
|
691
|
+
let targetIndexPath = IndexPath(item: toIndex, section: 0)
|
|
692
|
+
collectionView?.moveItem(at: sourceIndexPath, to: targetIndexPath)
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|