[iOS] 이벤트 처리
원래는 UIResponder에 대해 알아보려했는데, UIResponder를 알아보기 전 아이폰에 이벤트가 발생하면 어떤 식으로 처리되는지를 먼저 알아보면 좋을 것 같아 Using responders and the responder chain to handle events라는 아티클을 읽고 이해해보는 시간을 선행해서 가져보려고 한다.
Handle events
앱은 responder 객체를 사용해서 앱에서 이벤트를 받거나 처리한다.
이 responder 객체란 UIResponder
클래스 인스턴스 혹은 UIView
, UIViewController
, UIApplication
을 포함한 여러 UIResponder
의 서브클래스들을 말한다.
이 responder들은 이벤트를 받으면 그걸 그 자리에서 처리하거나 다른 responder 객체로 이벤트를 넘긴다. 우리의 앱이 이벤트를 받게되면 맨 처음엔UIKit
이 자동으로 가장 적합한 responder 객체(first responder라고 불리는 responder 객체)로 이벤트를 넘겨준다.
이후 first responder에서 처리되지 않은 이벤트는 responder chain이라는 responder 객체들의 chain을 따라 다음 responder 객체로 차례로 넘어간다.
아래 그림이 responder chain의 예시이다 !
만약 UITextField
가 있는 부분에서 이벤트가 발생했는데, 이 이벤트를 UITextField
가 처리하지 않는다면, UIView
로 해당 이벤트가 전달된다. 그 전달된 UIView
에서도 처리가 안되면 responder chain에 있는 상위 UIView
로,, 그 다음은 UIViewController
로.. 이렇게 계속 전달전달되다가, 마지막까지 처리되지 않은 이벤트는 그냥 버려지게 되는 것이다..(불쌍해라..)
First Responder 결정짓기
아까 위에서 처음 이벤트가 발생하면 UIKit
이 알아서 적절한 responder 객체를 찾아 first responder로 정해준다 했는데, 이 first responder를 결정하는 기준은 아래 표와 같다.
Event type | First responder |
---|---|
Touch events | 터치가 발생한 뷰 |
Press events | focus된 객체 |
Shake-motion events | 개발자 혹은 UIKit이 지정해둔 객체 |
Remote-control events | 개발자 혹은 UIKit이 지정해둔 객체 |
Editing menu messages | 개발자 혹은 UIKit이 지정해둔 객체 |
위 표는 아티클에 있는 내용을 직독직해한거라 살짝? 이해가 안갈 수 있는데, 조금 설명을 덧붙여자면? Touch, Press 이벤트를 그 이벤트가 발생한 위치를 기준으로 동적으로 first responder가 정해지고, 나머지 이벤트들은 특정하게 정적으로 지정된 객체가 first responder가 되는 것 같다.
accelerometers, gyroscopes, magnetometer 이벤트들(속도나 자력 등에 관련된 이벤트)은 responder chain을 따르지 않는다고 한다. 대신 Core Motion이 해당 이벤트들을 지정된 객체에 바로 전달한다고 한다 !!
컨트롤러(button이나 slider같은 것들..)에서 User Interaction이 발생하면 먼저 자신의 target 객체로 action message를 보낸다. (action 메세지는 그냥 쉽게말해 responder 객체가 실행할 행동을 얘기하는 것 같다. 내 뇌피셜이다..)
이 action message라는 것은 이벤트는 아니지만 responder chain을 활용한다고 한다. 그래서 User Interaction이 일어난 컨트롤러의 target 객체가 nil
이면 UIKit
은 responder chain을 순회하며 해당 action message를 처리해줄 객체를 찾게 된다.
Gesture recognizer의 경우 뷰에서 touch나 press 이벤트를 받으면 해당 위치에 있는 뷰(View)로 바로 이 이벤트를 보내기 전에 먼저 이벤트를 받게된다. 만약 뷰에 지정된 Gesture recognizer가 없어서 해당 이벤트를 처리하지 못했다면, 해당 뷰로 이벤트가 보내지고, 그 뷰에서도 이벤트 처리를 못 했다면, responder chain에 들거가게 되는 것이다.
옹.. 그렇다면 Gesture Recognizer 말고 그냥 UIView 자체로 이벤트 핸들링을 할 수 있는 무언가가 있다는 말인가..? 훔.. 🤔
touch events를 받은 responder 지정하기
자 이제까지 first responder가 무엇인지, 그리고 어떻게 responder chain이 활용되는지 알아봤다.
그럼 만약 뷰들이 여러개 중첩되어있는 상황에 터치 이벤트가 발생하면 그 이벤트를 어떤 뷰에서 가장 먼저 처리할지는 어떻게 결정되는 것일까?
UIKit
은 hit-test
라는 것을 통해 찾게된다. UIView
에는 hitTest(_:with:)
라는 메소드가 존재한다. 이 hitTest
메소드는 터치 이벤트가 발생하면 해당 이벤트가 발생한 위치를 확인한다. 그리고 그 위치에 존재하는 최상단 뷰의 hitTest
를 맨 먼저 실행시킨다. (이 최상단 뷰라는 건 가장 슈퍼뷰 즉, 중첩된 뷰들 중 가장 먼저 깔린.. view hierachy에서 가장 아래에 위치하는.. 뷰를 말한다.)
hitTest
가 실행되면 hitTest
를 실행시킨 뷰의 서브뷰들 중 터치 이벤트가 발생한 위치에 존재하는 서브뷰의 hitTest
를 또 실행시키게된다. 이렇게 재귀느낌으로 서브뷰의 서브뷰로.. hitTest
를 실행시키며 들어가다보면 뷰의 상속관계 중 최하단에 존재하는 하나의 뷰를 반환하게된다.
이 뷰가 바로 !! 터치 이벤트의 first responder
가 되는 것이다 ~
이러한 프로세스 때문에, 만약 서브뷰의 위치가 자신의 슈퍼뷰의 bounds 바깥에 존재하게 되면 그 서브뷰는 이벤트를 받을 수 없게된다. 뿐만 아니라 clipsToBounds
라는 프로퍼티가 true
로 설정되었다면, 그 잘린 부분의 터치 이벤트 또한 무시되게된다.
Custom responder chain
responder chain은 responder 객체의 next
프로퍼티를 오버라이딩하여 내 맘대로 바꿀 수 있다.
리꾸 ~ (responder chain 꾸미기..)
이미 여러 UIKit 클래스들은 해당 프로퍼티를 오버라이드하여 특정 객체들을 반환하도록 짜여져있다고 한다.
아래와 같이 바꿔줄 수 있다 ~
class CustomView: UIView {
override var next: UIResponder? { nil }
// 이러면 해당 위치에서 더이상 다음 responder로 넘어가지 않고 이벤트가 처리 혹은 증발된다.
}
오늘은 이렇게 responder chain이 무엇인지, 그놈의 first responder가 뭔지, 이벤트가 어떻게 처리되는지의 프로세스를 알아봤다 ! 다음 글에선 UIResponder
라는 클래스에 대해 더 자세히 알아봐야겠다 !
끗 ~