swift

view resizing (ft. keyboard notification)

kimyounggyun 2022. 8. 4. 21:12

keyboard가 화면에 나타나거나 사라질 때 등록되는 notification을 이용해 키보드가 textview를 가리는 문제를 해결해보자!

Keyboard Notifications

When the system shows or hides the keyboard, it posts several keyboard notifications. These notifications contain information about the keyboard, including its size, which you can use for calculations that involve repositioning or resizing views. Registering for these notifications is the only way to get some types of information about the keyboard.

시스템은 키보드 상태를 아래 4가지 Notification.Name으로 notification center에 등록한다.

  • keyboardWillShowNotification
  • keyboardDidShowNotification
  • keyboardWillHideNotification
  • keyboardDidHideNotification

시스템은 아래의 정보를 notification center의 userInfo에 저장한다.

  • keyboardAnimationCurveUserInfoKey
  • keyboardAnimationDurationUserInfoKey
  • keyboardFrameBeginUserInfoKey
  • keyboardFrameEndUserInfoKey
  • keyboardIsLocalUserInfoKey

keyboardFrameBeginUserInfoKeykeyboardFrameEndUserInfoKey에 키보드의 위치 및 사이즈 정보가 담겨있다.

NotificationCenter + Rx

notification 메서드의 반환형은 Observable<Notification>으로 등록한 notification의 observable sequence를 반환한다.

notification 메서드는 Notification name을 매개 변수로 받아 notification center에 addObserver을 해주고 observable이 dispose 될 때 자동으로 removeObserver을 해주기 때문에 편리하다.

 public func notification(_ name: Notification.Name?, object: AnyObject? = nil) 
 -> Observable<Notification> {
    return Observable.create { [weak object] observer in
        let nsObserver = self.base.addObserver(forName: name,
                                                object: object, 
                                                queue: nil) { notification in
            observer.on(.next(notification))
        }
        return Disposables.create {
            self.base.removeObserver(nsObserver)
        }
    }
}

구현

키보드가 나타나거나 사라질 때 등록되는 notification을 구독하여 키보드 사이즈 정보를 얻어 keyboardFrameBeginUserInfoKey와 keyboardFrameEndUserInfoKey에 있는 키보드 사이즈 정보를 이용한다.

키보드가 나타날 때 textview의 아래 여백을 키보드 높이만큼 만들어주고, 사라질 때 아래 여백을 0으로 만들어주면 된다.

let willShowObservable =
    NotificationCenter.default.rx
    .notification(UIResponder.keyboardWillShowNotification)
    .map {
        ($0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey]
        as? NSValue)?.cgRectValue.height ?? 0
    }
        
let willHideObservable =
    NotificationCenter.default.rx
    .notification(UIResponder.keyboardWillHideNotification)
    .map { notification -> CGFloat in 0 }
    
Observable
    .merge(willShowObservable, willHideObservable)
    .map { [unowned self] height -> UIEdgeInsets in
        var inset = self.textView.contentInset
        inset.bottom = height
        return inset
    }
    .subscribe(onNext: { [weak self] inset in
        UIView.animate(withDuration: 0.4) {
        self?.textView.contentInset = inset
        }
    })
    .disposed(by: disposeBag)