J
[UIKit & RxSwift] 키보드가 화면을 가릴 때 해결 방법 본문
NotificationCenter를 이용한 Y축 이동
로그인 화면과 같이 텍스트필드가 존재하는 화면을 구현하다 보면 자주 발생하는 문제가 있다.
그건 바로 키보드가 텍스트필드나, 버튼 등을 가린다는 문제다.
문제 | 해결 |
![]() |
![]() |
나는 이 문제를 NotificationCenter와 RxSwift를 이용해서 해결했다.
키보드가 나타나는 시점에 키보드 높이에 맞춰 올려주고 키보드가 사라지는 시점에 다시 원래의 위치로 돌려주는 작업이었다.
여기서 위치가 변해야 하는 객체들은 logoImageView, emailTextField, passwordTextField, loginButton이었는데,
각각 따로 위치를 조정하기 보다는 한 번에 이들의 위치를 조정하기 위해서 loginBoxView를 만들고 그 안에 이것들을 담아서 관리했다.
//LoginViewController
override func bind() {
let keyboardWillShow = NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification) // 키보드가 나타나는 시점
let keyboardWillHide = NotificationCenter.default.rx.notification(UIResponder.keyboardWillHideNotification) // 키보드가 사라지는 시점
let input = LoginViewModel.Input(keyboardWillShow: keyboardWillShow, keyboardWillHide: keyboardWillHide)
let output = viewModel.transform(input: input)
output.keyboardWillShow.bind(with: self) { owner, notification in
owner.keyboardWillShow(notification: notification)
}.disposed(by: disposeBag)
output.keyboardWillHide.bind(with: self) { owner, notification in
owner.keyboardWillHide(notification: notification)
}.disposed(by: disposeBag)
}
// 키보드가 나타나는 시점의 위치 조정
private func keyboardWillShow(notification: Notification) {
guard let userInfo = notification.userInfo, let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
let keyboardHeight = keyboardFrame.height
UIView.animate(withDuration: 0.3) {
self.loginView.loginBoxView.snp.updateConstraints { make in
make.centerY.equalTo(self.loginView.safeAreaLayoutGuide).offset(-keyboardHeight / 2)
}
self.view.layoutIfNeeded()
}
}
// 키보드가 사라지는 시점의 위치 조정
private func keyboardWillHide(notification: Notification) {
UIView.animate(withDuration: 0.3) {
self.loginView.loginBoxView.snp.updateConstraints { make in
make.centerY.equalTo(self.loginView.safeAreaLayoutGuide).offset(-50)
}
self.view.layoutIfNeeded()
}
}
- 뷰컨트롤러의 bind 함수에서 NotificationCenter의 키보드가 나타나는 시점과, 사라지는 시점을 뷰모델의 input으로 전달.
- 이후에 뷰모델에서는 과정을 거치지 않고 바로 Output으로 전달.(UI와 관련된 내용이기 때문에 뷰컨트롤러에서 처리하기 위해서)
- Output으로 받아온 시점을 bind해서 키보드가 보여지는 시점에는 loginBoxView를 키보드 높이에 맞춰 위로 올리고, 사라지는 시점에는 기존 설정했던 위치와 동일한 위치로 돌려 놓음.
class LoginViewModel: ViewModelType {
var disposeBag: DisposeBag = DisposeBag()
struct Input {
let keyboardWillShow: Observable<Notification>
let keyboardWillHide: Observable<Notification>
}
struct Output {
let keyboardWillShow: Observable<Notification>
let keyboardWillHide: Observable<Notification>
}
func transform(input: Input) -> Output {
return Output(keyboardWillShow: input.keyboardWillShow, keyboardWillHide: input.keyboardWillHide)
}
}
(보다시피 뷰모델에서는 전달만 하고 있다.)
여기서 의문을 가질만한 점이 하나 있는데 아무래도 NotificationCenter를 사용하고 있다보니, 해당 뷰컨트롤러가 deinit 될 때 NotificationCenter도 해제해줘야 하지 않냐는 것이다.
그러나 RxSwif에서는 따로 해제해주지 않아도 dispose 되는 시점에 해제가 된다는 장점이 있다.
즉 해당 뷰컨 deinit → dispose → NotificationCenter 해제로 이어진다.