@@ -13,170 +13,155 @@ 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 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
- }
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 // when RxKeyboard is initialized before UIApplication.window is created
129
+ . subscribe ( onNext: { _ in
130
+ UIApplication . shared. windows. first? . addGestureRecognizer ( self . panRecognizer)
131
+ } )
132
+ . disposed ( by: self . disposeBag)
133
+ }
134
+
140
135
}
141
136
142
137
143
138
// MARK: - UIGestureRecognizerDelegate
144
139
145
140
extension RxKeyboard : UIGestureRecognizerDelegate {
146
141
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
142
+ public func gestureRecognizer(
143
+ _ gestureRecognizer: UIGestureRecognizer ,
144
+ shouldReceive touch: UITouch
145
+ ) -> Bool {
146
+ let point = touch. location ( in: gestureRecognizer. view)
147
+ var view = gestureRecognizer. view? . hitTest ( point, with: nil )
148
+ while let candidate = view {
149
+ if let scrollView = candidate as? UIScrollView ,
150
+ case . interactive = scrollView. keyboardDismissMode {
151
+ return true
152
+ }
153
+ view = candidate. superview
160
154
}
155
+ return false
156
+ }
161
157
162
- public func gestureRecognizer(
163
- _ gestureRecognizer: UIGestureRecognizer ,
164
- shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
165
- ) -> Bool {
166
- return 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 {
158
+ public func gestureRecognizer(
159
+ _ gestureRecognizer: UIGestureRecognizer ,
160
+ shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
161
+ ) -> Bool {
162
+ return gestureRecognizer === self . panRecognizer
163
+ }
177
164
178
- static let keyboardFrameEndKey = UIResponder . keyboardFrameEndUserInfoKey
179
165
}
180
-
181
166
#endif
182
167
0 commit comments