
iOS의 이벤트 처리

우리가 사용하는 UIKit에 있는 클래스들은 UIResponder를 채택한 경우가 굉장히 많다.
UIWindow, UIApplication, UIViewController, UIView 정말 많은데 그러면 UIResponder는 어떤 역할을 할까?

위에는 공식 문서의 개요 첫 번째 문장인데, UIResponder는 UIKit 앱 이벤트 처리 backbone을 구성한다고 한다. 그럼 여기서 backbone은 무엇을 뜻하는 걸까??
인터넷에서 검색해보니, 정보를 교환할 수 있는 망과 같은 말이 있다. 사용자로부터 이벤트가 발생하면 UIKit은 앱의 Responder객체에 이벤트 발생을 전달한다고 한다.
이벤트 발생을 전달한다는 의미가 정보를 교환할 수 있는 망이라는 말과 비슷한 것 같기도 해서 backbone 이라는 단어를 사용한 이유를 알 거 같기도 하다.
특정 타입의 이벤트(터치, 드래그와 같은 이벤트)를 처리하려면 리스폰더 객체는 해당 메소드를 override 해야 한다고 하는데, 그러면 어떤 메소드가 존재할까?
UIResponder 헤더 파일을 확인하던 중에 매우 익숙한 메소드가 하나 있었다. 아래는 UIResponder의 헤더 파일이다.

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
내가 처음 이 메소드를 사용했던 이유는 시스템 키보드를 화면에서 내리기 위해서 사용했었다. 시스템 키보드는 텍스트필드를 통해서 나타나며, 텍스트필드 프레임외에 다른 프레임을 터치하면 위와 같은 touchesBegan 메소드가 호출된다. 그렇게 아래와 같은 코드를 통해서 사용했던 시스템 키보드를 없앨 수 있었다.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
textField.resignFirstResponder()
}
그러면 정확히 touchesBegan 메소드가 했었던 역할은 뭘까?

Tells this object that one or more new touches occurred in a view or window.
해당 오브젝트에게 View또는 Window에서 하나 또는 하나 이상의 터치가 발생했다는 것을 알려줍니다.
여기서 해당 오브젝트라는 말은 touchesBegan 을 오버라이드한 Responder객체가 될 것이다. 보통 ViewController에서 많이 호출하니까 보통의 경우에는 VIewController가 될 것이다.
그러면 textField.resignFirstResponder() 메소드는 어떤 뜻이 있는걸까? FirstResponder를 보면 Responder와 어떤 관련이 있어 보인다.
그리고 화면을 터치했는데, 어떻게 ViewController의 touchesBegan 메소드가 실행될 수 있는 걸까?
우리는 위의 해답을 얻기 위해서는 Responder Chain에 대해서 알아 볼 필요가 있다.
Responder는 체인을 이루고 있다. 체인을 이루고 있다는 것은 무슨 의미일까? Responder객체는 하나의 체인처럼 연결되어 있다는 것이다. 체인처럼 연결되어 있기 때문에 어떤 이벤트가 발생했을 때 그 이벤트는 다른 Responder객체로 전달될 수 있다.

위의 사진과 같이 이벤트를 계속해서 전달한다. 이벤트를 전달하는 과정은 다음과 같다.
그러면 touchesBegan 메소드는 터치 이벤트니까 해당 이벤트도 체인처럼 전달될까? 한 번 직접 확인해보자.
Window의 경우는 touchesBegan 메소드 오버라이딩을 위해 다음과 같이 커스텀한다.
final class CustomWindow: UIWindow {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touchesBegan - Window")
super.touchesBegan(touches, with: event)
}
override init(windowScene: UIWindowScene) {
super.init(windowScene: windowScene)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = CustomWindow(windowScene: windowScene)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
}
}
가장 최하단 View를 클릭한다고 가정했을 때의 결과는 다음과 같다.

결과를 확인했을 때, 정말로 이벤트가 체인처럼 연결된다는 사실을 확인할 수 있다!
그렇다면 우리는 아까봤던 textField.resignFirstResponder() 가 뭔지 조금 짐작이 올 수도 있을 거 같다.
FirstResponder라는 말은 Responder Chain에서 가장 먼저 반응하는 Responder라는 뜻으로 받아들일 수 있을 거 같다.
그러면 이쯤에서 resignFirstResponder() 메소드의 의미를 공식문서를 통해 확인해보자!

해당 객체에게 Window의 첫 번째 Responder로서의 상태를 포기하라고 요청이 왔음을 알리는 메소드
만약 텍스트필드로 인해서 시스템 키보드가 올라와 있는 상태면 텍스트필드가 Window에서의 First Responder일 것이다. 여기서 시스템 키보드를 내리고 싶다면 더 이상 텍스트필드가 First Responder 상태가 아니어야 한다. 그렇기 때문에 textField.resignFirstResponder() 를 통해서 “너는 이제 First Responder가 되기를 포기해라” 라는 메세지를 전하는 것과 같다.
그렇다면 반대로 시스템 키보드를 올리고 싶다면 어떻게 하면 될까? textField.becomeFirstResponder() 를 하면 될 것이다. “너가 이제 First Responder가 되라” 라는 메세지를 전달하는 것과 같기에 이벤트를 통해서 시스템 키보드가 나타나게 된다.
지금까지 Responder에 대해서 확인해봤는데, 아무래도 이벤트의 근간이 되는 객체이다 보니 이해해 볼 필요가 있어서 직접 실습까지 진행해봤고, TextField에서 becomeFirstResponder(), resignFirstResponder() 를 사용했을 때 왜 키보드가 나타나고 사라지는지에 대해서 더 잘 이해할 수 있었다.
https://developer.apple.com/documentation/uikit/uiresponder
https://zeddios.tistory.com/538
https://jeonyeohun.tistory.com/257