@@ -13,157 +13,170 @@ import RxCocoa
13
13
import RxSwift
14
14
15
15
public protocol RxKeyboardType {
16
- var frame : Driver < CGRect > { get }
17
- var visibleHeight : Driver < CGFloat > { get }
18
- var willShowVisibleHeight : Driver < CGFloat > { get }
19
- var isHidden : Driver < Bool > { get }
16
+ var frame : Driver < CGRect > { get }
17
+ var visibleHeight : Driver < CGFloat > { get }
18
+ var willShowVisibleHeight : Driver < CGFloat > { get }
19
+ var isHidden : Driver < Bool > { get }
20
20
}
21
21
22
22
/// RxKeyboard provides a reactive way of observing keyboard frame changes.
23
23
public class RxKeyboard : NSObject , RxKeyboardType {
24
24
25
- // MARK: Public
26
-
27
- /// Get a singleton instance.
28
- public static let instance = RxKeyboard ( )
29
-
30
- /// An observable keyboard frame.
31
- public let frame : Driver < CGRect >
32
-
33
- /// An observable visible height of keyboard. Emits keyboard height if the keyboard is visible
34
- /// or `0` if the keyboard is not visible.
35
- public let visibleHeight : Driver < CGFloat >
36
-
37
- /// Same with `visibleHeight` but only emits values when keyboard is about to show. This is
38
- /// useful when adjusting scroll view content offset.
39
- public let willShowVisibleHeight : Driver < CGFloat >
40
-
41
- /// An observable visibility of keyboard. Emits keyboard visibility
42
- /// when changed keyboard show and hide.
43
- public let isHidden : Driver < Bool >
44
-
45
- // MARK: Private
46
-
47
- private let disposeBag = DisposeBag ( )
48
- private let panRecognizer = UIPanGestureRecognizer ( )
49
-
50
- // MARK: Initializing
51
-
52
- override init ( ) {
53
-
54
- let keyboardWillChangeFrame = UIResponder . keyboardWillChangeFrameNotification
55
- let keyboardWillHide = UIResponder . keyboardWillHideNotification
56
- let keyboardFrameEndKey = UIResponder . keyboardFrameEndUserInfoKey
57
-
58
- let defaultFrame = CGRect (
59
- x: 0 ,
60
- y: UIScreen . main. bounds. height,
61
- width: UIScreen . main. bounds. width,
62
- height: 0
63
- )
64
- let frameVariable = BehaviorRelay < CGRect > ( value: defaultFrame)
65
- self . frame = frameVariable. asDriver ( ) . distinctUntilChanged ( )
66
- self . visibleHeight = self . frame. map { UIScreen . main. bounds. height - $0. origin. y }
67
- self . willShowVisibleHeight = self . visibleHeight
68
- . scan ( ( visibleHeight: 0 , isShowing: false ) ) { lastState, newVisibleHeight in
69
- return ( visibleHeight: newVisibleHeight, isShowing: lastState. visibleHeight == 0 && newVisibleHeight > 0 )
70
- }
71
- . filter { state in state. isShowing }
72
- . map { state in state. visibleHeight }
73
- self . isHidden = self . visibleHeight. map ( { $0 == 0.0 } ) . distinctUntilChanged ( )
74
- super. init ( )
75
-
76
- // keyboard will change frame
77
- let willChangeFrame = NotificationCenter . default. rx. notification ( keyboardWillChangeFrame)
78
- . map { notification -> CGRect in
79
- let rectValue = notification. userInfo ? [ keyboardFrameEndKey] as? NSValue
80
- return rectValue? . cgRectValue ?? defaultFrame
81
- }
82
- . map { frame -> CGRect in
83
- if frame. origin. y < 0 { // if went to wrong frame
84
- var newFrame = frame
85
- newFrame. origin. y = UIScreen . main. bounds. height - newFrame. height
86
- return newFrame
87
- }
88
- return frame
89
- }
90
-
91
- // keyboard will hide
92
- let willHide = NotificationCenter . default. rx. notification ( keyboardWillHide)
93
- . map { notification -> CGRect in
94
- let rectValue = notification. userInfo ? [ keyboardFrameEndKey] as? NSValue
95
- return rectValue? . cgRectValue ?? defaultFrame
96
- }
97
- . map { frame -> CGRect in
98
- if frame. origin. y < 0 { // if went to wrong frame
99
- var newFrame = frame
100
- newFrame. origin. y = UIScreen . main. bounds. height
101
- return newFrame
102
- }
103
- return frame
104
- }
105
-
106
- // pan gesture
107
- let didPan = self . panRecognizer. rx. event
108
- . withLatestFrom ( frameVariable. asObservable ( ) ) { ( $0, $1) }
109
- . flatMap { ( gestureRecognizer, frame) -> Observable < CGRect > in
110
- guard case . changed = gestureRecognizer. state,
111
- let window = UIApplication . shared. windows. first,
112
- frame. origin. y < UIScreen . main. bounds. height
113
- else { return . empty( ) }
114
- let origin = gestureRecognizer. location ( in: window)
115
- var newFrame = frame
116
- newFrame. origin. y = max ( origin. y, UIScreen . main. bounds. height - frame. height)
117
- return . just( newFrame)
118
- }
119
-
120
- // merge into single sequence
121
- Observable . of ( didPan, willChangeFrame, willHide) . merge ( )
122
- . bind ( to: frameVariable)
123
- . disposed ( by: self . disposeBag)
124
-
125
- // gesture recognizer
126
- self . panRecognizer. delegate = self
127
-
128
- UIApplication . rx. didFinishLaunching
129
- . map { _ in ( ) }
130
- . startWith ( ( ) ) // when RxKeyboard is initialized before UIApplication.window is created
131
- . subscribe ( onNext: { _ in
132
- UIApplication . shared. windows. first? . addGestureRecognizer ( self . panRecognizer)
133
- } )
134
- . disposed ( by: self . disposeBag)
135
- }
136
-
25
+ // MARK: Public
26
+
27
+ /// Get a singleton instance.
28
+ public static let instance = RxKeyboard ( )
29
+
30
+ /// An observable keyboard frame.
31
+ public let frame : Driver < CGRect >
32
+
33
+ /// An observable visible height of keyboard. Emits keyboard height if the keyboard is visible
34
+ /// or `0` if the keyboard is not visible.
35
+ public let visibleHeight : Driver < CGFloat >
36
+
37
+ /// Same with `visibleHeight` but only emits values when keyboard is about to show. This is
38
+ /// useful when adjusting scroll view content offset.
39
+ public let willShowVisibleHeight : Driver < CGFloat >
40
+
41
+ /// An observable visibility of keyboard. Emits keyboard visibility
42
+ /// when changed keyboard show and hide.
43
+ public let isHidden : Driver < Bool >
44
+
45
+ // MARK: Private
46
+
47
+ private let disposeBag = DisposeBag ( )
48
+ private let panRecognizer = UIPanGestureRecognizer ( )
49
+
50
+ // MARK: Initializing
51
+
52
+ override init ( ) {
53
+
54
+ let defaultFrame = CGRect (
55
+ x: . zero,
56
+ y: UIScreen . main. bounds. height,
57
+ width: UIScreen . main. bounds. width,
58
+ height: . zero
59
+ )
60
+
61
+ let frameVariable = BehaviorRelay < CGRect > ( value: defaultFrame)
62
+
63
+ frame = frameVariable. asDriver ( ) . distinctUntilChanged ( )
64
+ visibleHeight = frame. map { UIScreen . main. bounds. height - $0. origin. y }
65
+
66
+ willShowVisibleHeight = visibleHeight
67
+ . scan ( ( visibleHeight: . zero, isShowing: false ) ) { lastState, newVisibleHeight in
68
+ ( visibleHeight: newVisibleHeight, isShowing: lastState. visibleHeight <= . zero && newVisibleHeight > . zero)
69
+ }
70
+ . filter { $0. isShowing }
71
+ . map { $0. visibleHeight }
72
+
73
+ isHidden = visibleHeight
74
+ . map { $0 <= . ulpOfOne }
75
+ . distinctUntilChanged ( )
76
+
77
+ super. init ( )
78
+
79
+ // keyboard will change frame
80
+ let willChangeFrame = NotificationCenter . default. rx. notification ( . keyboardWillChangeFrame)
81
+ . map { notification -> CGRect in
82
+ let rectValue = notification. userInfo ? [ String . keyboardFrameEndKey] as? NSValue
83
+ return rectValue? . cgRectValue ?? defaultFrame
84
+ }
85
+ . map { frame -> CGRect in
86
+ if frame. origin. y < . zero { // if went to wrong frame
87
+ var newFrame = frame
88
+ newFrame. origin. y = UIScreen . main. bounds. height - newFrame. height
89
+ return newFrame
90
+ }
91
+ return frame
92
+ }
93
+
94
+ // keyboard will hide
95
+ let willHide = NotificationCenter . default. rx. notification ( . keyboardWillHide)
96
+ . map { notification -> CGRect in
97
+ let rectValue = notification. userInfo ? [ String . keyboardFrameEndKey] as? NSValue
98
+ return rectValue? . cgRectValue ?? defaultFrame
99
+ }
100
+ . map { frame -> CGRect in
101
+ if frame. origin. y < . zero { // if went to wrong frame
102
+ var newFrame = frame
103
+ newFrame. origin. y = UIScreen . main. bounds. height
104
+ return newFrame
105
+ }
106
+ return frame
107
+ }
108
+
109
+ // pan gesture
110
+ let didPan = panRecognizer. rx. event
111
+ . withLatestFrom ( frameVariable. asObservable ( ) ) { ( $0, $1) }
112
+ . flatMap { ( gestureRecognizer, frame) -> Observable < CGRect > in
113
+ guard case . changed = gestureRecognizer. state,
114
+ let window = UIApplication . shared. windows. first,
115
+ frame. origin. y < UIScreen . main. bounds. height else {
116
+ return . empty( )
117
+ }
118
+
119
+ let origin = gestureRecognizer. location ( in: window)
120
+ var newFrame = frame
121
+ newFrame. origin. y = max ( origin. y, UIScreen . main. bounds. height - frame. height)
122
+ return . just( newFrame)
123
+ }
124
+
125
+ // merge into single sequence
126
+ Observable . merge ( didPan, willChangeFrame, willHide)
127
+ . bind ( to: frameVariable)
128
+ . disposed ( by: disposeBag)
129
+
130
+ // gesture recognizer
131
+ panRecognizer. delegate = self
132
+
133
+ UIApplication . rx. didFinishLaunching // when RxKeyboard is initialized before UIApplication.window is created
134
+ . withUnretained ( panRecognizer)
135
+ . subscribe { gestureRecognizer, _ in
136
+ UIApplication . shared. windows. first? . addGestureRecognizer ( gestureRecognizer)
137
+ }
138
+ . disposed ( by: disposeBag)
139
+ }
137
140
}
138
141
139
142
140
143
// MARK: - UIGestureRecognizerDelegate
141
144
142
145
extension RxKeyboard : UIGestureRecognizerDelegate {
143
146
144
- public func gestureRecognizer(
145
- _ gestureRecognizer: UIGestureRecognizer ,
146
- shouldReceive touch: UITouch
147
- ) -> Bool {
148
- let point = touch. location ( in: gestureRecognizer. view)
149
- var view = gestureRecognizer. view? . hitTest ( point, with: nil )
150
- while let candidate = view {
151
- if let scrollView = candidate as? UIScrollView ,
152
- case . interactive = scrollView. keyboardDismissMode {
153
- return true
154
- }
155
- view = candidate. superview
147
+ public func gestureRecognizer( _ gestureRecognizer: UIGestureRecognizer , shouldReceive touch: UITouch ) -> Bool {
148
+ let point = touch. location ( in: gestureRecognizer. view)
149
+ var view = gestureRecognizer. view? . hitTest ( point, with: nil )
150
+
151
+ while let candidate = view {
152
+ if let scrollView = candidate as? UIScrollView ,
153
+ case . interactive = scrollView. keyboardDismissMode {
154
+ return true
155
+ }
156
+ view = candidate. superview
157
+ }
158
+
159
+ return false
156
160
}
157
- return false
158
- }
159
161
160
- public func gestureRecognizer(
161
- _ gestureRecognizer: UIGestureRecognizer ,
162
- shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
163
- ) -> Bool {
164
- return gestureRecognizer === self . panRecognizer
165
- }
162
+ public func gestureRecognizer(
163
+ _ gestureRecognizer: UIGestureRecognizer ,
164
+ shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
165
+ ) -> Bool {
166
+ gestureRecognizer === panRecognizer
167
+ }
168
+ }
169
+
170
+ private extension Notification . Name {
171
+
172
+ static let keyboardWillChangeFrame = UIResponder . keyboardWillChangeFrameNotification
173
+ static let keyboardWillHide = UIResponder . keyboardWillHideNotification
174
+ }
175
+
176
+ private extension String {
166
177
178
+ static let keyboardFrameEndKey = UIResponder . keyboardFrameEndUserInfoKey
167
179
}
180
+
168
181
#endif
169
182
0 commit comments