Skip to content

Instantly share code, notes, and snippets.

@gamako
Last active October 4, 2019 09:13
Show Gist options
  • Save gamako/6f7df33badaf279cc313cb934728a792 to your computer and use it in GitHub Desktop.
Save gamako/6f7df33badaf279cc313cb934728a792 to your computer and use it in GitHub Desktop.
UITextViewやUITextFieldがキーボードで隠れないようにスクロールする処理を、RxSwift, RxCocoaを使って1メソッドでセットできるようにしました
//
// 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