@thelacanians/vue-native-cli 0.4.3 → 0.4.5
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 +102 -32
- package/native/android/README.md +205 -0
- package/native/android/VueNativeCore/build.gradle.kts +113 -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 +163 -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 +136 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/ErrorOverlayView.swift +89 -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 +906 -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 +90 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VActivityIndicatorFactory.swift +74 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VAlertDialogFactory.swift +149 -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 +109 -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 +250 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +157 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VWebViewFactory.swift +174 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/NativeComponentFactory.swift +53 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/Extensions.swift +31 -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 +283 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AppStateModule.swift +59 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AsyncStorageModule.swift +68 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/AudioModule.swift +371 -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 +379 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/CalendarModule.swift +154 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/CameraModule.swift +315 -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 +141 -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 +48 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/SocialAuthModule.swift +256 -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
package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VModalFactory.swift
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import UIKit
|
|
3
|
+
import FlexLayout
|
|
4
|
+
|
|
5
|
+
/// Factory for VModal — a window-level overlay component.
|
|
6
|
+
/// The VModal view itself is a zero-size placeholder in the native view tree.
|
|
7
|
+
/// Its children are rendered in a full-screen overlay view added to the key window.
|
|
8
|
+
final class VModalFactory: NativeComponentFactory {
|
|
9
|
+
|
|
10
|
+
private static var overlayKey: UInt8 = 0
|
|
11
|
+
private static var visibleKey: UInt8 = 1
|
|
12
|
+
|
|
13
|
+
func createView() -> UIView {
|
|
14
|
+
// Zero-size placeholder in the tree
|
|
15
|
+
let placeholder = UIView()
|
|
16
|
+
placeholder.isHidden = true
|
|
17
|
+
_ = placeholder.flex.width(0).height(0)
|
|
18
|
+
return placeholder
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func updateProp(view: UIView, key: String, value: Any?) {
|
|
22
|
+
switch key {
|
|
23
|
+
case "visible":
|
|
24
|
+
let visible = (value as? Bool) ?? (value as? NSNumber)?.boolValue ?? false
|
|
25
|
+
updateVisibility(view: view, visible: visible)
|
|
26
|
+
case "onDismiss":
|
|
27
|
+
break // handled via event
|
|
28
|
+
default:
|
|
29
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private func updateVisibility(view: UIView, visible: Bool) {
|
|
34
|
+
if visible {
|
|
35
|
+
showOverlay(for: view)
|
|
36
|
+
} else {
|
|
37
|
+
hideOverlay(for: view)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private func showOverlay(for placeholder: UIView) {
|
|
42
|
+
guard let window = UIApplication.shared.vn_keyWindow else { return }
|
|
43
|
+
|
|
44
|
+
// Get or create overlay
|
|
45
|
+
let overlay: UIView
|
|
46
|
+
if let existing = objc_getAssociatedObject(placeholder, &VModalFactory.overlayKey) as? UIView {
|
|
47
|
+
overlay = existing
|
|
48
|
+
} else {
|
|
49
|
+
overlay = UIView()
|
|
50
|
+
overlay.backgroundColor = UIColor(white: 0, alpha: 0.5)
|
|
51
|
+
objc_setAssociatedObject(
|
|
52
|
+
placeholder,
|
|
53
|
+
&VModalFactory.overlayKey,
|
|
54
|
+
overlay,
|
|
55
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
overlay.frame = window.bounds
|
|
60
|
+
window.addSubview(overlay)
|
|
61
|
+
|
|
62
|
+
// Run Yoga on overlay children
|
|
63
|
+
overlay.flex.layout(mode: .fitContainer)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private func hideOverlay(for placeholder: UIView) {
|
|
67
|
+
if let overlay = objc_getAssociatedObject(placeholder, &VModalFactory.overlayKey) as? UIView {
|
|
68
|
+
overlay.removeFromSuperview()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func addEventListener(view: UIView, event: String, handler: @escaping (Any?) -> Void) {
|
|
73
|
+
// dismiss event from tapping backdrop — no-op for now
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
func removeEventListener(view: UIView, event: String) {}
|
|
77
|
+
|
|
78
|
+
// Custom child management: route children to the overlay view
|
|
79
|
+
func insertChild(_ child: UIView, into parent: UIView, before anchor: UIView?) {
|
|
80
|
+
let overlay: UIView
|
|
81
|
+
if let existing = objc_getAssociatedObject(parent, &VModalFactory.overlayKey) as? UIView {
|
|
82
|
+
overlay = existing
|
|
83
|
+
} else {
|
|
84
|
+
// Create overlay early so children have somewhere to go
|
|
85
|
+
let newOverlay = UIView()
|
|
86
|
+
newOverlay.backgroundColor = UIColor(white: 0, alpha: 0.5)
|
|
87
|
+
_ = newOverlay.flex.grow(1)
|
|
88
|
+
objc_setAssociatedObject(
|
|
89
|
+
parent,
|
|
90
|
+
&VModalFactory.overlayKey,
|
|
91
|
+
newOverlay,
|
|
92
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
93
|
+
)
|
|
94
|
+
overlay = newOverlay
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if let anchor = anchor, let idx = overlay.subviews.firstIndex(of: anchor) {
|
|
98
|
+
overlay.insertSubview(child, at: idx)
|
|
99
|
+
} else {
|
|
100
|
+
overlay.addSubview(child)
|
|
101
|
+
}
|
|
102
|
+
_ = child.flex
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
func removeChild(_ child: UIView, from parent: UIView) {
|
|
106
|
+
child.removeFromSuperview()
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
#endif
|
package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VPickerFactory.swift
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import UIKit
|
|
3
|
+
import FlexLayout
|
|
4
|
+
import ObjectiveC
|
|
5
|
+
|
|
6
|
+
private var pickerOnChangeKey: UInt8 = 0
|
|
7
|
+
private var pickerItemsKey: UInt8 = 1
|
|
8
|
+
private var pickerDelegateKey: UInt8 = 2
|
|
9
|
+
|
|
10
|
+
/// Factory for VPicker.
|
|
11
|
+
/// Modes: "selector" (UIPickerView), "date", "time", "datetime" (UIDatePicker)
|
|
12
|
+
final class VPickerFactory: NativeComponentFactory {
|
|
13
|
+
|
|
14
|
+
func createView() -> UIView {
|
|
15
|
+
// Default: UIDatePicker (most common on mobile)
|
|
16
|
+
let picker = UIDatePicker()
|
|
17
|
+
picker.datePickerMode = .date
|
|
18
|
+
if #available(iOS 14.0, *) {
|
|
19
|
+
picker.preferredDatePickerStyle = .compact
|
|
20
|
+
}
|
|
21
|
+
_ = picker.flex
|
|
22
|
+
return picker
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func updateProp(view: UIView, key: String, value: Any?) {
|
|
26
|
+
switch key {
|
|
27
|
+
case "mode":
|
|
28
|
+
guard let modeStr = value as? String else { return }
|
|
29
|
+
// If we have a UIDatePicker, set its mode
|
|
30
|
+
if let datePicker = view as? UIDatePicker {
|
|
31
|
+
switch modeStr {
|
|
32
|
+
case "date": datePicker.datePickerMode = .date
|
|
33
|
+
case "time": datePicker.datePickerMode = .time
|
|
34
|
+
case "datetime": datePicker.datePickerMode = .dateAndTime
|
|
35
|
+
default: break
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
case "value":
|
|
39
|
+
if let datePicker = view as? UIDatePicker {
|
|
40
|
+
if let ms = (value as? Double) ?? (value as? NSNumber)?.doubleValue {
|
|
41
|
+
datePicker.date = Date(timeIntervalSince1970: ms / 1000.0)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
case "minimumDate":
|
|
45
|
+
if let datePicker = view as? UIDatePicker,
|
|
46
|
+
let ms = (value as? Double) ?? (value as? NSNumber)?.doubleValue {
|
|
47
|
+
datePicker.minimumDate = Date(timeIntervalSince1970: ms / 1000.0)
|
|
48
|
+
}
|
|
49
|
+
case "maximumDate":
|
|
50
|
+
if let datePicker = view as? UIDatePicker,
|
|
51
|
+
let ms = (value as? Double) ?? (value as? NSNumber)?.doubleValue {
|
|
52
|
+
datePicker.maximumDate = Date(timeIntervalSince1970: ms / 1000.0)
|
|
53
|
+
}
|
|
54
|
+
case "minuteInterval":
|
|
55
|
+
if let datePicker = view as? UIDatePicker,
|
|
56
|
+
let interval = (value as? Int) ?? (value as? NSNumber)?.intValue {
|
|
57
|
+
datePicker.minuteInterval = interval
|
|
58
|
+
}
|
|
59
|
+
case "items":
|
|
60
|
+
// UIPickerView items — not using UIPickerView in this simplified version
|
|
61
|
+
objc_setAssociatedObject(view, &pickerItemsKey, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
62
|
+
default:
|
|
63
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func addEventListener(view: UIView, event: String, handler: @escaping (Any?) -> Void) {
|
|
68
|
+
if event == "change" {
|
|
69
|
+
objc_setAssociatedObject(view, &pickerOnChangeKey, handler as AnyObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
70
|
+
if let datePicker = view as? UIDatePicker {
|
|
71
|
+
let target = PickerChangeTarget(view: view)
|
|
72
|
+
objc_setAssociatedObject(view, &pickerDelegateKey, target, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
73
|
+
datePicker.addTarget(target, action: #selector(PickerChangeTarget.handleChange(_:)), for: .valueChanged)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
func removeEventListener(view: UIView, event: String) {
|
|
79
|
+
if event == "change" {
|
|
80
|
+
objc_setAssociatedObject(view, &pickerOnChangeKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private final class PickerChangeTarget: NSObject {
|
|
86
|
+
private weak var view: UIView?
|
|
87
|
+
init(view: UIView) { self.view = view }
|
|
88
|
+
|
|
89
|
+
@objc func handleChange(_ picker: UIDatePicker) {
|
|
90
|
+
guard let view = view else { return }
|
|
91
|
+
if let handler = objc_getAssociatedObject(view, &pickerOnChangeKey) as? ((Any?) -> Void) {
|
|
92
|
+
handler(["value": picker.date.timeIntervalSince1970 * 1000.0])
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
#endif
|
package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VPressableFactory.swift
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import UIKit
|
|
3
|
+
import ObjectiveC
|
|
4
|
+
import FlexLayout
|
|
5
|
+
|
|
6
|
+
/// Factory for VPressable — a generic pressable container component.
|
|
7
|
+
///
|
|
8
|
+
/// Like VButton but without built-in text/label support. Provides press,
|
|
9
|
+
/// long press, pressIn, and pressOut events. Uses PressableView, a subclass
|
|
10
|
+
/// of TouchableView that adds pressIn/pressOut callbacks.
|
|
11
|
+
final class VPressableFactory: NativeComponentFactory {
|
|
12
|
+
|
|
13
|
+
// MARK: - Associated object keys for event handlers
|
|
14
|
+
|
|
15
|
+
private static var pressHandlerKey: UInt8 = 0
|
|
16
|
+
private static var longPressHandlerKey: UInt8 = 1
|
|
17
|
+
private static var pressInHandlerKey: UInt8 = 2
|
|
18
|
+
private static var pressOutHandlerKey: UInt8 = 3
|
|
19
|
+
|
|
20
|
+
// MARK: - NativeComponentFactory
|
|
21
|
+
|
|
22
|
+
func createView() -> UIView {
|
|
23
|
+
let pressable = PressableView()
|
|
24
|
+
// Accessing .flex automatically enables Yoga layout
|
|
25
|
+
_ = pressable.flex
|
|
26
|
+
return pressable
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func updateProp(view: UIView, key: String, value: Any?) {
|
|
30
|
+
guard let pressable = view as? PressableView else {
|
|
31
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
switch key {
|
|
36
|
+
case "disabled":
|
|
37
|
+
if let disabled = value as? Bool {
|
|
38
|
+
pressable.isDisabled = disabled
|
|
39
|
+
} else if let disabled = value as? Int {
|
|
40
|
+
pressable.isDisabled = disabled != 0
|
|
41
|
+
} else {
|
|
42
|
+
pressable.isDisabled = false
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
case "activeOpacity":
|
|
46
|
+
if let opacity = value as? Double {
|
|
47
|
+
pressable.activeOpacity = CGFloat(opacity)
|
|
48
|
+
} else if let opacity = value as? Int {
|
|
49
|
+
pressable.activeOpacity = CGFloat(opacity)
|
|
50
|
+
} else {
|
|
51
|
+
pressable.activeOpacity = 0.7
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
default:
|
|
55
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func addEventListener(view: UIView, event: String, handler: @escaping (Any?) -> Void) {
|
|
60
|
+
guard let pressable = view as? PressableView else { return }
|
|
61
|
+
|
|
62
|
+
switch event {
|
|
63
|
+
case "press":
|
|
64
|
+
pressable.onPress = {
|
|
65
|
+
handler(nil)
|
|
66
|
+
}
|
|
67
|
+
objc_setAssociatedObject(
|
|
68
|
+
view,
|
|
69
|
+
&VPressableFactory.pressHandlerKey,
|
|
70
|
+
handler as AnyObject,
|
|
71
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
case "longpress":
|
|
75
|
+
pressable.onLongPress = {
|
|
76
|
+
handler(nil)
|
|
77
|
+
}
|
|
78
|
+
objc_setAssociatedObject(
|
|
79
|
+
view,
|
|
80
|
+
&VPressableFactory.longPressHandlerKey,
|
|
81
|
+
handler as AnyObject,
|
|
82
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
case "pressIn", "pressin":
|
|
86
|
+
pressable.onPressIn = {
|
|
87
|
+
handler(nil)
|
|
88
|
+
}
|
|
89
|
+
objc_setAssociatedObject(
|
|
90
|
+
view,
|
|
91
|
+
&VPressableFactory.pressInHandlerKey,
|
|
92
|
+
handler as AnyObject,
|
|
93
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
case "pressOut", "pressout":
|
|
97
|
+
pressable.onPressOut = {
|
|
98
|
+
handler(nil)
|
|
99
|
+
}
|
|
100
|
+
objc_setAssociatedObject(
|
|
101
|
+
view,
|
|
102
|
+
&VPressableFactory.pressOutHandlerKey,
|
|
103
|
+
handler as AnyObject,
|
|
104
|
+
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
default:
|
|
108
|
+
break
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func removeEventListener(view: UIView, event: String) {
|
|
113
|
+
guard let pressable = view as? PressableView else { return }
|
|
114
|
+
|
|
115
|
+
switch event {
|
|
116
|
+
case "press":
|
|
117
|
+
pressable.onPress = nil
|
|
118
|
+
objc_setAssociatedObject(view, &VPressableFactory.pressHandlerKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
119
|
+
|
|
120
|
+
case "longpress":
|
|
121
|
+
pressable.onLongPress = nil
|
|
122
|
+
objc_setAssociatedObject(view, &VPressableFactory.longPressHandlerKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
123
|
+
|
|
124
|
+
case "pressIn", "pressin":
|
|
125
|
+
pressable.onPressIn = nil
|
|
126
|
+
objc_setAssociatedObject(view, &VPressableFactory.pressInHandlerKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
127
|
+
|
|
128
|
+
case "pressOut", "pressout":
|
|
129
|
+
pressable.onPressOut = nil
|
|
130
|
+
objc_setAssociatedObject(view, &VPressableFactory.pressOutHandlerKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
131
|
+
|
|
132
|
+
default:
|
|
133
|
+
break
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// MARK: - PressableView
|
|
139
|
+
|
|
140
|
+
/// Extension of TouchableView that adds pressIn and pressOut callbacks.
|
|
141
|
+
/// Fires pressIn when a touch begins and pressOut when the touch ends or is cancelled.
|
|
142
|
+
final class PressableView: TouchableView {
|
|
143
|
+
|
|
144
|
+
/// Called when a touch begins inside the view bounds.
|
|
145
|
+
var onPressIn: (() -> Void)?
|
|
146
|
+
|
|
147
|
+
/// Called when a touch ends or is cancelled.
|
|
148
|
+
var onPressOut: (() -> Void)?
|
|
149
|
+
|
|
150
|
+
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
151
|
+
super.touchesBegan(touches, with: event)
|
|
152
|
+
guard !isDisabled else { return }
|
|
153
|
+
onPressIn?()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
157
|
+
super.touchesEnded(touches, with: event)
|
|
158
|
+
guard !isDisabled else { return }
|
|
159
|
+
onPressOut?()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
163
|
+
super.touchesCancelled(touches, with: event)
|
|
164
|
+
guard !isDisabled else { return }
|
|
165
|
+
onPressOut?()
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
#endif
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import UIKit
|
|
3
|
+
import FlexLayout
|
|
4
|
+
|
|
5
|
+
/// Factory for VProgressBar — maps to UIProgressView.
|
|
6
|
+
final class VProgressBarFactory: NativeComponentFactory {
|
|
7
|
+
|
|
8
|
+
func createView() -> UIView {
|
|
9
|
+
let bar = UIProgressView(progressViewStyle: .default)
|
|
10
|
+
bar.progress = 0
|
|
11
|
+
_ = bar.flex
|
|
12
|
+
return bar
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
func updateProp(view: UIView, key: String, value: Any?) {
|
|
16
|
+
guard let bar = view as? UIProgressView else {
|
|
17
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
switch key {
|
|
21
|
+
case "progress":
|
|
22
|
+
let p = (value as? Double) ?? (value as? NSNumber)?.doubleValue ?? 0
|
|
23
|
+
bar.setProgress(Float(max(0, min(1, p))), animated: false)
|
|
24
|
+
case "progressTintColor":
|
|
25
|
+
if let str = value as? String { bar.progressTintColor = UIColor.fromHex(str) }
|
|
26
|
+
case "trackTintColor":
|
|
27
|
+
if let str = value as? String { bar.trackTintColor = UIColor.fromHex(str) }
|
|
28
|
+
case "animated":
|
|
29
|
+
// stored for use in future progress updates — no-op here
|
|
30
|
+
break
|
|
31
|
+
default:
|
|
32
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func addEventListener(view: UIView, event: String, handler: @escaping (Any?) -> Void) {}
|
|
37
|
+
func removeEventListener(view: UIView, event: String) {}
|
|
38
|
+
}
|
|
39
|
+
#endif
|
package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VRadioFactory.swift
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#if canImport(UIKit)
|
|
2
|
+
import UIKit
|
|
3
|
+
import FlexLayout
|
|
4
|
+
import ObjectiveC
|
|
5
|
+
|
|
6
|
+
private var radioOnChangeKey: UInt8 = 0
|
|
7
|
+
private var radioOptionsKey: UInt8 = 1
|
|
8
|
+
private var radioSelectedKey: UInt8 = 2
|
|
9
|
+
|
|
10
|
+
/// Factory for VRadio — a radio button group.
|
|
11
|
+
/// Uses a vertical UIStackView with rows of custom radio circles and labels.
|
|
12
|
+
final class VRadioFactory: NativeComponentFactory {
|
|
13
|
+
|
|
14
|
+
func createView() -> UIView {
|
|
15
|
+
let stack = UIStackView()
|
|
16
|
+
stack.axis = .vertical
|
|
17
|
+
stack.spacing = 12
|
|
18
|
+
stack.alignment = .leading
|
|
19
|
+
_ = stack.flex
|
|
20
|
+
return stack
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func updateProp(view: UIView, key: String, value: Any?) {
|
|
24
|
+
guard let stack = view as? UIStackView else {
|
|
25
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
switch key {
|
|
30
|
+
case "options":
|
|
31
|
+
guard let items = value as? [[String: Any]] else { return }
|
|
32
|
+
let options = items.compactMap { dict -> (label: String, value: String)? in
|
|
33
|
+
guard let label = dict["label"] as? String,
|
|
34
|
+
let val = dict["value"] as? String else { return nil }
|
|
35
|
+
return (label, val)
|
|
36
|
+
}
|
|
37
|
+
objc_setAssociatedObject(view, &radioOptionsKey, options.map { $0.value }, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
38
|
+
|
|
39
|
+
// Rebuild radio buttons
|
|
40
|
+
stack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
|
41
|
+
for (index, option) in options.enumerated() {
|
|
42
|
+
let row = createRadioRow(label: option.label, index: index, in: view)
|
|
43
|
+
stack.addArrangedSubview(row)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Re-apply selection
|
|
47
|
+
if let selected = objc_getAssociatedObject(view, &radioSelectedKey) as? String {
|
|
48
|
+
applySelection(stack, selectedValue: selected)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
case "selectedValue":
|
|
52
|
+
let selected = value as? String ?? ""
|
|
53
|
+
objc_setAssociatedObject(view, &radioSelectedKey, selected, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
54
|
+
applySelection(stack, selectedValue: selected)
|
|
55
|
+
|
|
56
|
+
case "disabled":
|
|
57
|
+
let disabled = (value as? Bool) ?? false
|
|
58
|
+
stack.isUserInteractionEnabled = !disabled
|
|
59
|
+
stack.alpha = disabled ? 0.4 : 1.0
|
|
60
|
+
|
|
61
|
+
case "tintColor":
|
|
62
|
+
if let colorStr = value as? String {
|
|
63
|
+
let color = UIColor.fromHex(colorStr)
|
|
64
|
+
for subview in stack.arrangedSubviews {
|
|
65
|
+
if let circle = subview.viewWithTag(2001) as? UIImageView {
|
|
66
|
+
circle.tintColor = color
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
default:
|
|
72
|
+
StyleEngine.apply(key: key, value: value, to: view)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
func addEventListener(view: UIView, event: String, handler: @escaping (Any?) -> Void) {
|
|
77
|
+
guard event == "change" else { return }
|
|
78
|
+
objc_setAssociatedObject(view, &radioOnChangeKey, handler as AnyObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func removeEventListener(view: UIView, event: String) {
|
|
82
|
+
if event == "change" {
|
|
83
|
+
objc_setAssociatedObject(view, &radioOnChangeKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
84
|
+
// Remove RadioTapGesture recognizers from all radio row subviews
|
|
85
|
+
if let stack = view as? UIStackView {
|
|
86
|
+
for row in stack.arrangedSubviews {
|
|
87
|
+
row.gestureRecognizers?.forEach { recognizer in
|
|
88
|
+
if recognizer is RadioTapGesture {
|
|
89
|
+
row.removeGestureRecognizer(recognizer)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// MARK: - Helpers
|
|
98
|
+
|
|
99
|
+
private func createRadioRow(label: String, index: Int, in container: UIView) -> UIView {
|
|
100
|
+
let row = UIStackView()
|
|
101
|
+
row.axis = .horizontal
|
|
102
|
+
row.spacing = 8
|
|
103
|
+
row.alignment = .center
|
|
104
|
+
|
|
105
|
+
let circle = UIImageView()
|
|
106
|
+
circle.contentMode = .scaleAspectFit
|
|
107
|
+
circle.tag = 2001
|
|
108
|
+
circle.tintColor = .systemBlue
|
|
109
|
+
circle.translatesAutoresizingMaskIntoConstraints = false
|
|
110
|
+
NSLayoutConstraint.activate([
|
|
111
|
+
circle.widthAnchor.constraint(equalToConstant: 22),
|
|
112
|
+
circle.heightAnchor.constraint(equalToConstant: 22),
|
|
113
|
+
])
|
|
114
|
+
updateRadioImage(circle, selected: false)
|
|
115
|
+
|
|
116
|
+
let textLabel = UILabel()
|
|
117
|
+
textLabel.text = label
|
|
118
|
+
textLabel.font = UIFont.systemFont(ofSize: 16)
|
|
119
|
+
textLabel.tag = 2002
|
|
120
|
+
|
|
121
|
+
row.addArrangedSubview(circle)
|
|
122
|
+
row.addArrangedSubview(textLabel)
|
|
123
|
+
|
|
124
|
+
row.tag = 3000 + index
|
|
125
|
+
row.isUserInteractionEnabled = true
|
|
126
|
+
let tap = RadioTapGesture(target: self, action: #selector(handleRadioTap(_:)))
|
|
127
|
+
tap.radioIndex = index
|
|
128
|
+
tap.containerView = container
|
|
129
|
+
row.addGestureRecognizer(tap)
|
|
130
|
+
|
|
131
|
+
return row
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@objc private func handleRadioTap(_ sender: RadioTapGesture) {
|
|
135
|
+
guard let container = sender.containerView else { return }
|
|
136
|
+
let values = objc_getAssociatedObject(container, &radioOptionsKey) as? [String] ?? []
|
|
137
|
+
guard sender.radioIndex < values.count else { return }
|
|
138
|
+
let value = values[sender.radioIndex]
|
|
139
|
+
|
|
140
|
+
if let handler = objc_getAssociatedObject(container, &radioOnChangeKey) as? ((Any?) -> Void) {
|
|
141
|
+
handler(["value": value])
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private func applySelection(_ stack: UIStackView, selectedValue: String) {
|
|
146
|
+
let values = objc_getAssociatedObject(stack, &radioOptionsKey) as? [String] ?? []
|
|
147
|
+
for (index, subview) in stack.arrangedSubviews.enumerated() {
|
|
148
|
+
if let circle = subview.viewWithTag(2001) as? UIImageView {
|
|
149
|
+
let isSelected = index < values.count && values[index] == selectedValue
|
|
150
|
+
updateRadioImage(circle, selected: isSelected)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private func updateRadioImage(_ imageView: UIImageView, selected: Bool) {
|
|
156
|
+
let symbolName = selected ? "circle.inset.filled" : "circle"
|
|
157
|
+
let config = UIImage.SymbolConfiguration(pointSize: 20, weight: .regular)
|
|
158
|
+
imageView.image = UIImage(systemName: symbolName, withConfiguration: config)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// Custom UITapGestureRecognizer that carries the radio index and parent view.
|
|
163
|
+
private final class RadioTapGesture: UITapGestureRecognizer {
|
|
164
|
+
var radioIndex: Int = 0
|
|
165
|
+
weak var containerView: UIView?
|
|
166
|
+
}
|
|
167
|
+
#endif
|