Keyboard Management Notification Observers

commit => refactor: CustomTextField init argument receiving its height viewDidLoad() should be always clean!

  • NotificationCenter.default.addObserver
  • notification
  • Shifting view

keyboard

keyboard가 나올 때마다 반응할 수 있는 observerNotificationCenter.default에서 self에 부여한다. 그리고 observer가 keyboard를 캐치하면 실행할 handle 함수를 만든다. keyboardWillShowNotification : 키보드가 나오면 호출

	fileprivate func setupNotificationObservers() {
        NotificationCenter.default.addObserver(self, selector: #selector(handlekeyboardShow), name: UIResponder.keyboardWillShowNotification, object: nil)
    }
    
    @objc fileprivate func handlekeyboardShow(notification: Notification) {
        print("keyboard will show")
      	print(notification)
    }

handle함수는 항상 @objc

addObserver

Adds an entry to the notification center’s dispatch table 1 with an observer and a notification selector, and an optional notification name and sender.

Declaration

func addObserver(_ observer: Any, selector aSelector: Selector, name aName: NSNotification.Name?, object anObject: Any?)

Parameters

// observer
Object registering as an observer.

// aSelector
Selector that specifies the message the receiver sends observer to notify it of the notification posting. The method specified by aSelector must have one and only one argument (an instance of NSNotification).

// aName
The name of the notification for which to register the observer; that is, only notifications with this name are delivered to the observer.

If you pass nil, the notification center doesn’t use a notification’s name to decide whether to deliver it to the observer.

// anObject
The object whose notifications the observer wants to receive; that is, only notifications sent by this sender are delivered to the observer.

If you pass nil, the notification center doesn’t use a notification’s sender to decide whether to deliver it to the observer.

UIResponder

An abstract interface for responding to and handling events.

What’s in notification argument?

print(notification) in handleKeyboardShow

print(notification.userInfo)

여기서 UIKeyboardCenterEndUserInfoKey만 사용하기를 원한다.

guard let value = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardFrame = value.cgRectValue
print(keyboardFrame)

keyboardFrame은 CGRect로 keyboard의 좌표와 너비를 반환

shifting view

정확하게 움직이기 위해서는 register button의 하단 부터 screen bottom까지의 길이를 알아야한다.

  1. Keyboard의 정확한 좌표 파악하기 (by notification.userInfo?)
  2. reigsterButton의 정확한 좌표 파악하기
  3. 움직여야할 1과2의 차이 계산
  4. transform으로 이동

register button이 stackView에 속해있으니 stackView에서 길이를 알아오는게 쉽다.

setupLayout()에 있는 stackView에 접근하기 위해서는 stackView를 setupLayout() 함수 안에서 꺼내서 RegistrationController class 내 변수로 선언해준다. 여기서 컴파일 오류가 발생하는데 selectPhotoButton과 같은 멤버들에게 접근할 수 없다.

따라서 let을 lazy var로 바꿔줘야한다.

reason : stackView가 lazy하게 생성되는데, 배열 안 요소들이 들어간 후에 stackView가 생성된다.

lazy var stackView = UIStackView(arrangedSubviews: [
        selectPhotoButton,
        fullNameTextField,
        emailTextField,
        passwordTextField,
        registerButton
        ])
@objc fileprivate func handlekeyboardShow(notification: Notification) {
        // how to figure out how tall the keyboard actually is
        guard let value = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
        let keyboardFrame = value.cgRectValue
        
        // Let's try to figure out how tall the gap is from the register button to the bottom of the screen
        let bottomSpace = view.frame.height - stackView.frame.origin.y - stackView.frame.height
        
        let difference = keyboardFrame.height - bottomSpace
        self.view.transform = CGAffineTransform(translationX: 0, y: -difference - 8)
    }

difference 값을 계산한 후에 transform으로 이동시켜준다. 여기서 y축 이동을 difference로 주면 위로 이동하기 때문에 negative 값으로 주고 padding 값으로 8을 준다.

항상 이동은 transform을 생각하자

TapGesture .identity

이제 키보드가 아닌 곳을 누르면 다시 키보드를 없애는 작업을 해보자.

		fileprivate func setupTapGesture() {
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTapDismiss)))
    }
    
    @objc fileprivate func handleTapDismiss() {
        view.endEditing(true)
      	self.view.transform = .identity
    }

먼저 터치를 받기 위해서 TapGesture를 만든다.

항상 view에 addGestureRecognizerGestureRecognizer을 추가! target에는 self, action 에는 #selector를 넣는다.

.identity는 처음 상태로 만들어준다.

using UIView.animate for smooth move

		@objc fileprivate func handleTapDismiss() {
        self.view.endEditing(true) // dismisses keyboard
        UIView.animate(withDuration: 0.5) {
            self.view.transform = .identity
        }
    }

advanced animate default value

				UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
            self.view.transform = .identity
        })

유용한 기본 애니메이션 세팅이니 기억해두자

New Observer for dismissing Keyboard

actually we need to change the code to dismiss keyboard. Because handleTapDismiss() is func for ending editing not transforming the view. So we need to make a new observer for transforming the view.

		fileprivate func setupNotificationObservers() {
        NotificationCenter.default.addObserver(self, selector: #selector(handlekeyboardShow), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardHide), name: UIResponder.keyboardWillHideNotification, object: nil)
    }
    
    @objc fileprivate func handleKeyboardHide() {
        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
            self.view.transform = .identity
        })
    }

핵심 동작 과정 UITapGesture - endEditing(true) - new observer’s selector - dismissing with animation

viewWillDisappear

		override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        NotificationCenter.default.removeObserver(self) // you'll have a retain cycle
    }

메모리를 다시 요청하기 때문에 observer를 제거한다.

What is a retain cycle?

  1. 컴퓨터 과학에서 디스패치 테이블 (Dispatch table)은 메서드들을 가리키는 포인터들이나 메소드들의 테이블이다. 이런 테이블을 사용하는 것은 객체 지향 프로그래밍에서 늦은 바인딩을 구현하는 기본적인 기술이다. 

태그:

카테고리:

업데이트:

댓글남기기