관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 1. 키보드 처리 - 키보드가 올라갈 때 뷰를 올리는 UI 본문

UI 컴포넌트 (swift)

[iOS - swift] 1. 키보드 처리 - 키보드가 올라갈 때 뷰를 올리는 UI

jake-kim 2023. 7. 10. 02:23

1. 키보드 처리 - 키보드가 올라갈 때 뷰를 올리는 UI

2. 키보드 처리 - 키보드가 올라갈 때 스크롤 뷰를 올리는 UI

키보드가 올라갈 때 뷰를 올리는 UI

구현한 화면

구현 아이디어

  • keyboard를 감싸는 투명 UIView, keyboard 바로 위쪽을 감싸는 투명 UIView를 준비
    • 투명 UIView는 hitTest를 사용하여 pass through하게 구현 (PassThroughView 구현은 이전 포스팅 글 참고)
  • 키보드 바로 위쪽을 감싸는 투명 UIView위에 UITextView, UIButton을 두어서 구현

구현

  • 사용한 라이브러리
pod 'SnapKit'
pod 'Then'
pod 'RxSwift'
pod 'RxCocoa'
pod 'RxGesture'
  • 상속보다는 유지보수에 용이한 프로토콜 형태인 KeyboardWrappable 구현
    • 해당 프로토콜을 채택하는 UIViewController에서 사용할 수 있도록 구현
import UIKit
import SnapKit
import RxSwift

protocol KeyboardWrapperable {
    var keyboardWrapperView: PassThroughView { get }
    var keyboardSafeAreaView: PassThroughView { get }
    var disposeBag: DisposeBag { get }
    
    func setupKeybaordWrapper()
}
  • 사용하는쪽에서 setupKeyboardWrapper()를 한번이라도 호출해야하므로, 이를 개발자에게 명시해주기 위해서 flag를 하나 구현
private struct AssociatedKeys {
    static var isEnabled = "isEnabled"
}

extension KeyboardWrapperable where Self: UIViewController {
    private var isEnabled: Bool {
        get {
            (objc_getAssociatedObject(self, &AssociatedKeys.isEnabled) as? Bool) ?? false
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.isEnabled, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}
  • extension에 이어서 구현하면, setupKeyboardWrapper()에서 isEnabled 플래그를 변경한 후 필요한 레이아웃과 키보드의 높이가 변경될때마다 autolayout으로 키보드 wrapperView를 조정하는 코드 호출
    func setupKeybaordWrapper() {
        guard !isEnabled else { return }
        isEnabled.toggle()

        setupLayout()
        observeKeyboardHeight()
    }
    
    private func setupLayout() {
        // TODO
    }

    private func observeKeyboardHeight() {
        // TODO
    }
  • setupLayout() 구현
    • wrapperView를 view에 넣어주고 autolayout 설정
view.addSubview(keyboardWrapperView)
view.addSubview(keyboardSafeAreaView)

keyboardWrapperView.snp.makeConstraints {
    $0.leading.trailing.bottom.equalToSuperview()
    $0.height.equalTo(0).priority(.high)
}

keyboardSafeAreaView.snp.makeConstraints {
    $0.top.leading.trailing.equalToSuperview()
    $0.bottom.equalTo(keyboardWrapperView.snp.top)
}
  • observeKeyboardHeight() 구현
    • 키보드의 높이가 변경될때마다 wrapperView의 height를 변경
    • 아래 코드에서 transition 애니메이션을 넣는데, options에 init(rawValue: 458752)값을 넣는데 이는 (.transitionCrossDissolve, .curveEaseInOut) 두개의 애니메이션을 OR로 넣은 것이므로 키보드의 애니메이션과 동일한 옵션
private func observeKeyboardHeight() {
    Observable<(Bool, Notification)>
        .merge(
            NotificationCenter.default.rx.notification(UIResponder.keyboardWillShowNotification)
                .map { notification in (true, notification) },
            NotificationCenter.default.rx.notification(UIResponder.keyboardWillHideNotification)
                .map { notification in (false, notification) }
        )
        .bind(with: self) { ss, tuple in
            let (isKeyboardUp, notification) = tuple
            let uesrInfo = notification.userInfo
            guard let endFrame = uesrInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
            
            let endFrameMinY = endFrame.origin.y
            let shownKeyboardHeight = isKeyboardUp ? endFrame.height : 0
            
            ss.keyboardWrapperView.snp.updateConstraints {
                $0.height.equalTo(shownKeyboardHeight).priority(.high)
            }
            UIView.transition(
                with: ss.keyboardWrapperView,
                duration: 0.25,
                options: .init(rawValue: 458752),
                animations: ss.view.layoutIfNeeded
            )
        }
        .disposed(by: disposeBag)
}

(KeyboardWrappable 모두 구현 완료)

사용하는쪽

  • ViewController에서 KeyboardWrappable을 채택하고 setupKeyboardWrapper()를 호출
  • keyboardSafeAreaView에다가 원하는 뷰를 삽입한 후 autolayout 설정만 하면 완료
class ViewController: UIViewController, KeyboardWrapperable {
    var keyboardWrapperView = PassThroughView()
    var keyboardSafeAreaView = PassThroughView()
    
    ...
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupKeybaordWrapper()
        setupUI()
    }
    
    private func setupUI() {        
        keyboardSafeAreaView.addSubview(textView)
        keyboardSafeAreaView.addSubview(button)
        
        textView.snp.makeConstraints {
            $0.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(30)
            $0.leading.trailing.equalToSuperview().inset(30)
            $0.height.equalTo(Metric.textViewHeight).priority(.medium)
            $0.bottom.lessThanOrEqualTo(button.snp.top).offset(-Metric.spacing)
        }
        button.snp.makeConstraints {
            $0.leading.trailing.bottom.equalToSuperview()
            $0.height.equalTo(80)
        }
    }
}

(완료)

구현한 화면

* 전체 코드

https://github.com/JK0369/ExKeyboardSafeAreaView

 

Comments