pythonnative 0.36.0__py3-none-any.whl → 0.37.0__py3-none-any.whl
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.
- pythonnative/__init__.py +6 -4
- pythonnative/_ios_log.py +1 -1
- pythonnative/alerts.py +34 -88
- pythonnative/bootstrap.py +90 -0
- pythonnative/bridge/__init__.py +306 -0
- pythonnative/bridge/android.py +78 -0
- pythonnative/bridge/codec.py +164 -0
- pythonnative/bridge/fake.py +236 -0
- pythonnative/bridge/ios.py +138 -0
- pythonnative/components/layout.py +65 -3
- pythonnative/gestures.py +13 -10
- pythonnative/hooks.py +41 -26
- pythonnative/hosts/__init__.py +13 -36
- pythonnative/hosts/native.py +348 -0
- pythonnative/images.py +6 -5
- pythonnative/native_modules/__init__.py +30 -29
- pythonnative/native_modules/app_state.py +10 -6
- pythonnative/native_modules/battery.py +20 -84
- pythonnative/native_modules/biometrics.py +11 -124
- pythonnative/native_modules/camera.py +15 -291
- pythonnative/native_modules/clipboard.py +14 -109
- pythonnative/native_modules/desktop.py +322 -0
- pythonnative/native_modules/file_system.py +15 -21
- pythonnative/native_modules/haptics.py +19 -129
- pythonnative/native_modules/linking.py +24 -137
- pythonnative/native_modules/location.py +28 -180
- pythonnative/native_modules/net_info.py +25 -135
- pythonnative/native_modules/notifications.py +36 -257
- pythonnative/native_modules/permissions.py +20 -184
- pythonnative/native_modules/registry.py +513 -0
- pythonnative/native_modules/secure_store.py +18 -155
- pythonnative/native_modules/share.py +8 -113
- pythonnative/native_views/__init__.py +67 -86
- pythonnative/native_views/bridge_backend.py +264 -0
- pythonnative/platform.py +19 -15
- pythonnative/platform_metrics.py +3 -3
- pythonnative/project/android.py +0 -3
- pythonnative/project/builder.py +38 -16
- pythonnative/project/config.py +12 -2
- pythonnative/project/plugins.py +366 -0
- pythonnative/py.typed +0 -0
- pythonnative/runtime.py +35 -203
- pythonnative/sdk/__init__.py +48 -33
- pythonnative/sdk/_components.py +57 -76
- pythonnative/storage.py +18 -243
- pythonnative/templates/android_template/app/build.gradle +6 -9
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/MainActivity.kt +34 -166
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/ScreenFragment.kt +13 -151
- pythonnative/templates/android_template/pythonnative/build.gradle +48 -0
- pythonnative/templates/android_template/pythonnative/src/main/AndroidManifest.xml +11 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PNBridge.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/PythonHost.kt +20 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/AnimationSpecs.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/animation/PNAnimator.kt +249 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/JsonUtil.kt +175 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/MainThread.kt +32 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNLog.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNRegistry.kt +82 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/PNTransaction.kt +80 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/TransactionApplier.kt +115 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/bridge/ViewRegistry.kt +74 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/BuiltinComponents.kt +35 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ButtonManager.kt +33 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ComponentManager.kt +213 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ContainerManagers.kt +69 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ControlManagers.kt +186 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/DatePickerManager.kt +123 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageLoader.kt +141 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ImageManager.kt +156 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ModalManager.kt +134 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PNColor.kt +201 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PickerManager.kt +87 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PortalManager.kt +55 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/PressableManager.kt +122 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ScrollViewManager.kt +214 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/SegmentedControlManager.kt +113 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/StatusBarManager.kt +52 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TabBarManager.kt +131 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextInputManager.kt +245 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/TextManager.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/ViewStyler.kt +355 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/VirtualListManager.kt +179 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/components/WebViewManager.kt +149 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureArbiter.kt +233 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureCoordinator.kt +127 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/gestures/GestureRecognizers.kt +527 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/BuiltinModules.kt +73 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/DeviceModules.kt +316 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/HostModule.kt +97 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/MediaModules.kt +200 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NativeModule.kt +135 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/NotificationsModule.kt +100 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/PermissionsModule.kt +88 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/StorageModules.kt +89 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/modules/SystemModules.kt +178 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/plugins/GeneratedPlugins.kt +16 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/Navigator.kt +116 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/PNScreenFragment.kt +223 -0
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/screens/ScreenRegistry.kt +80 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNAccessibilityDelegate.kt +5 -4
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNBorderDrawable.kt +12 -3
- pythonnative/templates/android_template/pythonnative/src/main/java/com/pythonnative/runtime/views/PNEditText.kt +18 -0
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNFrameLayout.kt +9 -10
- pythonnative/templates/android_template/{app/src/main/java/com/pythonnative/android_template → pythonnative/src/main/java/com/pythonnative/runtime/views}/PNVirtualListView.java +9 -12
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/AnimationSpecsTest.kt +75 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/GestureArbiterTest.kt +197 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNColorTest.kt +47 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PNTransactionTest.kt +78 -0
- pythonnative/templates/android_template/pythonnative/src/test/java/com/pythonnative/runtime/PromiseTest.kt +75 -0
- pythonnative/templates/android_template/settings.gradle +1 -0
- pythonnative/templates/ios_template/PythonNativeKit/Package.swift +21 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Animation/PNAnimator.swift +301 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNBridge.swift +191 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNRegistry.swift +140 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNTransaction.swift +165 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Bridge/PNViewRegistry.swift +53 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNButtonManagers.swift +206 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNColor.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNComponentManager.swift +183 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNContainerView.swift +56 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNControlManagers.swift +281 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNImageManager.swift +221 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNOverlayManagers.swift +243 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNPressableManager.swift +114 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNScrollViewManager.swift +197 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTabBarManager.swift +106 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextInputManager.swift +289 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTextManager.swift +230 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNTransform.swift +82 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewManager.swift +86 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewState.swift +87 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNViewStyler.swift +340 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNVirtualListManager.swift +257 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Components/PNWebViewManager.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Gestures/PNGestureCoordinator.swift +260 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/AlertModule.swift +92 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/HostModule.swift +158 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/LifecycleModules.swift +174 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/MediaModules.swift +177 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PNNativeModule.swift +125 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/PermissionModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Modules/SystemModules.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Plugins/PNPluginRegistration.swift +8 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNScreenRegistry.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Screens/PNViewController.swift +251 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNCompat.swift +46 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNJSON.swift +173 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNLog.swift +47 -0
- pythonnative/templates/ios_template/PythonNativeKit/Sources/PythonNativeKit/Support/PNWindow.swift +51 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNColorTests.swift +50 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNGestureTests.swift +101 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNManagerTests.swift +124 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNModuleTests.swift +98 -0
- pythonnative/templates/ios_template/PythonNativeKit/Tests/PythonNativeKitTests/PNTransactionTests.swift +91 -0
- pythonnative/templates/ios_template/ios_template/AppDelegate.swift +8 -50
- pythonnative/templates/ios_template/ios_template/Info.plist +4 -4
- pythonnative/templates/ios_template/ios_template/PythonRuntime.swift +38 -46
- pythonnative/templates/ios_template/ios_template/SceneDelegate.swift +11 -16
- pythonnative/templates/ios_template/ios_template/ViewController.swift +12 -142
- pythonnative/templates/ios_template/ios_template.xcodeproj/project.pbxproj +47 -1
- pythonnative/utils.py +4 -82
- pythonnative/virtual_rows.py +3 -3
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/METADATA +3 -5
- pythonnative-0.37.0.dist-info/RECORD +263 -0
- pythonnative/hosts/android.py +0 -269
- pythonnative/hosts/ios.py +0 -404
- pythonnative/native_views/android.py +0 -3702
- pythonnative/native_views/ios.py +0 -5253
- pythonnative/templates/android_template/app/src/main/java/com/pythonnative/android_template/Navigator.kt +0 -43
- pythonnative-0.36.0.dist-info/RECORD +0 -155
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.36.0.dist-info → pythonnative-0.37.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/// Base class for every element type's native implementation.
|
|
4
|
+
///
|
|
5
|
+
/// One manager instance serves every view of its type; per-view state
|
|
6
|
+
/// is kept in `PNViewState`. Subclasses usually override `makeView` and
|
|
7
|
+
/// `apply`, and opt into `measure`, `command`, and the child hooks when
|
|
8
|
+
/// the stock behavior isn't right.
|
|
9
|
+
open class PNComponentManager {
|
|
10
|
+
public init() {}
|
|
11
|
+
|
|
12
|
+
// MARK: - Creation and props
|
|
13
|
+
|
|
14
|
+
/// Create the native view and apply its initial props.
|
|
15
|
+
open func createView(tag: Int64, props: [String: Any]) -> UIView {
|
|
16
|
+
let view = makeView(props: props)
|
|
17
|
+
view.translatesAutoresizingMaskIntoConstraints = true
|
|
18
|
+
let state = PNViewState.attach(view, tag: tag, typeName: String(describing: type(of: self)))
|
|
19
|
+
state.props = PNComponentManager.stripNulls(props)
|
|
20
|
+
apply(view: view, props: props, initial: true)
|
|
21
|
+
if let gestures = PNProps.value(props, "gestures") {
|
|
22
|
+
PNGestureCoordinator.shared.wire(view: view, specs: gestures)
|
|
23
|
+
}
|
|
24
|
+
return view
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Hook run after the view is registered in `PNViewRegistry`.
|
|
28
|
+
open func didCreate(view: UIView, tag: Int64, props: [String: Any]) {}
|
|
29
|
+
|
|
30
|
+
/// Construct the bare native view. Subclasses override this.
|
|
31
|
+
open func makeView(props: [String: Any]) -> UIView {
|
|
32
|
+
PNContainerView(frame: .zero)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Merge `changed` into the view's props and apply them.
|
|
36
|
+
open func update(view: UIView, changed: [String: Any]) {
|
|
37
|
+
if let state = PNViewState.existing(for: view) {
|
|
38
|
+
for (key, value) in changed {
|
|
39
|
+
if value is NSNull {
|
|
40
|
+
state.props.removeValue(forKey: key)
|
|
41
|
+
} else {
|
|
42
|
+
state.props[key] = value
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
apply(view: view, props: changed, initial: false)
|
|
47
|
+
if PNProps.has(changed, "gestures") {
|
|
48
|
+
PNGestureCoordinator.shared.wire(view: view, specs: PNProps.value(changed, "gestures"))
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Apply `props` (initial or changed) to the view. The default
|
|
53
|
+
/// applies every shared visual prop; leaf managers override.
|
|
54
|
+
open func apply(view: UIView, props: [String: Any], initial: Bool) {
|
|
55
|
+
PNViewStyler.applyCommon(view, props)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// MARK: - Children
|
|
59
|
+
|
|
60
|
+
/// Ensure `child` sits at `index` under `parent` (move-aware, clamped).
|
|
61
|
+
open func insertChild(parent: UIView, child: UIView, index: Int) {
|
|
62
|
+
child.translatesAutoresizingMaskIntoConstraints = true
|
|
63
|
+
let container = childContainer(for: parent)
|
|
64
|
+
let siblings = container.subviews
|
|
65
|
+
if child.superview === container, let current = siblings.firstIndex(where: { $0 === child }) {
|
|
66
|
+
let target = max(0, min(index, siblings.count - 1))
|
|
67
|
+
if current == target { return }
|
|
68
|
+
container.insertSubview(child, at: target)
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
let target = max(0, min(index, siblings.count))
|
|
72
|
+
container.insertSubview(child, at: target)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Detach `child` from `parent`.
|
|
76
|
+
open func removeChild(parent: UIView, child: UIView) {
|
|
77
|
+
if child.superview != nil {
|
|
78
|
+
child.removeFromSuperview()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// The view children are inserted into (the view itself by default).
|
|
83
|
+
open func childContainer(for view: UIView) -> UIView {
|
|
84
|
+
view
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// MARK: - Lifecycle
|
|
88
|
+
|
|
89
|
+
/// Release the view's resources and detach it from the hierarchy.
|
|
90
|
+
open func destroy(view: UIView) {
|
|
91
|
+
teardown(view: view)
|
|
92
|
+
PNGestureCoordinator.shared.unwire(view: view)
|
|
93
|
+
PNAnimator.shared.forget(view: view)
|
|
94
|
+
view.layer.removeAllAnimations()
|
|
95
|
+
if view.superview != nil {
|
|
96
|
+
view.removeFromSuperview()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// Subclass hook for extra cleanup before the view is released.
|
|
101
|
+
open func teardown(view: UIView) {}
|
|
102
|
+
|
|
103
|
+
// MARK: - Layout
|
|
104
|
+
|
|
105
|
+
/// Apply a frame from the layout engine (transform-safe, non-finite values clamped).
|
|
106
|
+
open func setFrame(view: UIView, x: Double, y: Double, w: Double, h: Double) {
|
|
107
|
+
let fx = CGFloat(PNProps.finite(x))
|
|
108
|
+
let fy = CGFloat(PNProps.finite(y))
|
|
109
|
+
let fw = CGFloat(max(0, PNProps.finite(w)))
|
|
110
|
+
let fh = CGFloat(max(0, PNProps.finite(h)))
|
|
111
|
+
if !(x.isFinite && y.isFinite && w.isFinite && h.isFinite) {
|
|
112
|
+
PNLog.rateLimited(PNLog.components, key: "set_frame:nan", "[set_frame:nan] (\(x), \(y), \(w), \(h)) clamped")
|
|
113
|
+
}
|
|
114
|
+
view.translatesAutoresizingMaskIntoConstraints = true
|
|
115
|
+
// Setting `bounds.size` + `center` (instead of `frame`) keeps the
|
|
116
|
+
// frame meaningful under a non-identity transform and preserves
|
|
117
|
+
// a scroll view's content offset (its `bounds.origin`).
|
|
118
|
+
var bounds = view.bounds
|
|
119
|
+
bounds.size = CGSize(width: fw, height: fh)
|
|
120
|
+
view.bounds = bounds
|
|
121
|
+
let anchor = view.layer.anchorPoint
|
|
122
|
+
view.center = CGPoint(x: fx + fw * anchor.x, y: fy + fh * anchor.y)
|
|
123
|
+
PNViewStyler.syncFrameDependentStyles(view, size: CGSize(width: fw, height: fh))
|
|
124
|
+
if let scroll = view.superview as? UIScrollView, !(scroll is UITableView) {
|
|
125
|
+
let visible = scroll.bounds.size
|
|
126
|
+
let contentW = max(visible.width, fx + fw)
|
|
127
|
+
let contentH = max(visible.height, fy + fh)
|
|
128
|
+
if scroll.contentSize != CGSize(width: contentW, height: contentH) {
|
|
129
|
+
scroll.contentSize = CGSize(width: contentW, height: contentH)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Natural size under the constraints (either may be `1e6` / infinite).
|
|
135
|
+
open func measure(view: UIView, maxW: CGFloat, maxH: CGFloat) -> CGSize {
|
|
136
|
+
let mw = PNComponentManager.clampConstraint(maxW)
|
|
137
|
+
let mh = PNComponentManager.clampConstraint(maxH)
|
|
138
|
+
var size = view.sizeThatFits(CGSize(width: mw, height: mh))
|
|
139
|
+
if maxW.isFinite { size.width = min(size.width, maxW) }
|
|
140
|
+
if !size.width.isFinite { size.width = 0 }
|
|
141
|
+
if !size.height.isFinite { size.height = 0 }
|
|
142
|
+
return size
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// MARK: - Commands and animation
|
|
146
|
+
|
|
147
|
+
/// Run an imperative command. Unknown commands return `nil`.
|
|
148
|
+
open func command(view: UIView, name: String, args: [String: Any]) -> Any? {
|
|
149
|
+
nil
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// Apply one Python-driven animation frame immediately.
|
|
153
|
+
open func setAnimatedProperty(view: UIView, prop: String, value: Any?) {
|
|
154
|
+
PNAnimator.shared.applyValue(view: view, prop: prop, value: value)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// Start a natively driven animation. Returns `false` to fall back to Python.
|
|
158
|
+
open func startAnimation(view: UIView, id: Int64, prop: String, spec: [String: Any]) -> Bool {
|
|
159
|
+
PNAnimator.shared.start(view: view, id: id, prop: prop, spec: spec)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// Cancel a native animation, returning the presentation value when known.
|
|
163
|
+
open func cancelAnimation(view: UIView, id: Int64) -> Any? {
|
|
164
|
+
PNAnimator.shared.cancel(id: id)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// MARK: - Helpers
|
|
168
|
+
|
|
169
|
+
/// The merged props recorded for `view` (empty when unmanaged).
|
|
170
|
+
public func mergedProps(_ view: UIView) -> [String: Any] {
|
|
171
|
+
PNViewState.existing(for: view)?.props ?? [:]
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// Clamp an infinite or oversized layout constraint for UIKit.
|
|
175
|
+
public static func clampConstraint(_ value: CGFloat, fallback: CGFloat = 10_000) -> CGFloat {
|
|
176
|
+
guard value.isFinite else { return fallback }
|
|
177
|
+
return max(0, min(value, fallback))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
static func stripNulls(_ props: [String: Any]) -> [String: Any] {
|
|
181
|
+
props.filter { !($0.value is NSNull) }
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/// Hit-test-aware container used by View, Row, Column, Pressable,
|
|
4
|
+
/// SafeAreaView, and KeyboardAvoidingView.
|
|
5
|
+
///
|
|
6
|
+
/// Implements `pointer_events` (`"none"`, `"box_none"`, `"box_only"`,
|
|
7
|
+
/// anything else = auto) and `hit_slop` by overriding the two UIKit
|
|
8
|
+
/// hit-testing entry points. With neither prop set the behavior is
|
|
9
|
+
/// exactly stock `UIView`.
|
|
10
|
+
open class PNContainerView: UIView {
|
|
11
|
+
/// `nil` or `"auto"` means stock hit testing.
|
|
12
|
+
public var pointerEvents: String?
|
|
13
|
+
/// Extra touchable margin as (top, left, bottom, right).
|
|
14
|
+
public var hitSlop: UIEdgeInsets = .zero
|
|
15
|
+
|
|
16
|
+
public override init(frame: CGRect) {
|
|
17
|
+
super.init(frame: frame)
|
|
18
|
+
translatesAutoresizingMaskIntoConstraints = true
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public required init?(coder: NSCoder) {
|
|
22
|
+
super.init(coder: coder)
|
|
23
|
+
translatesAutoresizingMaskIntoConstraints = true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
|
27
|
+
if pointerEvents == "none" { return false }
|
|
28
|
+
let expanded = bounds.inset(by: UIEdgeInsets(
|
|
29
|
+
top: -hitSlop.top, left: -hitSlop.left, bottom: -hitSlop.bottom, right: -hitSlop.right
|
|
30
|
+
))
|
|
31
|
+
return expanded.contains(point)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
35
|
+
if pointerEvents == "none" { return nil }
|
|
36
|
+
let result = super.hitTest(point, with: event)
|
|
37
|
+
switch pointerEvents {
|
|
38
|
+
case "box_none":
|
|
39
|
+
return result === self ? nil : result
|
|
40
|
+
case "box_only":
|
|
41
|
+
return result == nil ? nil : self
|
|
42
|
+
default:
|
|
43
|
+
return result
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Manager used for element types nobody registered. Creates a plain
|
|
49
|
+
/// `UIView` so the tree stays consistent.
|
|
50
|
+
final class PNPlaceholderManager: PNComponentManager {
|
|
51
|
+
override func createView(tag: Int64, props: [String: Any]) -> UIView {
|
|
52
|
+
let view = PNContainerView(frame: .zero)
|
|
53
|
+
view.backgroundColor = .clear
|
|
54
|
+
return view
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
/// `Switch`: `UISwitch` emitting `on_change(bool)`.
|
|
4
|
+
public final class PNSwitchManager: PNComponentManager {
|
|
5
|
+
public override func makeView(props: [String: Any]) -> UIView {
|
|
6
|
+
UISwitch(frame: .zero)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
public override func createView(tag: Int64, props: [String: Any]) -> UIView {
|
|
10
|
+
let view = super.createView(tag: tag, props: props)
|
|
11
|
+
if let control = view as? UISwitch {
|
|
12
|
+
PNActionTarget.attach(control, events: .valueChanged) { [weak control] in
|
|
13
|
+
guard let control = control, PNViewState.existing(for: control)?.flag("suppress") != true else { return }
|
|
14
|
+
PNEvents.emit(control, "on_change", [control.isOn])
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return view
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public override func apply(view: UIView, props: [String: Any], initial: Bool) {
|
|
21
|
+
guard let control = view as? UISwitch, let state = PNViewState.existing(for: control) else { return }
|
|
22
|
+
if PNProps.has(props, "value") {
|
|
23
|
+
let value = PNProps.bool(PNProps.value(props, "value")) ?? false
|
|
24
|
+
if control.isOn != value {
|
|
25
|
+
state.extras["suppress"] = true
|
|
26
|
+
control.setOn(value, animated: !initial)
|
|
27
|
+
state.extras["suppress"] = false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if let color = PNColor.parse(PNProps.value(props, "on_tint_color") ?? PNProps.value(props, "tint_color")) {
|
|
31
|
+
control.onTintColor = color
|
|
32
|
+
}
|
|
33
|
+
if let color = PNColor.parse(PNProps.value(props, "thumb_color")) {
|
|
34
|
+
control.thumbTintColor = color
|
|
35
|
+
}
|
|
36
|
+
if PNProps.has(props, "enabled") {
|
|
37
|
+
control.isEnabled = PNProps.bool(PNProps.value(props, "enabled")) ?? true
|
|
38
|
+
}
|
|
39
|
+
PNViewStyler.applyAccessibility(control, props)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public override func measure(view: UIView, maxW: CGFloat, maxH: CGFloat) -> CGSize {
|
|
43
|
+
CGSize(width: 51, height: 31)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// `UISwitch` ignores the size part of `frame`, but the base class sets
|
|
47
|
+
/// `bounds` directly, which would stretch its hit area (and accessibility
|
|
48
|
+
/// frame) across a stretched flex cross axis. Pin the box to the natural
|
|
49
|
+
/// size at the layout origin so taps land on the visible control.
|
|
50
|
+
public override func setFrame(view: UIView, x: Double, y: Double, w: Double, h: Double) {
|
|
51
|
+
let natural = view.intrinsicContentSize
|
|
52
|
+
let pw = natural.width > 0 ? min(w, Double(natural.width)) : w
|
|
53
|
+
let ph = natural.height > 0 ? min(h, Double(natural.height)) : h
|
|
54
|
+
super.setFrame(view: view, x: x, y: y, w: pw, h: ph)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// `Slider`: `UISlider` emitting `on_change(float)`.
|
|
59
|
+
public final class PNSliderManager: PNComponentManager {
|
|
60
|
+
public override func makeView(props: [String: Any]) -> UIView {
|
|
61
|
+
UISlider(frame: .zero)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public override func createView(tag: Int64, props: [String: Any]) -> UIView {
|
|
65
|
+
let view = super.createView(tag: tag, props: props)
|
|
66
|
+
if let slider = view as? UISlider {
|
|
67
|
+
PNActionTarget.attach(slider, events: .valueChanged) { [weak slider] in
|
|
68
|
+
guard let slider = slider, PNViewState.existing(for: slider)?.flag("suppress") != true else { return }
|
|
69
|
+
PNEvents.emit(slider, "on_change", [Double(slider.value)])
|
|
70
|
+
}
|
|
71
|
+
PNActionTarget.attach(slider, events: [.touchUpInside, .touchUpOutside, .touchCancel]) { [weak slider] in
|
|
72
|
+
guard let slider = slider else { return }
|
|
73
|
+
PNEvents.emitIfWired(slider, "on_sliding_complete", [Double(slider.value)])
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return view
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public override func apply(view: UIView, props: [String: Any], initial: Bool) {
|
|
80
|
+
guard let slider = view as? UISlider, let state = PNViewState.existing(for: slider) else { return }
|
|
81
|
+
if let min = PNProps.double(PNProps.value(props, "min_value")) { slider.minimumValue = Float(min) }
|
|
82
|
+
if let max = PNProps.double(PNProps.value(props, "max_value")) { slider.maximumValue = Float(max) }
|
|
83
|
+
if let value = PNProps.double(PNProps.value(props, "value")), abs(Double(slider.value) - value) > 1e-9 {
|
|
84
|
+
state.extras["suppress"] = true
|
|
85
|
+
slider.setValue(Float(value), animated: !initial)
|
|
86
|
+
state.extras["suppress"] = false
|
|
87
|
+
}
|
|
88
|
+
if let color = PNColor.parse(PNProps.value(props, "minimum_track_color") ?? PNProps.value(props, "tint_color")) {
|
|
89
|
+
slider.minimumTrackTintColor = color
|
|
90
|
+
}
|
|
91
|
+
if let color = PNColor.parse(PNProps.value(props, "maximum_track_color")) { slider.maximumTrackTintColor = color }
|
|
92
|
+
if let color = PNColor.parse(PNProps.value(props, "thumb_color")) { slider.thumbTintColor = color }
|
|
93
|
+
if PNProps.has(props, "enabled") { slider.isEnabled = PNProps.bool(PNProps.value(props, "enabled")) ?? true }
|
|
94
|
+
PNViewStyler.applyAccessibility(slider, props)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public override func measure(view: UIView, maxW: CGFloat, maxH: CGFloat) -> CGSize {
|
|
98
|
+
let w = maxW.isFinite && maxW < 1e6 ? maxW : 200
|
|
99
|
+
return CGSize(width: max(w, 100), height: 34)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// `ActivityIndicator`: `UIActivityIndicatorView`.
|
|
104
|
+
public final class PNActivityIndicatorManager: PNComponentManager {
|
|
105
|
+
public override func makeView(props: [String: Any]) -> UIView {
|
|
106
|
+
let style: UIActivityIndicatorView.Style = PNProps.string(PNProps.value(props, "size")) == "large" ? .large : .medium
|
|
107
|
+
let view = UIActivityIndicatorView(style: style)
|
|
108
|
+
view.hidesWhenStopped = true
|
|
109
|
+
return view
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public override func apply(view: UIView, props: [String: Any], initial: Bool) {
|
|
113
|
+
guard let indicator = view as? UIActivityIndicatorView else { return }
|
|
114
|
+
if !initial, let size = PNProps.string(PNProps.value(props, "size")) {
|
|
115
|
+
indicator.style = size == "large" ? .large : .medium
|
|
116
|
+
}
|
|
117
|
+
if let color = PNColor.parse(PNProps.value(props, "color")) { indicator.color = color }
|
|
118
|
+
if PNProps.has(props, "animating") || initial {
|
|
119
|
+
let animating = PNProps.bool(PNProps.value(props, "animating")) ?? true
|
|
120
|
+
if animating { indicator.startAnimating() } else { indicator.stopAnimating() }
|
|
121
|
+
}
|
|
122
|
+
if PNProps.has(props, "hides_when_stopped") {
|
|
123
|
+
indicator.hidesWhenStopped = PNProps.bool(PNProps.value(props, "hides_when_stopped")) ?? true
|
|
124
|
+
}
|
|
125
|
+
PNViewStyler.applyAccessibility(indicator, props)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
public override func measure(view: UIView, maxW: CGFloat, maxH: CGFloat) -> CGSize {
|
|
129
|
+
let size = view.intrinsicContentSize
|
|
130
|
+
return size.width > 0 ? size : CGSize(width: 20, height: 20)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// `ProgressBar`: a determinate `UIProgressView`, or a spinning
|
|
135
|
+
/// `UIActivityIndicatorView` when `indeterminate` is set at creation.
|
|
136
|
+
public final class PNProgressBarManager: PNComponentManager {
|
|
137
|
+
public override func makeView(props: [String: Any]) -> UIView {
|
|
138
|
+
if PNProps.bool(PNProps.value(props, "indeterminate")) == true {
|
|
139
|
+
let spinner = UIActivityIndicatorView(style: .medium)
|
|
140
|
+
spinner.startAnimating()
|
|
141
|
+
return spinner
|
|
142
|
+
}
|
|
143
|
+
return UIProgressView(progressViewStyle: .default)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public override func apply(view: UIView, props: [String: Any], initial: Bool) {
|
|
147
|
+
if let spinner = view as? UIActivityIndicatorView {
|
|
148
|
+
if let color = PNColor.parse(PNProps.value(props, "color")) { spinner.color = color }
|
|
149
|
+
spinner.startAnimating()
|
|
150
|
+
} else if let bar = view as? UIProgressView {
|
|
151
|
+
if let value = PNProps.double(PNProps.value(props, "value") ?? PNProps.value(props, "progress")) {
|
|
152
|
+
bar.setProgress(Float(max(0, min(1, value))), animated: !initial)
|
|
153
|
+
}
|
|
154
|
+
if let color = PNColor.parse(PNProps.value(props, "color")) { bar.progressTintColor = color }
|
|
155
|
+
if let color = PNColor.parse(PNProps.value(props, "track_color")) { bar.trackTintColor = color }
|
|
156
|
+
}
|
|
157
|
+
PNViewStyler.applyAccessibility(view, props)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public override func measure(view: UIView, maxW: CGFloat, maxH: CGFloat) -> CGSize {
|
|
161
|
+
if view is UIActivityIndicatorView { return CGSize(width: 20, height: 20) }
|
|
162
|
+
let w = maxW.isFinite && maxW < 1e6 ? maxW : 200
|
|
163
|
+
return CGSize(width: max(w, 40), height: 4)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/// `SegmentedControl`: `UISegmentedControl` emitting `on_change(index)`.
|
|
168
|
+
public final class PNSegmentedControlManager: PNComponentManager {
|
|
169
|
+
public override func makeView(props: [String: Any]) -> UIView {
|
|
170
|
+
let segments = ((PNProps.value(props, "segments") as? [Any]) ?? []).map { PNProps.string($0) ?? "" }
|
|
171
|
+
return UISegmentedControl(items: segments)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public override func createView(tag: Int64, props: [String: Any]) -> UIView {
|
|
175
|
+
let view = super.createView(tag: tag, props: props)
|
|
176
|
+
if let control = view as? UISegmentedControl {
|
|
177
|
+
PNActionTarget.attach(control, events: .valueChanged) { [weak control] in
|
|
178
|
+
guard let control = control, PNViewState.existing(for: control)?.flag("suppress") != true else { return }
|
|
179
|
+
PNEvents.emit(control, "on_change", [control.selectedSegmentIndex])
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return view
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
public override func apply(view: UIView, props: [String: Any], initial: Bool) {
|
|
186
|
+
guard let control = view as? UISegmentedControl, let state = PNViewState.existing(for: control) else { return }
|
|
187
|
+
let merged = state.props
|
|
188
|
+
var rebuilt = false
|
|
189
|
+
if let segments = PNProps.value(props, "segments") as? [Any] {
|
|
190
|
+
let titles = segments.map { PNProps.string($0) ?? "" }
|
|
191
|
+
if !initial, titles != (state.extras["segments"] as? [String]) {
|
|
192
|
+
state.extras["suppress"] = true
|
|
193
|
+
control.removeAllSegments()
|
|
194
|
+
for (i, title) in titles.enumerated() {
|
|
195
|
+
control.insertSegment(withTitle: title, at: i, animated: false)
|
|
196
|
+
}
|
|
197
|
+
state.extras["suppress"] = false
|
|
198
|
+
rebuilt = true
|
|
199
|
+
}
|
|
200
|
+
state.extras["segments"] = titles
|
|
201
|
+
}
|
|
202
|
+
if rebuilt || initial || PNProps.value(props, "selected_index") != nil {
|
|
203
|
+
state.extras["suppress"] = true
|
|
204
|
+
control.selectedSegmentIndex = PNProps.int(PNProps.value(merged, "selected_index")) ?? 0
|
|
205
|
+
state.extras["suppress"] = false
|
|
206
|
+
}
|
|
207
|
+
if let color = PNColor.parse(PNProps.value(props, "tint_color")) {
|
|
208
|
+
control.selectedSegmentTintColor = color
|
|
209
|
+
control.tintColor = color
|
|
210
|
+
}
|
|
211
|
+
if PNProps.has(props, "enabled") {
|
|
212
|
+
control.isEnabled = PNProps.bool(PNProps.value(props, "enabled")) ?? true
|
|
213
|
+
}
|
|
214
|
+
PNViewStyler.applyAccessibility(control, props)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/// `DatePicker`: compact `UIDatePicker` emitting ISO-style strings.
|
|
219
|
+
public final class PNDatePickerManager: PNComponentManager {
|
|
220
|
+
static let formats = ["date": "yyyy-MM-dd", "time": "HH:mm", "datetime": "yyyy-MM-dd'T'HH:mm"]
|
|
221
|
+
static var formatters: [String: DateFormatter] = [:]
|
|
222
|
+
|
|
223
|
+
static func formatter(mode: String) -> DateFormatter {
|
|
224
|
+
let format = formats[mode] ?? formats["date"]!
|
|
225
|
+
if let cached = formatters[format] { return cached }
|
|
226
|
+
let formatter = DateFormatter()
|
|
227
|
+
formatter.dateFormat = format
|
|
228
|
+
formatter.locale = Locale(identifier: "en_US_POSIX")
|
|
229
|
+
formatters[format] = formatter
|
|
230
|
+
return formatter
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
public override func makeView(props: [String: Any]) -> UIView {
|
|
234
|
+
let picker = UIDatePicker(frame: .zero)
|
|
235
|
+
picker.pnApplyCompactStyle()
|
|
236
|
+
return picker
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
public override func createView(tag: Int64, props: [String: Any]) -> UIView {
|
|
240
|
+
let view = super.createView(tag: tag, props: props)
|
|
241
|
+
if let picker = view as? UIDatePicker {
|
|
242
|
+
PNActionTarget.attach(picker, events: .valueChanged) { [weak picker] in
|
|
243
|
+
guard let picker = picker, let state = PNViewState.existing(for: picker), !state.flag("suppress") else { return }
|
|
244
|
+
let mode = PNProps.string(PNProps.value(state.props, "mode")) ?? "date"
|
|
245
|
+
PNEvents.emit(picker, "on_change", [PNDatePickerManager.formatter(mode: mode).string(from: picker.date)])
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return view
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
public override func apply(view: UIView, props: [String: Any], initial: Bool) {
|
|
252
|
+
guard let picker = view as? UIDatePicker, let state = PNViewState.existing(for: picker) else { return }
|
|
253
|
+
let mode = PNProps.string(PNProps.value(state.props, "mode")) ?? "date"
|
|
254
|
+
if PNProps.value(props, "mode") != nil {
|
|
255
|
+
switch mode {
|
|
256
|
+
case "time": picker.datePickerMode = .time
|
|
257
|
+
case "datetime": picker.datePickerMode = .dateAndTime
|
|
258
|
+
default: picker.datePickerMode = .date
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
let formatter = PNDatePickerManager.formatter(mode: mode)
|
|
262
|
+
if PNProps.has(props, "minimum") {
|
|
263
|
+
picker.minimumDate = PNProps.string(PNProps.value(props, "minimum")).flatMap { formatter.date(from: $0) }
|
|
264
|
+
}
|
|
265
|
+
if PNProps.has(props, "maximum") {
|
|
266
|
+
picker.maximumDate = PNProps.string(PNProps.value(props, "maximum")).flatMap { formatter.date(from: $0) }
|
|
267
|
+
}
|
|
268
|
+
if let value = PNProps.string(PNProps.value(props, "value")), !value.isEmpty, let date = formatter.date(from: value) {
|
|
269
|
+
state.extras["suppress"] = true
|
|
270
|
+
picker.setDate(date, animated: false)
|
|
271
|
+
state.extras["suppress"] = false
|
|
272
|
+
}
|
|
273
|
+
if PNProps.has(props, "enabled") {
|
|
274
|
+
picker.isEnabled = PNProps.bool(PNProps.value(props, "enabled")) ?? true
|
|
275
|
+
}
|
|
276
|
+
if let color = PNColor.parse(PNProps.value(props, "tint_color")) {
|
|
277
|
+
picker.tintColor = color
|
|
278
|
+
}
|
|
279
|
+
PNViewStyler.applyAccessibility(picker, props)
|
|
280
|
+
}
|
|
281
|
+
}
|