@thelacanians/vue-native-cli 0.4.3 → 0.4.4
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/dist/cli.js +34 -17
- package/native/android/README.md +205 -0
- package/native/android/VueNativeCore/build.gradle.kts +100 -0
- package/native/android/VueNativeCore/consumer-rules.pro +12 -0
- package/native/android/VueNativeCore/proguard-rules.pro +33 -0
- package/native/android/VueNativeCore/src/main/AndroidManifest.xml +17 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/ErrorOverlayView.kt +94 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/HotReloadManager.kt +105 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/JSPolyfills.kt +652 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/JSRuntime.kt +207 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +417 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/ComponentRegistry.kt +76 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VActionSheetFactory.kt +78 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VActivityIndicatorFactory.kt +46 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VAlertDialogFactory.kt +84 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VButtonFactory.kt +73 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VCheckboxFactory.kt +93 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VDropdownFactory.kt +125 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VImageFactory.kt +75 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VInputFactory.kt +210 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VKeyboardAvoidingFactory.kt +31 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VListFactory.kt +183 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VModalFactory.kt +105 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VPickerFactory.kt +57 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VPressableFactory.kt +109 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VProgressBarFactory.kt +43 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VRadioFactory.kt +103 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VRefreshControlFactory.kt +73 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VRootFactory.kt +39 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSafeAreaFactory.kt +48 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VScrollViewFactory.kt +105 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSectionListFactory.kt +144 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSegmentedControlFactory.kt +77 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSliderFactory.kt +74 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VStatusBarFactory.kt +52 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSwitchFactory.kt +62 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VTextFactory.kt +53 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VVideoFactory.kt +191 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +48 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VWebViewFactory.kt +90 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/NativeComponentFactory.kt +40 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/VTextNodeView.kt +23 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/GestureHelper.kt +16 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/TouchableView.kt +105 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AnimationModule.kt +292 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AppStateModule.kt +41 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AsyncStorageModule.kt +59 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/AudioModule.kt +331 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BackgroundTaskModule.kt +166 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BiometryModule.kt +56 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/BluetoothModule.kt +302 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/CalendarModule.kt +198 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/CameraModule.kt +64 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ClipboardModule.kt +36 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ContactsModule.kt +288 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/DatabaseModule.kt +229 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/DeviceInfoModule.kt +39 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/FileSystemModule.kt +193 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeolocationModule.kt +68 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/HapticsModule.kt +61 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/HttpModule.kt +111 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/IAPModule.kt +302 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/KeyboardModule.kt +26 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/LinkingModule.kt +43 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModule.kt +27 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +92 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NetworkModule.kt +75 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NotificationsModule.kt +181 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/OTAModule.kt +255 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/PerformanceModule.kt +147 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/PermissionsModule.kt +126 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SecureStorageModule.kt +51 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SensorsModule.kt +134 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/ShareModule.kt +36 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/SocialAuthModule.kt +160 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/WebSocketModule.kt +155 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Styling/StyleEngine.kt +802 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Tags.kt +43 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/VueNativeActivity.kt +169 -0
- package/native/android/VueNativeCore/src/main/res/values/ids.xml +8 -0
- package/native/android/app/build.gradle.kts +45 -0
- package/native/android/app/proguard-rules.pro +5 -0
- package/native/android/app/src/main/AndroidManifest.xml +25 -0
- package/native/android/app/src/main/assets/.gitkeep +0 -0
- package/native/android/app/src/main/kotlin/com/vuenative/example/counter/MainActivity.kt +14 -0
- package/native/android/app/src/main/res/layout/activity_main.xml +6 -0
- package/native/android/app/src/main/res/values/strings.xml +3 -0
- package/native/android/app/src/main/res/values/themes.xml +9 -0
- package/native/android/app/src/main/res/xml/network_security_config.xml +8 -0
- package/native/android/build.gradle.kts +6 -0
- package/native/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/native/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/native/android/gradle.properties +4 -0
- package/native/android/gradlew +87 -0
- package/native/android/gradlew.bat +48 -0
- package/native/android/settings.gradle.kts +20 -0
- package/native/ios/VueNativeCore/Package.resolved +23 -0
- package/native/ios/VueNativeCore/Package.swift +32 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/CertificatePinning.swift +132 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/ErrorOverlayView.swift +92 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/HotReloadManager.swift +147 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/JSPolyfills.swift +711 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/JSRuntime.swift +421 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +891 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/VueNativeViewController.swift +88 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/ComponentRegistry.swift +193 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VActionSheetFactory.swift +91 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VActivityIndicatorFactory.swift +74 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VAlertDialogFactory.swift +150 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VButtonFactory.swift +93 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VCheckboxFactory.swift +114 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VDropdownFactory.swift +112 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VImageFactory.swift +172 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VInputFactory.swift +357 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VKeyboardAvoidingFactory.swift +99 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VListFactory.swift +250 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VModalFactory.swift +112 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VPickerFactory.swift +96 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VPressableFactory.swift +168 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VProgressBarFactory.swift +39 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VRadioFactory.swift +167 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VRefreshControlFactory.swift +153 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSafeAreaFactory.swift +56 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VScrollViewFactory.swift +240 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSectionListFactory.swift +248 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSegmentedControlFactory.swift +73 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSliderFactory.swift +63 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VStatusBarFactory.swift +50 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSwitchFactory.swift +108 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +290 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VVideoFactory.swift +246 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +157 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VWebViewFactory.swift +172 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/NativeComponentFactory.swift +53 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +107 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/TouchableView.swift +136 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/UIColor+Hex.swift +80 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AnimationModule.swift +291 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AppStateModule.swift +65 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AsyncStorageModule.swift +68 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AudioModule.swift +366 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/BackgroundTaskModule.swift +135 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/BiometryModule.swift +61 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/BluetoothModule.swift +387 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/CalendarModule.swift +161 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/CameraModule.swift +318 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/ClipboardModule.swift +33 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/ContactsModule.swift +173 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/DatabaseModule.swift +259 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/DeviceInfoModule.swift +34 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/FileSystemModule.swift +233 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeolocationModule.swift +147 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/HapticsModule.swift +50 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/IAPModule.swift +194 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/KeyboardModule.swift +31 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/LinkingModule.swift +42 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModule.swift +28 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +78 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NetworkModule.swift +62 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NotificationsModule.swift +215 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/OTAModule.swift +281 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/PerformanceModule.swift +138 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/PermissionsModule.swift +190 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/SecureStorageModule.swift +118 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/SensorsModule.swift +103 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/ShareModule.swift +49 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/SocialAuthModule.swift +240 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/WebSocketModule.swift +213 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Resources/vue-native-placeholder.js +8 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Styling/StyleEngine.swift +885 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSRuntimeTests.swift +362 -0
- package/package.json +3 -2
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import UIKit
|
|
3
|
+
import ObjectiveC
|
|
4
|
+
import FlexLayout
|
|
5
|
+
|
|
6
|
+
/// Factory for VRefreshControl — the pull-to-refresh indicator component.
|
|
7
|
+
///
|
|
8
|
+
/// Creates a lightweight UIView wrapper. When this view is inserted as a child
|
|
9
|
+
/// of a UIScrollView (via VScrollView or VList), the factory attaches a
|
|
10
|
+
/// UIRefreshControl to the parent scroll view's `.refreshControl` property.
|
|
11
|
+
final class VRefreshControlFactory: NativeComponentFactory {
|
|
12
|
+
|
|
13
|
+
// MARK: - Associated object keys
|
|
14
|
+
|
|
15
|
+
private static var refreshControlKey: UInt8 = 0
|
|
16
|
+
private static var refreshTargetKey: UInt8 = 1
|
|
17
|
+
private static var refreshHandlerKey: UInt8 = 2
|
|
18
|
+
|
|
19
|
+
// MARK: - NativeComponentFactory
|
|
20
|
+
|
|
21
|
+
func createView() -> UIView {
|
|
22
|
+
// The wrapper view is zero-sized and invisible.
|
|
23
|
+
// The actual UIRefreshControl is attached to the parent scroll view.
|
|
24
|
+
let wrapper = UIView()
|
|
25
|
+
wrapper.isHidden = true
|
|
26
|
+
wrapper.frame = .zero
|
|
27
|
+
|
|
28
|
+
// Create the UIRefreshControl and store it on the wrapper
|
|
29
|
+
let refreshControl = UIRefreshControl()
|
|
30
|
+
objc_setAssociatedObject(
|
|
31
|
+
wrapper,
|
|
32
|
+
&VRefreshControlFactory.refreshControlKey,
|
|
33
|
+
refreshControl,
|
|
34
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
return wrapper
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func updateProp(view: UIView, key: String, value: Any?) {
|
|
41
|
+
guard let refreshControl = objc_getAssociatedObject(
|
|
42
|
+
view,
|
|
43
|
+
&VRefreshControlFactory.refreshControlKey
|
|
44
|
+
) as? UIRefreshControl else { return }
|
|
45
|
+
|
|
46
|
+
switch key {
|
|
47
|
+
case "refreshing":
|
|
48
|
+
let refreshing: Bool
|
|
49
|
+
if let b = value as? Bool { refreshing = b }
|
|
50
|
+
else if let n = value as? NSNumber { refreshing = n.boolValue }
|
|
51
|
+
else if let n = value as? Int { refreshing = n != 0 }
|
|
52
|
+
else { refreshing = false }
|
|
53
|
+
|
|
54
|
+
if refreshing {
|
|
55
|
+
refreshControl.beginRefreshing()
|
|
56
|
+
} else {
|
|
57
|
+
refreshControl.endRefreshing()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
case "tintColor":
|
|
61
|
+
if let hex = value as? String {
|
|
62
|
+
refreshControl.tintColor = UIColor.fromHex(hex)
|
|
63
|
+
} else {
|
|
64
|
+
refreshControl.tintColor = nil
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
case "title":
|
|
68
|
+
if let text = value as? String {
|
|
69
|
+
refreshControl.attributedTitle = NSAttributedString(string: text)
|
|
70
|
+
} else {
|
|
71
|
+
refreshControl.attributedTitle = nil
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
default:
|
|
75
|
+
break
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
func addEventListener(view: UIView, event: String, handler: @escaping (Any?) -> Void) {
|
|
80
|
+
guard event == "refresh" else { return }
|
|
81
|
+
guard let refreshControl = objc_getAssociatedObject(
|
|
82
|
+
view,
|
|
83
|
+
&VRefreshControlFactory.refreshControlKey
|
|
84
|
+
) as? UIRefreshControl else { return }
|
|
85
|
+
|
|
86
|
+
// Store handler reference
|
|
87
|
+
objc_setAssociatedObject(
|
|
88
|
+
view,
|
|
89
|
+
&VRefreshControlFactory.refreshHandlerKey,
|
|
90
|
+
handler as AnyObject,
|
|
91
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
// Create ObjC-compatible target
|
|
95
|
+
let target = RefreshControlTarget(handler: handler)
|
|
96
|
+
objc_setAssociatedObject(
|
|
97
|
+
view,
|
|
98
|
+
&VRefreshControlFactory.refreshTargetKey,
|
|
99
|
+
target,
|
|
100
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
101
|
+
)
|
|
102
|
+
refreshControl.addTarget(target, action: #selector(RefreshControlTarget.handleRefresh), for: .valueChanged)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
func removeEventListener(view: UIView, event: String) {
|
|
106
|
+
guard event == "refresh" else { return }
|
|
107
|
+
guard let refreshControl = objc_getAssociatedObject(
|
|
108
|
+
view,
|
|
109
|
+
&VRefreshControlFactory.refreshControlKey
|
|
110
|
+
) as? UIRefreshControl else { return }
|
|
111
|
+
|
|
112
|
+
refreshControl.removeTarget(nil, action: nil, for: .valueChanged)
|
|
113
|
+
objc_setAssociatedObject(view, &VRefreshControlFactory.refreshTargetKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
114
|
+
objc_setAssociatedObject(view, &VRefreshControlFactory.refreshHandlerKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// MARK: - Child management
|
|
118
|
+
|
|
119
|
+
/// When inserted into a parent, attach the UIRefreshControl to the
|
|
120
|
+
/// nearest UIScrollView ancestor.
|
|
121
|
+
func insertChild(_ child: UIView, into parent: UIView, before anchor: UIView?) {
|
|
122
|
+
// Default: just add as subview (hidden, zero-frame)
|
|
123
|
+
if let anchor = anchor, let idx = parent.subviews.firstIndex(of: anchor) {
|
|
124
|
+
parent.insertSubview(child, at: idx)
|
|
125
|
+
} else {
|
|
126
|
+
parent.addSubview(child)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// MARK: - Static helpers
|
|
131
|
+
|
|
132
|
+
/// Retrieve the UIRefreshControl stored on a VRefreshControl wrapper view.
|
|
133
|
+
static func refreshControl(for view: UIView) -> UIRefreshControl? {
|
|
134
|
+
return objc_getAssociatedObject(view, &refreshControlKey) as? UIRefreshControl
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// MARK: - RefreshControlTarget
|
|
139
|
+
|
|
140
|
+
/// ObjC-compatible target for UIRefreshControl .valueChanged action.
|
|
141
|
+
private final class RefreshControlTarget: NSObject {
|
|
142
|
+
private let handler: (Any?) -> Void
|
|
143
|
+
|
|
144
|
+
init(handler: @escaping (Any?) -> Void) {
|
|
145
|
+
self.handler = handler
|
|
146
|
+
super.init()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@objc func handleRefresh() {
|
|
150
|
+
handler(nil)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
#endif
|
package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSafeAreaFactory.swift
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import UIKit
|
|
3
|
+
import FlexLayout
|
|
4
|
+
|
|
5
|
+
/// Factory for VSafeArea — a container that automatically applies safe area insets as padding.
|
|
6
|
+
/// Maps content insets to Yoga padding so children avoid the notch/home indicator.
|
|
7
|
+
final class VSafeAreaFactory: NativeComponentFactory {
|
|
8
|
+
|
|
9
|
+
func createView() -> UIView {
|
|
10
|
+
let view = SafeAreaView()
|
|
11
|
+
_ = view.flex
|
|
12
|
+
return view
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
func updateProp(view: UIView, key: String, value: Any?) {
|
|
16
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func addEventListener(view: UIView, event: String, handler: @escaping (Any?) -> Void) {}
|
|
20
|
+
func removeEventListener(view: UIView, event: String) {}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// UIView subclass that applies safeAreaInsets as Yoga padding on every layout pass.
|
|
24
|
+
final class SafeAreaView: UIView {
|
|
25
|
+
|
|
26
|
+
override func safeAreaInsetsDidChange() {
|
|
27
|
+
super.safeAreaInsetsDidChange()
|
|
28
|
+
applyInsets()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
override func didMoveToWindow() {
|
|
32
|
+
super.didMoveToWindow()
|
|
33
|
+
applyInsets()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private func applyInsets() {
|
|
37
|
+
let insets = safeAreaInsets
|
|
38
|
+
flex.paddingTop(insets.top)
|
|
39
|
+
flex.paddingBottom(insets.bottom)
|
|
40
|
+
flex.paddingLeft(insets.left)
|
|
41
|
+
flex.paddingRight(insets.right)
|
|
42
|
+
// Trigger layout recalculation
|
|
43
|
+
if let root = findFlexRoot() {
|
|
44
|
+
root.flex.layout(mode: .fitContainer)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private func findFlexRoot() -> UIView? {
|
|
49
|
+
var v: UIView? = self
|
|
50
|
+
while let parent = v?.superview {
|
|
51
|
+
v = parent
|
|
52
|
+
}
|
|
53
|
+
return v
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
#endif
|
package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VScrollViewFactory.swift
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import UIKit
|
|
3
|
+
import ObjectiveC
|
|
4
|
+
import FlexLayout
|
|
5
|
+
|
|
6
|
+
/// Factory for VScrollView — the scrollable container component.
|
|
7
|
+
///
|
|
8
|
+
/// Maps to UIScrollView on iOS. An inner `contentView` UIView is created as
|
|
9
|
+
/// a subview and managed separately from the scroll view's Yoga node. All
|
|
10
|
+
/// children reported by the bridge are inserted into the contentView, not
|
|
11
|
+
/// the scroll view itself.
|
|
12
|
+
///
|
|
13
|
+
/// After each main layout pass, NativeBridge calls `layoutContentView(for:)`
|
|
14
|
+
/// to compute the contentView's natural height (unconstrained) and update
|
|
15
|
+
/// the scroll view's contentSize.
|
|
16
|
+
final class VScrollViewFactory: NativeComponentFactory {
|
|
17
|
+
|
|
18
|
+
// MARK: - Associated object keys
|
|
19
|
+
|
|
20
|
+
static var contentViewKey: UInt8 = 0
|
|
21
|
+
static var delegateProxyKey: UInt8 = 1
|
|
22
|
+
static var onRefreshKey: UInt8 = 5
|
|
23
|
+
static var refreshTargetKey: UInt8 = 6
|
|
24
|
+
|
|
25
|
+
// MARK: - NativeComponentFactory
|
|
26
|
+
|
|
27
|
+
func createView() -> UIView {
|
|
28
|
+
let scrollView = UIScrollView()
|
|
29
|
+
scrollView.showsVerticalScrollIndicator = true
|
|
30
|
+
scrollView.showsHorizontalScrollIndicator = false
|
|
31
|
+
scrollView.alwaysBounceVertical = true
|
|
32
|
+
scrollView.clipsToBounds = true
|
|
33
|
+
_ = scrollView.flex
|
|
34
|
+
|
|
35
|
+
// Content view: children are added here so Yoga can measure
|
|
36
|
+
// their natural size without being constrained by the scroll view's bounds.
|
|
37
|
+
let contentView = UIView()
|
|
38
|
+
_ = contentView.flex
|
|
39
|
+
scrollView.addSubview(contentView)
|
|
40
|
+
|
|
41
|
+
objc_setAssociatedObject(
|
|
42
|
+
scrollView,
|
|
43
|
+
&VScrollViewFactory.contentViewKey,
|
|
44
|
+
contentView,
|
|
45
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
return scrollView
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func updateProp(view: UIView, key: String, value: Any?) {
|
|
52
|
+
guard let scrollView = view as? UIScrollView else {
|
|
53
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
switch key {
|
|
58
|
+
case "horizontal":
|
|
59
|
+
let horizontal: Bool
|
|
60
|
+
if let h = value as? Bool { horizontal = h }
|
|
61
|
+
else if let h = value as? Int { horizontal = h != 0 }
|
|
62
|
+
else { horizontal = false }
|
|
63
|
+
scrollView.alwaysBounceHorizontal = horizontal
|
|
64
|
+
scrollView.alwaysBounceVertical = !horizontal
|
|
65
|
+
|
|
66
|
+
case "showsVerticalScrollIndicator":
|
|
67
|
+
if let shows = value as? Bool { scrollView.showsVerticalScrollIndicator = shows }
|
|
68
|
+
|
|
69
|
+
case "showsHorizontalScrollIndicator":
|
|
70
|
+
if let shows = value as? Bool { scrollView.showsHorizontalScrollIndicator = shows }
|
|
71
|
+
|
|
72
|
+
case "scrollEnabled":
|
|
73
|
+
if let enabled = value as? Bool { scrollView.isScrollEnabled = enabled }
|
|
74
|
+
else if let enabled = value as? Int { scrollView.isScrollEnabled = enabled != 0 }
|
|
75
|
+
|
|
76
|
+
case "bounces":
|
|
77
|
+
if let bounces = value as? Bool { scrollView.bounces = bounces }
|
|
78
|
+
|
|
79
|
+
case "pagingEnabled":
|
|
80
|
+
if let paging = value as? Bool { scrollView.isPagingEnabled = paging }
|
|
81
|
+
|
|
82
|
+
case "refreshing":
|
|
83
|
+
let refreshing: Bool
|
|
84
|
+
if let b = value as? Bool { refreshing = b }
|
|
85
|
+
else if let n = value as? NSNumber { refreshing = n.boolValue }
|
|
86
|
+
else { refreshing = false }
|
|
87
|
+
|
|
88
|
+
if refreshing {
|
|
89
|
+
// Add UIRefreshControl if not already present
|
|
90
|
+
if scrollView.refreshControl == nil {
|
|
91
|
+
let control = UIRefreshControl()
|
|
92
|
+
scrollView.refreshControl = control
|
|
93
|
+
}
|
|
94
|
+
scrollView.refreshControl?.beginRefreshing()
|
|
95
|
+
} else {
|
|
96
|
+
scrollView.refreshControl?.endRefreshing()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
default:
|
|
100
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
func addEventListener(view: UIView, event: String, handler: @escaping (Any?) -> Void) {
|
|
105
|
+
guard let scrollView = view as? UIScrollView else { return }
|
|
106
|
+
switch event {
|
|
107
|
+
case "scroll":
|
|
108
|
+
let proxy = ScrollDelegateProxy(handler: handler)
|
|
109
|
+
objc_setAssociatedObject(
|
|
110
|
+
scrollView,
|
|
111
|
+
&VScrollViewFactory.delegateProxyKey,
|
|
112
|
+
proxy,
|
|
113
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
114
|
+
)
|
|
115
|
+
scrollView.delegate = proxy
|
|
116
|
+
case "refresh":
|
|
117
|
+
// Store the handler
|
|
118
|
+
objc_setAssociatedObject(
|
|
119
|
+
scrollView,
|
|
120
|
+
&VScrollViewFactory.onRefreshKey,
|
|
121
|
+
handler as AnyObject,
|
|
122
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
123
|
+
)
|
|
124
|
+
// Create UIRefreshControl if not already present
|
|
125
|
+
if scrollView.refreshControl == nil {
|
|
126
|
+
scrollView.refreshControl = UIRefreshControl()
|
|
127
|
+
}
|
|
128
|
+
guard let refreshControl = scrollView.refreshControl else { break }
|
|
129
|
+
// Create a target helper and store it
|
|
130
|
+
let target = RefreshTarget(handler: handler)
|
|
131
|
+
objc_setAssociatedObject(
|
|
132
|
+
scrollView,
|
|
133
|
+
&VScrollViewFactory.refreshTargetKey,
|
|
134
|
+
target,
|
|
135
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
136
|
+
)
|
|
137
|
+
refreshControl.addTarget(target, action: #selector(RefreshTarget.handleRefresh), for: .valueChanged)
|
|
138
|
+
|
|
139
|
+
default:
|
|
140
|
+
break
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
func removeEventListener(view: UIView, event: String) {
|
|
145
|
+
guard let scrollView = view as? UIScrollView, event == "scroll" else { return }
|
|
146
|
+
scrollView.delegate = nil
|
|
147
|
+
objc_setAssociatedObject(
|
|
148
|
+
scrollView,
|
|
149
|
+
&VScrollViewFactory.delegateProxyKey,
|
|
150
|
+
nil,
|
|
151
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// MARK: - Child management
|
|
156
|
+
|
|
157
|
+
func insertChild(_ child: UIView, into parent: UIView, before anchor: UIView?) {
|
|
158
|
+
if let anchor = anchor, let idx = parent.subviews.firstIndex(of: anchor) {
|
|
159
|
+
parent.insertSubview(child, at: idx)
|
|
160
|
+
} else {
|
|
161
|
+
parent.addSubview(child)
|
|
162
|
+
}
|
|
163
|
+
// Ensure content size is recalculated after child insertion
|
|
164
|
+
if let scrollView = parent.superview as? UIScrollView {
|
|
165
|
+
scrollView.setNeedsLayout()
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
func removeChild(_ child: UIView, from parent: UIView) {
|
|
170
|
+
child.removeFromSuperview()
|
|
171
|
+
// Ensure content size is recalculated after child removal
|
|
172
|
+
if let scrollView = parent.superview as? UIScrollView {
|
|
173
|
+
scrollView.setNeedsLayout()
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// MARK: - Static helpers
|
|
178
|
+
|
|
179
|
+
/// Retrieve the inner content UIView for a scroll view.
|
|
180
|
+
/// NativeBridge calls this to redirect child insertions.
|
|
181
|
+
static func contentView(for scrollView: UIScrollView) -> UIView? {
|
|
182
|
+
return objc_getAssociatedObject(scrollView, &contentViewKey) as? UIView
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// Recompute the content view's layout and update the scroll view's contentSize.
|
|
186
|
+
/// Call this after the main Yoga layout pass, once the scroll view's frame is set.
|
|
187
|
+
@MainActor
|
|
188
|
+
static func layoutContentView(for scrollView: UIScrollView) {
|
|
189
|
+
guard let contentView = contentView(for: scrollView) else { return }
|
|
190
|
+
|
|
191
|
+
let scrollWidth = scrollView.bounds.width
|
|
192
|
+
guard scrollWidth > 0 else { return }
|
|
193
|
+
|
|
194
|
+
// Pin the content view's origin and width
|
|
195
|
+
contentView.frame = CGRect(x: 0, y: 0, width: scrollWidth, height: 0)
|
|
196
|
+
|
|
197
|
+
// Compute natural height — adjustHeight mode grows height to fit children
|
|
198
|
+
contentView.flex.layout(mode: .adjustHeight)
|
|
199
|
+
|
|
200
|
+
// Update the scroll view's scrollable content size
|
|
201
|
+
scrollView.contentSize = contentView.frame.size
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// MARK: - RefreshTarget
|
|
206
|
+
|
|
207
|
+
/// ObjC-compatible target for UIRefreshControl value-changed actions.
|
|
208
|
+
private final class RefreshTarget: NSObject {
|
|
209
|
+
private let handler: (Any?) -> Void
|
|
210
|
+
|
|
211
|
+
init(handler: @escaping (Any?) -> Void) {
|
|
212
|
+
self.handler = handler
|
|
213
|
+
super.init()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@objc func handleRefresh() {
|
|
217
|
+
handler(nil)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// MARK: - ScrollDelegateProxy
|
|
222
|
+
|
|
223
|
+
/// UIScrollViewDelegate proxy that forwards scroll events to a JS handler.
|
|
224
|
+
private final class ScrollDelegateProxy: NSObject, UIScrollViewDelegate {
|
|
225
|
+
private let handler: (Any?) -> Void
|
|
226
|
+
|
|
227
|
+
init(handler: @escaping (Any?) -> Void) {
|
|
228
|
+
self.handler = handler
|
|
229
|
+
super.init()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
|
233
|
+
let payload: [String: Any] = [
|
|
234
|
+
"x": scrollView.contentOffset.x,
|
|
235
|
+
"y": scrollView.contentOffset.y
|
|
236
|
+
]
|
|
237
|
+
handler(payload)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
#endif
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import UIKit
|
|
3
|
+
import FlexLayout
|
|
4
|
+
|
|
5
|
+
// MARK: - VSectionListFactory
|
|
6
|
+
|
|
7
|
+
/// Factory for VSectionList — a sectioned list backed by UITableView with section support.
|
|
8
|
+
/// Children with `__sectionHeader` prop are treated as section headers.
|
|
9
|
+
/// All other children are treated as regular items, grouped under the preceding header.
|
|
10
|
+
final class VSectionListFactory: NativeComponentFactory {
|
|
11
|
+
|
|
12
|
+
func createView() -> UIView {
|
|
13
|
+
let container = VSectionListContainerView()
|
|
14
|
+
_ = container.flex
|
|
15
|
+
return container
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func updateProp(view: UIView, key: String, value: Any?) {
|
|
19
|
+
guard let container = view as? VSectionListContainerView else {
|
|
20
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
switch key {
|
|
24
|
+
case "estimatedItemHeight":
|
|
25
|
+
container.estimatedItemHeight = CGFloat(value as? Double ?? 44)
|
|
26
|
+
case "stickySectionHeaders":
|
|
27
|
+
// plain style = sticky headers (default), grouped = non-sticky
|
|
28
|
+
// This is set at creation time; we store the preference for reference
|
|
29
|
+
container.stickySectionHeaders = value as? Bool ?? true
|
|
30
|
+
case "showsScrollIndicator":
|
|
31
|
+
container.tableView.showsVerticalScrollIndicator = value as? Bool ?? true
|
|
32
|
+
case "bounces":
|
|
33
|
+
container.tableView.bounces = value as? Bool ?? true
|
|
34
|
+
default:
|
|
35
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func addEventListener(view: UIView, event: String, handler: @escaping (Any?) -> Void) {
|
|
40
|
+
guard let container = view as? VSectionListContainerView else { return }
|
|
41
|
+
switch event {
|
|
42
|
+
case "scroll":
|
|
43
|
+
container.onScroll = handler
|
|
44
|
+
case "endReached":
|
|
45
|
+
container.onEndReached = handler
|
|
46
|
+
default:
|
|
47
|
+
break
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func removeEventListener(view: UIView, event: String) {
|
|
52
|
+
guard let container = view as? VSectionListContainerView else { return }
|
|
53
|
+
switch event {
|
|
54
|
+
case "scroll": container.onScroll = nil
|
|
55
|
+
case "endReached": container.onEndReached = nil
|
|
56
|
+
default: break
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// MARK: - Custom child management
|
|
61
|
+
|
|
62
|
+
func insertChild(_ child: UIView, into parent: UIView, before anchor: UIView?) {
|
|
63
|
+
guard let container = parent as? VSectionListContainerView else {
|
|
64
|
+
if let anchor = anchor, let idx = parent.subviews.firstIndex(of: anchor) {
|
|
65
|
+
parent.insertSubview(child, at: idx)
|
|
66
|
+
} else {
|
|
67
|
+
parent.addSubview(child)
|
|
68
|
+
}
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
container.allChildren.append(child)
|
|
72
|
+
container.rebuildSections()
|
|
73
|
+
container.tableView.reloadData()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
func removeChild(_ child: UIView, from parent: UIView) {
|
|
77
|
+
guard let container = parent as? VSectionListContainerView else {
|
|
78
|
+
child.removeFromSuperview()
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
container.allChildren.removeAll { $0 === child }
|
|
82
|
+
child.removeFromSuperview()
|
|
83
|
+
container.rebuildSections()
|
|
84
|
+
container.tableView.reloadData()
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// MARK: - SectionData
|
|
89
|
+
|
|
90
|
+
/// Represents a section: an optional header view and an array of item views.
|
|
91
|
+
private struct SectionData {
|
|
92
|
+
var headerView: UIView?
|
|
93
|
+
var itemViews: [UIView]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// MARK: - VSectionListContainerView
|
|
97
|
+
|
|
98
|
+
/// Container view that hosts a UITableView with section support.
|
|
99
|
+
final class VSectionListContainerView: UIView {
|
|
100
|
+
|
|
101
|
+
let tableView: UITableView
|
|
102
|
+
var allChildren: [UIView] = []
|
|
103
|
+
var estimatedItemHeight: CGFloat = 44
|
|
104
|
+
var stickySectionHeaders: Bool = true
|
|
105
|
+
var onScroll: ((Any?) -> Void)?
|
|
106
|
+
var onEndReached: ((Any?) -> Void)?
|
|
107
|
+
fileprivate var firedEndReached = false
|
|
108
|
+
fileprivate var sections: [SectionData] = []
|
|
109
|
+
private lazy var internalDelegate = VSectionListInternalDelegate(container: self)
|
|
110
|
+
|
|
111
|
+
init() {
|
|
112
|
+
tableView = UITableView(frame: .zero, style: .plain)
|
|
113
|
+
super.init(frame: .zero)
|
|
114
|
+
tableView.separatorStyle = .none
|
|
115
|
+
tableView.tableFooterView = UIView()
|
|
116
|
+
tableView.dataSource = internalDelegate
|
|
117
|
+
tableView.delegate = internalDelegate
|
|
118
|
+
tableView.register(VListCell.self, forCellReuseIdentifier: "VListCell")
|
|
119
|
+
super.addSubview(tableView)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
required init?(coder: NSCoder) { fatalError() }
|
|
123
|
+
|
|
124
|
+
/// Rebuild section data from the flat allChildren array.
|
|
125
|
+
/// Children that had the `__sectionHeader: true` prop set via the bridge start a new section.
|
|
126
|
+
func rebuildSections() {
|
|
127
|
+
sections = []
|
|
128
|
+
var currentSection = SectionData(headerView: nil, itemViews: [])
|
|
129
|
+
|
|
130
|
+
for child in allChildren {
|
|
131
|
+
let isSectionHeader = StyleEngine.getInternalProp("__sectionHeader", from: child) as? Bool ?? false
|
|
132
|
+
if isSectionHeader {
|
|
133
|
+
// Finalize previous section if it has items or a header
|
|
134
|
+
if currentSection.headerView != nil || !currentSection.itemViews.isEmpty {
|
|
135
|
+
sections.append(currentSection)
|
|
136
|
+
}
|
|
137
|
+
currentSection = SectionData(headerView: child, itemViews: [])
|
|
138
|
+
} else {
|
|
139
|
+
currentSection.itemViews.append(child)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Finalize last section
|
|
143
|
+
if currentSection.headerView != nil || !currentSection.itemViews.isEmpty {
|
|
144
|
+
sections.append(currentSection)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
override func layoutSubviews() {
|
|
149
|
+
super.layoutSubviews()
|
|
150
|
+
tableView.frame = bounds
|
|
151
|
+
|
|
152
|
+
let width = bounds.width
|
|
153
|
+
guard width > 0 else { return }
|
|
154
|
+
|
|
155
|
+
// Lay out all child views for height calculation
|
|
156
|
+
for child in allChildren {
|
|
157
|
+
if abs(child.frame.size.width - width) > 0.5 {
|
|
158
|
+
child.frame.size.width = width
|
|
159
|
+
child.flex.layout(mode: .adjustHeight)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
tableView.reloadData()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// MARK: - VSectionListInternalDelegate
|
|
167
|
+
|
|
168
|
+
private final class VSectionListInternalDelegate: NSObject,
|
|
169
|
+
UITableViewDataSource, UITableViewDelegate
|
|
170
|
+
{
|
|
171
|
+
private weak var container: VSectionListContainerView?
|
|
172
|
+
|
|
173
|
+
init(container: VSectionListContainerView) {
|
|
174
|
+
self.container = container
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
func numberOfSections(in tableView: UITableView) -> Int {
|
|
178
|
+
container?.sections.count ?? 0
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
182
|
+
guard let container = container, section < container.sections.count else { return 0 }
|
|
183
|
+
return container.sections[section].itemViews.count
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
func tableView(_ tableView: UITableView,
|
|
187
|
+
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
188
|
+
let cell = tableView.dequeueReusableCell(
|
|
189
|
+
withIdentifier: "VListCell", for: indexPath) as! VListCell
|
|
190
|
+
guard let container = container,
|
|
191
|
+
indexPath.section < container.sections.count,
|
|
192
|
+
indexPath.row < container.sections[indexPath.section].itemViews.count else { return cell }
|
|
193
|
+
cell.setItemView(container.sections[indexPath.section].itemViews[indexPath.row])
|
|
194
|
+
return cell
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
func tableView(_ tableView: UITableView,
|
|
198
|
+
viewForHeaderInSection section: Int) -> UIView? {
|
|
199
|
+
guard let container = container, section < container.sections.count else { return nil }
|
|
200
|
+
return container.sections[section].headerView
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
func tableView(_ tableView: UITableView,
|
|
204
|
+
heightForHeaderInSection section: Int) -> CGFloat {
|
|
205
|
+
guard let container = container, section < container.sections.count,
|
|
206
|
+
let header = container.sections[section].headerView else { return 0 }
|
|
207
|
+
let h = header.frame.size.height
|
|
208
|
+
return h > 1 ? h : container.estimatedItemHeight
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
func tableView(_ tableView: UITableView,
|
|
212
|
+
estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
|
|
213
|
+
container?.estimatedItemHeight ?? 44
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
func tableView(_ tableView: UITableView,
|
|
217
|
+
heightForRowAt indexPath: IndexPath) -> CGFloat {
|
|
218
|
+
guard let container = container,
|
|
219
|
+
indexPath.section < container.sections.count,
|
|
220
|
+
indexPath.row < container.sections[indexPath.section].itemViews.count else {
|
|
221
|
+
return container?.estimatedItemHeight ?? 44
|
|
222
|
+
}
|
|
223
|
+
let h = container.sections[indexPath.section].itemViews[indexPath.row].frame.size.height
|
|
224
|
+
return h > 1 ? h : (container?.estimatedItemHeight ?? 44)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
|
228
|
+
guard let container = container else { return }
|
|
229
|
+
let offset = scrollView.contentOffset
|
|
230
|
+
container.onScroll?(["x": Double(offset.x), "y": Double(offset.y)])
|
|
231
|
+
|
|
232
|
+
let contentH = scrollView.contentSize.height
|
|
233
|
+
let frameH = scrollView.frame.size.height
|
|
234
|
+
guard contentH > frameH else { return }
|
|
235
|
+
let distanceFromBottom = contentH - frameH - offset.y
|
|
236
|
+
let threshold = frameH * 0.2
|
|
237
|
+
|
|
238
|
+
if distanceFromBottom < threshold && !container.firedEndReached {
|
|
239
|
+
container.firedEndReached = true
|
|
240
|
+
container.onEndReached?(nil)
|
|
241
|
+
} else if distanceFromBottom >= threshold {
|
|
242
|
+
container.firedEndReached = false
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Reuses VListCell from VListFactory (same module)
|
|
248
|
+
#endif
|