https://www.youtube.com/watch?v=O4tP7egAV1I

  1. 키보드 존재 감지
  2. 키보드가 탭 된 element의 위치를 결정
  3. 키보드 height 알아내기
  4. 이 두 정보를 활용해서 element가 hidden 인지 확인

NSNotification을 활용해서 keyboardFrame을 알아낸다.


Responder Chain

이벤트 처리를 시도했는데 처리하지 못하면 상위로 전달

image.png

import Combine
import UIKit

// 현재 응답받는 UI를 알아내기 위해서 사용됩니다.
extension UIResponder {
  private struct Static {
    static weak var responder: UIResponder?
  }
  
  static fileprivate var currentResponder: UIResponder? {
    Static.responder = nil
    UIApplication.shared.sendAction(#selector(UIResponder._trap), to: nil, from: nil, for: nil)
    return Static.responder
  }
  
  @objc private func _trap() {
    Static.responder = self
  }
}

/// textBox(textField, textView)의 위치를 감지하고 필요시 transformView의 y값을 이동시킵니다.
protocol KeyboardEventable: AnyObject {
  var subscriptions: Set<AnyCancellable> { get set }
  var transformView: UIView { get }
  
  func bindKeyboard()
}

extension KeyboardEventable where Self: UIViewController {
  func bindKeyboard() {
    NotificationCenter.default
      .publisher(for: UIResponder.keyboardWillShowNotification)
      .compactMap { [weak self] notification in
        guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue,
              let currentTextInputView = UIResponder.currentResponder as? (UITextInputTraits & UIView)
        else { return nil }
        
        guard let convertedTextFieldFrame = self?.transformView.convert(
          currentTextInputView.frame,
          from: currentTextInputView.superview
        ) else {
          return nil
        }
        
        let keyboardTopY = keyboardFrame.cgRectValue.origin.y
        let textBoxBottomY = convertedTextFieldFrame.origin.y + convertedTextFieldFrame.size.height
        
        if textBoxBottomY > keyboardTopY {
          let newFrameY = -keyboardFrame.cgRectValue.size.height
          return newFrameY
        }
        
        return nil
      }
      .sink { [weak self] newFrameY in
        self?.transformView.frame.origin.y = newFrameY
      }
      .store(in: &self.subscriptions)
    
    NotificationCenter.default
      .publisher(for: UIResponder.keyboardWillHideNotification)
      .sink { [weak self] _ in
        self?.transformView.frame.origin.y = .zero
      }
      .store(in: &self.subscriptions)
  }
}

이름 없는 노트북.jpeg