Skip to content

Commit

Permalink
Revert code indentation and #98
Browse files Browse the repository at this point in the history
  • Loading branch information
Loupehope committed Jan 4, 2021
1 parent 62e9c99 commit 89a0830
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 152 deletions.
2 changes: 1 addition & 1 deletion Example/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ SPEC CHECKSUMS:
ManualLayout: 68ac8cfa6b5f656f7a9fadec3730208b95986880
ReusableKit: e5f853ad4652e411f96b6119b2488afa12929be6
RxCocoa: 3f79328fafa3645b34600f37c31e64c73ae3a80e
RxKeyboard: 63595f98880901578c019beabc4df74b424ebe1d
RxKeyboard: aefd4787ca8be28a4470cb871141fb50e105f900
RxRelay: 8d593be109c06ea850df027351beba614b012ffb
RxSwift: c14e798c59b9f6e9a2df8fd235602e85cc044295
SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "RxKeyboard",
platforms: [
.iOS(.v10)
.iOS(.v9)
],
products: [
.library(name: "RxKeyboard", targets: ["RxKeyboard"]),
Expand Down
2 changes: 1 addition & 1 deletion RxKeyboard.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ Pod::Spec.new do |s|
s.dependency 'RxSwift', '~> 6.0'
s.dependency 'RxCocoa', '~> 6.0'

s.ios.deployment_target = '10.0'
s.ios.deployment_target = '9.0'
end
283 changes: 134 additions & 149 deletions Sources/RxKeyboard/RxKeyboard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,170 +13,155 @@ import RxCocoa
import RxSwift

public protocol RxKeyboardType {
var frame: Driver<CGRect> { get }
var visibleHeight: Driver<CGFloat> { get }
var willShowVisibleHeight: Driver<CGFloat> { get }
var isHidden: Driver<Bool> { get }
var frame: Driver<CGRect> { get }
var visibleHeight: Driver<CGFloat> { get }
var willShowVisibleHeight: Driver<CGFloat> { get }
var isHidden: Driver<Bool> { get }
}

/// RxKeyboard provides a reactive way of observing keyboard frame changes.
public class RxKeyboard: NSObject, RxKeyboardType {

// MARK: Public

/// Get a singleton instance.
public static let instance = RxKeyboard()

/// An observable keyboard frame.
public let frame: Driver<CGRect>

/// An observable visible height of keyboard. Emits keyboard height if the keyboard is visible
/// or `0` if the keyboard is not visible.
public let visibleHeight: Driver<CGFloat>

/// Same with `visibleHeight` but only emits values when keyboard is about to show. This is
/// useful when adjusting scroll view content offset.
public let willShowVisibleHeight: Driver<CGFloat>

/// An observable visibility of keyboard. Emits keyboard visibility
/// when changed keyboard show and hide.
public let isHidden: Driver<Bool>

// MARK: Private

private let disposeBag = DisposeBag()
private let panRecognizer = UIPanGestureRecognizer()

// MARK: Initializing

override init() {

let defaultFrame = CGRect(
x: .zero,
y: UIScreen.main.bounds.height,
width: UIScreen.main.bounds.width,
height: .zero
)

let frameVariable = BehaviorRelay<CGRect>(value: defaultFrame)

frame = frameVariable.asDriver().distinctUntilChanged()
visibleHeight = frame.map { UIScreen.main.bounds.height - $0.origin.y }

willShowVisibleHeight = visibleHeight
.scan((visibleHeight: .zero, isShowing: false)) { lastState, newVisibleHeight in
(visibleHeight: newVisibleHeight, isShowing: lastState.visibleHeight <= .zero && newVisibleHeight > .zero)
}
.filter { $0.isShowing }
.map { $0.visibleHeight }

isHidden = visibleHeight
.map { $0 <= .ulpOfOne }
.distinctUntilChanged()

super.init()

// keyboard will change frame
let willChangeFrame = NotificationCenter.default.rx.notification(.keyboardWillChangeFrame)
.map { notification -> CGRect in
let rectValue = notification.userInfo?[String.keyboardFrameEndKey] as? NSValue
return rectValue?.cgRectValue ?? defaultFrame
}
.map { frame -> CGRect in
if frame.origin.y < .zero { // if went to wrong frame
var newFrame = frame
newFrame.origin.y = UIScreen.main.bounds.height - newFrame.height
return newFrame
}
return frame
}

// keyboard will hide
let willHide = NotificationCenter.default.rx.notification(.keyboardWillHide)
.map { notification -> CGRect in
let rectValue = notification.userInfo?[String.keyboardFrameEndKey] as? NSValue
return rectValue?.cgRectValue ?? defaultFrame
}
.map { frame -> CGRect in
if frame.origin.y < .zero { // if went to wrong frame
var newFrame = frame
newFrame.origin.y = UIScreen.main.bounds.height
return newFrame
}
return frame
}

// pan gesture
let didPan = panRecognizer.rx.event
.withLatestFrom(frameVariable.asObservable()) { ($0, $1) }
.flatMap { (gestureRecognizer, frame) -> Observable<CGRect> in
guard case .changed = gestureRecognizer.state,
let window = UIApplication.shared.windows.first,
frame.origin.y < UIScreen.main.bounds.height else {
return .empty()
}

let origin = gestureRecognizer.location(in: window)
var newFrame = frame
newFrame.origin.y = max(origin.y, UIScreen.main.bounds.height - frame.height)
return .just(newFrame)
}

// merge into single sequence
Observable.merge(didPan, willChangeFrame, willHide)
.bind(to: frameVariable)
.disposed(by: disposeBag)

// gesture recognizer
panRecognizer.delegate = self

UIApplication.rx.didFinishLaunching // when RxKeyboard is initialized before UIApplication.window is created
.withUnretained(panRecognizer)
.subscribe { gestureRecognizer, _ in
UIApplication.shared.windows.first?.addGestureRecognizer(gestureRecognizer)
}
.disposed(by: disposeBag)
}
// MARK: Public

/// Get a singleton instance.
public static let instance = RxKeyboard()

/// An observable keyboard frame.
public let frame: Driver<CGRect>

/// An observable visible height of keyboard. Emits keyboard height if the keyboard is visible
/// or `0` if the keyboard is not visible.
public let visibleHeight: Driver<CGFloat>

/// Same with `visibleHeight` but only emits values when keyboard is about to show. This is
/// useful when adjusting scroll view content offset.
public let willShowVisibleHeight: Driver<CGFloat>

/// An observable visibility of keyboard. Emits keyboard visibility
/// when changed keyboard show and hide.
public let isHidden: Driver<Bool>

// MARK: Private

private let disposeBag = DisposeBag()
private let panRecognizer = UIPanGestureRecognizer()

// MARK: Initializing

override init() {

let keyboardWillChangeFrame = UIResponder.keyboardWillChangeFrameNotification
let keyboardWillHide = UIResponder.keyboardWillHideNotification
let keyboardFrameEndKey = UIResponder.keyboardFrameEndUserInfoKey

let defaultFrame = CGRect(
x: 0,
y: UIScreen.main.bounds.height,
width: UIScreen.main.bounds.width,
height: 0
)
let frameVariable = BehaviorRelay<CGRect>(value: defaultFrame)
self.frame = frameVariable.asDriver().distinctUntilChanged()
self.visibleHeight = self.frame.map { UIScreen.main.bounds.height - $0.origin.y }
self.willShowVisibleHeight = self.visibleHeight
.scan((visibleHeight: 0, isShowing: false)) { lastState, newVisibleHeight in
return (visibleHeight: newVisibleHeight, isShowing: lastState.visibleHeight == 0 && newVisibleHeight > 0)
}
.filter { state in state.isShowing }
.map { state in state.visibleHeight }
self.isHidden = self.visibleHeight.map({ $0 == 0.0 }).distinctUntilChanged()
super.init()

// keyboard will change frame
let willChangeFrame = NotificationCenter.default.rx.notification(keyboardWillChangeFrame)
.map { notification -> CGRect in
let rectValue = notification.userInfo?[keyboardFrameEndKey] as? NSValue
return rectValue?.cgRectValue ?? defaultFrame
}
.map { frame -> CGRect in
if frame.origin.y < 0 { // if went to wrong frame
var newFrame = frame
newFrame.origin.y = UIScreen.main.bounds.height - newFrame.height
return newFrame
}
return frame
}

// keyboard will hide
let willHide = NotificationCenter.default.rx.notification(keyboardWillHide)
.map { notification -> CGRect in
let rectValue = notification.userInfo?[keyboardFrameEndKey] as? NSValue
return rectValue?.cgRectValue ?? defaultFrame
}
.map { frame -> CGRect in
if frame.origin.y < 0 { // if went to wrong frame
var newFrame = frame
newFrame.origin.y = UIScreen.main.bounds.height
return newFrame
}
return frame
}

// pan gesture
let didPan = self.panRecognizer.rx.event
.withLatestFrom(frameVariable.asObservable()) { ($0, $1) }
.flatMap { (gestureRecognizer, frame) -> Observable<CGRect> in
guard case .changed = gestureRecognizer.state,
let window = UIApplication.shared.windows.first,
frame.origin.y < UIScreen.main.bounds.height
else { return .empty() }
let origin = gestureRecognizer.location(in: window)
var newFrame = frame
newFrame.origin.y = max(origin.y, UIScreen.main.bounds.height - frame.height)
return .just(newFrame)
}

// merge into single sequence
Observable.of(didPan, willChangeFrame, willHide).merge()
.bind(to: frameVariable)
.disposed(by: self.disposeBag)

// gesture recognizer
self.panRecognizer.delegate = self

UIApplication.rx.didFinishLaunching // when RxKeyboard is initialized before UIApplication.window is created
.subscribe(onNext: { _ in
UIApplication.shared.windows.first?.addGestureRecognizer(self.panRecognizer)
})
.disposed(by: self.disposeBag)
}

}


// MARK: - UIGestureRecognizerDelegate

extension RxKeyboard: UIGestureRecognizerDelegate {

public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
let point = touch.location(in: gestureRecognizer.view)
var view = gestureRecognizer.view?.hitTest(point, with: nil)

while let candidate = view {
if let scrollView = candidate as? UIScrollView,
case .interactive = scrollView.keyboardDismissMode {
return true
}
view = candidate.superview
}

return false
public func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldReceive touch: UITouch
) -> Bool {
let point = touch.location(in: gestureRecognizer.view)
var view = gestureRecognizer.view?.hitTest(point, with: nil)
while let candidate = view {
if let scrollView = candidate as? UIScrollView,
case .interactive = scrollView.keyboardDismissMode {
return true
}
view = candidate.superview
}
return false
}

public func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
) -> Bool {
return gestureRecognizer === panRecognizer
}
}

private extension Notification.Name {

static let keyboardWillChangeFrame = UIResponder.keyboardWillChangeFrameNotification
static let keyboardWillHide = UIResponder.keyboardWillHideNotification
}

private extension String {
public func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
) -> Bool {
return gestureRecognizer === self.panRecognizer
}

static let keyboardFrameEndKey = UIResponder.keyboardFrameEndUserInfoKey
}

#endif

0 comments on commit 89a0830

Please sign in to comment.