Last active
October 4, 2019 09:13
-
-
Save gamako/6f7df33badaf279cc313cb934728a792 to your computer and use it in GitHub Desktop.
UITextViewやUITextFieldがキーボードで隠れないようにスクロールする処理を、RxSwift, RxCocoaを使って1メソッドでセットできるようにしました
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UIViewController+ScrollWhenShowKeyboard.swift | |
// | |
// | |
import UIKit | |
import RxSwift | |
import RxCocoa | |
extension UIViewController { | |
// キーボードが現れたときに、テキストフィールドをスクロールする | |
func scrollWhenShowKeyboard() { | |
var disposeBag : DisposeBag? = DisposeBag() | |
// この関数内で完結するように、dealloc時にdisposeしてくれる仕組みを用意する | |
rx_deallocating.subscribeNext { disposeBag = nil }.addDisposableTo(disposeBag!) | |
// viewAppearの間だけUIKeyboardが発行するNotificationを受け取るObserbaleを作る | |
viewAppearedObservable().flatMapLatest | |
{ (b) -> Observable<(Bool, NSNotification)> in | |
if b { | |
// notificationは、(true=表示/false=非表示, NSNotification)のタプルで次のObservableに渡す | |
return Observable.of( | |
NSNotificationCenter.defaultCenter().rx_notification(UIKeyboardWillShowNotification).map { (true, $0)}, | |
NSNotificationCenter.defaultCenter().rx_notification(UIKeyboardWillHideNotification).map { (false, $0)} | |
).merge() | |
} else { | |
return Observable<(Bool, NSNotification)>.empty() | |
} | |
} | |
.subscribeNext { [weak self] (a: (isShow:Bool, notification:NSNotification)) in | |
// notificationに対して、適切にスクロールする処理 | |
if a.isShow { | |
self?.scrollTextFieldWhehKeybordShown(a.notification) | |
} else { | |
self?.restoreScrollTextField(a.notification) | |
} | |
}.addDisposableTo(disposeBag!) | |
} | |
// キーボードのframeとUITextFieldの位置を比較して、いい感じにスクロールする処理 | |
// 表示するターゲットは現在のFirstResponder | |
// スクロールするUIScrollViewは、その親をたどって見つけている | |
// | |
// 参考 : http://qiita.com/ysk_1031/items/3adb1c1bf5678e7e6f98 | |
private func scrollTextFieldWhehKeybordShown(notification: NSNotification) { | |
guard let textField = self.view.currentFirstResponder() as? UIView, | |
scrollView = textField.findSuperView(UIScrollView.self), | |
userInfo = notification.userInfo, | |
keyboardFrame = userInfo[UIKeyboardFrameEndUserInfoKey]?.CGRectValue, | |
animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey]?.doubleValue | |
else { return } | |
scrollView.contentInset = UIEdgeInsetsZero | |
scrollView.scrollIndicatorInsets = UIEdgeInsetsZero | |
let convertedKeyboardFrame = scrollView.convertRect(keyboardFrame, fromView: nil) | |
let convertedTextFieldFrame = textField.convertRect(textField.frame, toView: scrollView) | |
let offsetY = CGRectGetMaxY(convertedTextFieldFrame) - CGRectGetMinY(convertedKeyboardFrame) | |
if offsetY > 0 { | |
UIView.animateWithDuration(animationDuration) { | |
let contentInsets = UIEdgeInsetsMake(0, 0, offsetY, 0) | |
scrollView.contentInset = contentInsets | |
scrollView.scrollIndicatorInsets = contentInsets | |
scrollView.contentOffset = CGPointMake(0, offsetY) | |
} | |
} | |
} | |
// スクロールを元に戻す | |
private func restoreScrollTextField(notification: NSNotification) { | |
guard let textField = self.view.currentFirstResponder() as? UIView, | |
let scrollView = textField.findSuperView(UIScrollView.self) | |
else { return } | |
scrollView.contentInset = UIEdgeInsetsZero | |
scrollView.scrollIndicatorInsets = UIEdgeInsetsZero | |
} | |
} | |
extension UIView { | |
// 親ビューをたどってFirstResponderを探す | |
func currentFirstResponder() -> UIResponder? { | |
if self.isFirstResponder() { | |
return self | |
} | |
for view in self.subviews { | |
if let responder = view.currentFirstResponder() { | |
return responder | |
} | |
} | |
return nil | |
} | |
// 現在入力中のUITextFieldやUITextViewのカーソル位置に文字を挿入する | |
func insertTextToCurrentField(text : String) { | |
guard let field = self.currentFirstResponder() as? UIKeyInput else { return } | |
field.insertText(text) | |
} | |
// 任意の型の親ビューを探す | |
// 親をたどってScrollViewを探す場合などに使用する | |
func findSuperView<T>(ofType: T.Type) -> T? { | |
if let s = self.superview { | |
switch s { | |
case let s as T: | |
return s | |
default: | |
return s.findSuperView(ofType) | |
} | |
} | |
return nil | |
} | |
} | |
extension UIViewController { | |
// ViewがdidAppearでtrue, didDisappearでfalseになるobservable | |
func viewAppearedObservable() -> Observable<Bool> { | |
return Observable.of( | |
viewDidAppearTrigger.map { true } , | |
viewDidDisappearTrigger.map { false } | |
) | |
.merge() | |
} | |
} | |
// http://blog.sgr-ksmt.org/2016/04/23/viewcontroller_trigger/ | |
extension UIViewController { | |
private func trigger(selector: Selector) -> Observable<Void> { | |
return rx_sentMessage(selector).map { _ in () }.shareReplay(1) | |
} | |
var viewWillAppearTrigger: Observable<Void> { | |
return trigger(#selector(self.viewWillAppear(_:))) | |
} | |
var viewDidAppearTrigger: Observable<Void> { | |
return trigger(#selector(self.viewDidAppear(_:))) | |
} | |
var viewWillDisappearTrigger: Observable<Void> { | |
return trigger(#selector(self.viewWillDisappear(_:))) | |
} | |
var viewDidDisappearTrigger: Observable<Void> { | |
return trigger(#selector(self.viewDidDisappear(_:))) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment