-
-
Notifications
You must be signed in to change notification settings - Fork 155
Expand file tree
/
Copy pathKeyboardControllerViewManager.swift
More file actions
185 lines (157 loc) · 5.44 KB
/
KeyboardControllerViewManager.swift
File metadata and controls
185 lines (157 loc) · 5.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
@MainActor
@objc(KeyboardControllerViewManager)
class KeyboardControllerViewManager: RCTViewManager {
override class func requiresMainQueueSetup() -> Bool {
return false
}
override func view() -> (KeyboardControllerView) {
return KeyboardControllerView(frame: .zero, bridge: bridge)
}
@objc(synchronizeFocusedInputLayout:)
func synchronizeFocusedInputLayout(_ reactTag: NSNumber) {
bridge.uiManager.addUIBlock { _, viewRegistry in
guard let view = viewRegistry?[reactTag] as? KeyboardControllerView else {
return
}
view.inputObserver?.syncUpLayout()
KeyboardController.shared()?.sendEvent("KeyboardController::layoutDidSynchronize", body: nil)
}
}
}
class KeyboardControllerView: UIView {
// internal variables
private var keyboardObserver: KeyboardMovementObserver?
var inputObserver: FocusedInputObserver?
private var eventDispatcher: RCTEventDispatcherProtocol
private var bridge: RCTBridge
// internal state
private var lastScreenSize: CGSize = .zero
// react callbacks
/// keyboard
@objc var onKeyboardMoveStart: RCTDirectEventBlock?
@objc var onKeyboardMove: RCTDirectEventBlock?
@objc var onKeyboardMoveEnd: RCTDirectEventBlock?
@objc var onKeyboardMoveInteractive: RCTDirectEventBlock?
/// focused input
@objc var onFocusedInputLayoutChanged: RCTDirectEventBlock?
@objc var onFocusedInputTextChanged: RCTDirectEventBlock?
@objc var onFocusedInputSelectionChanged: RCTDirectEventBlock?
// react props
@objc var enabled: ObjCBool = true {
didSet {
if enabled.boolValue {
mount()
} else {
unmount()
}
}
}
init(frame: CGRect, bridge: RCTBridge) {
self.bridge = bridge
eventDispatcher = bridge.eventDispatcher()
super.init(frame: frame)
inputObserver = FocusedInputObserver(
onLayoutChangedHandler: { [weak self] event in self?.onLayoutChanged(event: event) },
onTextChangedHandler: { [weak self] text in self?.onTextChanged(text: text) },
onSelectionChangedHandler: { [weak self] event in self?.onSelectionChanged(event: event) },
onFocusDidSet: { [weak self] event in
self?.onNotify(event: "KeyboardController::focusDidSet", data: event)
}
)
keyboardObserver = KeyboardMovementObserver(
handler: { [weak self] event, height, progress, duration, target in
self?.onEvent(event: event, height: height, progress: progress, duration: duration, target: target)
},
onNotify: { [weak self] event, data in
self?.onNotify(event: event, data: data)
},
onRequestAnimation: { [weak self] in
self?.onRequestAnimation()
},
onCancelAnimation: { [weak self] in
self?.onCancelAnimation()
}
)
}
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// for mounting/unmounting observers for lifecycle events we're using willMove(toSuperview) method
// not willMove(toWindow)
// see https://github.com/kirillzyusko/react-native-keyboard-controller/issues/271
override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)
if newSuperview == nil {
// The view is about to be removed from its superview (destroyed)
unmount()
} else {
mount()
}
}
override func didMoveToWindow() {
super.didMoveToWindow()
if window != nil {
keyboardObserver?.keyboardTrackingView.attachToTopmostView(toWindow: window)
}
}
override func layoutSubviews() {
super.layoutSubviews()
guard let window = window else { return }
let screenSize = window.bounds.size
if lastScreenSize == screenSize {
return
}
lastScreenSize = screenSize
var data = [AnyHashable: Any]()
data["width"] = screenSize.width
data["height"] = screenSize.height
onNotify(event: "KeyboardController::windowDidResize", data: data)
}
func onLayoutChanged(event: NSObject) {
guard isJSThreadReady() else { return }
eventDispatcher.send(FocusedInputLayoutChangedEvent(reactTag: reactTag, event: event))
}
func onTextChanged(text: String) {
guard isJSThreadReady() else { return }
eventDispatcher.send(FocusedInputTextChangedEvent(reactTag: reactTag, text: text))
}
func onSelectionChanged(event: NSObject) {
guard isJSThreadReady() else { return }
eventDispatcher.send(FocusedInputSelectionChangedEvent(reactTag: reactTag, event: event))
}
func onEvent(event: NSString, height: NSNumber, progress: NSNumber, duration: NSNumber, target: NSNumber) {
guard isJSThreadReady() else { return }
eventDispatcher.send(
KeyboardMoveEvent(
reactTag: reactTag,
event: event as String,
height: height,
progress: progress,
duration: duration,
target: target
)
)
}
func onRequestAnimation() {
// bridge.uiManager.scheduleKeyboardAnimation()
}
func onCancelAnimation() {
// bridge.uiManager.unscheduleKeyboardAnimation()
}
func onNotify(event: String, data: Any) {
KeyboardController.shared()?.sendEvent(event, body: data)
}
private func mount() {
inputObserver?.mount()
keyboardObserver?.mount()
}
private func unmount() {
inputObserver?.unmount()
keyboardObserver?.unmount()
}
private func isJSThreadReady() -> Bool {
// we don't want to send event to JS before the JS thread is ready
return bridge.value(forKey: "_jsThread") != nil
}
}