@@ -13,157 +13,170 @@ import RxCocoa
1313import RxSwift
1414
1515public 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 }
2020}
2121
2222/// RxKeyboard provides a reactive way of observing keyboard frame changes.
2323public class RxKeyboard : NSObject , RxKeyboardType {
2424
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+ }
137140}
138141
139142
140143// MARK: - UIGestureRecognizerDelegate
141144
142145extension RxKeyboard : UIGestureRecognizerDelegate {
143146
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
156160 }
157- return false
158- }
159161
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 {
166177
178+ static let keyboardFrameEndKey = UIResponder . keyboardFrameEndUserInfoKey
167179}
180+
168181#endif
169182
0 commit comments