여러분 앱이 탭, 스와이프, 팬, 다른 인터렉션을 사용한다면 화면 뒤에서 이벤트를 사용하고 있는 것이다. 이러한 이벤트들은 잘 만들어진 과정을 거쳐 이동하며, 우리는 이것들이 어떻게 동작하는지 한번 볼 것이다. 텍스트 인풋이나 원격 제어 이벤트를 중심으로 트리키한 문제를 디버깅하면, 이것이 어떻게 동작하는지 쉽게 이해할 수 있다. 또한 이 지식을 이용하여 여러분 앱을 통해 커스텀 데이터 흐름을 만들 수도 있을 것이다.

이 글은 이벤트 전달에 관한 시리즈의 첫번째 글이다. 터치 핸들링에대해 다룰 것인데, 그냥 터치가 어덯게 여러분의 앱을 통해 받은 이벤트가 될 수 있는지, 그 이벤트를 다루기 위해 어떻게 요소들에게 기회를 줄 수 있는지 다룬다.

터치 핸들링
터치 이벤트는 iOS 앱에서 가장 주된 형태의 이벤트이다. 어떤식으로 터치를 처리하는지에대한 세부적인 내용은 우리가 매일 사용하는 API들에의해 가려져있다. 앱에서 이 이벤트들을 어떻게 분배하는지 이해하면 여러분의 앱을 어떻게 구성할지 정하는데 도움이 될 것이다. 또한 커스텀 이벤트를 전달하는데 이런 하부조직을 사용할 수도 있다.

Hit Testing
이벤트 전달에대해 이야기할 때 가장 먼저 다룰 것은 시스템이 어떻게 터치 이벤트를 다루고 어떻게 앱을 통해 이 이벤트를 받아드리는지이다. 이벤트 사용자가 화면을 탭하면 발생한다. 적절한 요소가 터치를 다루기 전에, 시스템은 어디에서 터치가 발생했는지, 누가 먼저 터치에 응답을 받을 것인지 정해야한다.

이것이 hit testing이 필요한 곳이다.

hit testing 과정과 관련이 있는 메소드에는 hitTest:withEvent:pointInside:withEvent:가 있다. hitTest:withEvent:는 hit test한 점이 그 바운드 안에 들어오는지 알아보기 위해 pointInside:withEvent:를 사용한다. 만약 바운드 안에 들어가지 않으면 nil을 반환하고 뷰 계층의 모든 브런치를 스킵한다.

테스트한 점이 바운드 안에 들어간다면 각 서브뷰마다 pointInside:withEvent:를 호출한다.  pointInside:withEvent:에서 YES를 받은 서브뷰는 hitTest:withEvent:를 호출한다. 최초의 hitTest:withEvent: 호출로부터 최종 결과는 서브뷰 중 하나이거나 자기 자신이다. 자기 자신을 반환하는 경우는 그 서브뷰가 모두 nil을 반환할 때이다.

아래의 뷰 계층을 보자.

사용자가 View E를 탭했다고 생각해보자. View A에서부터 hitTest:withEvent:로 시작한다. View A에서 pointInside:withEvent:YES면 View B나 View C의  pointInside:withEvent:를 호출한다. View B가 NO를 반환한다. View C가  YES를 반환하고 거기의 hitTest:withEvent:를 호출한다. View C도 View D와 View E로 똑같은 과정을 따른다. View D의 pointInside:withEvent:NO를 반환한다. View E는 YES를 반환하고 더이상 다른 서브뷰가 없으므로 hitTest:withEvent:에서 자기자신을 반환한다.

View D가 View C의 서브뷰라 가정하고, 사용자가 View C 바운드의 View D를 탭했다면 무슨일이 일어날까? (이것은 clipToBoundsNO로 설정되있을 때 가능하다) View A가 위 과정을 시작한다. View B와 View C 둘 다 pointInside:withEvent:NO를 반환하므로 결국 View A가 터치를 받는다.

이제 우리는 hitTest:withEvent:로부터 받은 뷰를 가지고 있다. 이 뷰는 hit-test 뷰이다. 이제 이것은 터치와 연관되있고 터치가 활성화되는 동안 터치 이벤트를 응답하는 어떤 제스처 이후에 첫번째 기회를 줄 것이다.

주어진 터치에 커스텀 구현이 따로 없으면 무슨일이 일어날까? 상황에 따라 다르다. 만약 뷰가 뷰컨트롤러에의해 관리되고 있으면 뷰컨트롤러에게 응답할 기회를 준다. 뷰컨트롤러가 응답하지 않으면 hit-test 뷰의 부모뷰가 그 응답 기회를 가져간다. 이러한 과정은 "Responder Chain"이라 불리는 방법으로서 반복에서 진행된다.

Responder Chain
Responder Chain은 chain-of-responsibility 디자인 패턴으로 구현되있다. Responder Chain의 각 요소는 UIResponder를 상속받는다. UIResponder는 여러 종류의 이벤트를 다루기 위한 메소드들을 가지고 있다. 터치 이벤트를 넘어서 UIResponder는 인풋 뷰, 모션 이벤트, 누름 이벤트, 원격 제어 이벤트를 다루기 위한 메소드들을 정의해 두었다.

여기에서 많은 이벤트 중 firstResponder가 중요하다. firstResponder는 이벤트를 다룰 수 있는 첫번째 기회가 주어진 오브젝트이다. 첫번째 응답자가 이벤트를 다루지 않으면 nextResponder에게 그 이벤트 핸들링 기회를 준다. 그리고 현재 오브젝트가 nil 일때까지 nextResponder를 반복한다. 보통 체인에서 마지막 오브젝트는 어플리케이션 델리게이트이다.

제스처
iOS에서 현재 있는 곳에 제스처 recognizer가 오기 전에, 터치 핸들링 프로세스는 어떻게 앱을 감지하고 제스처를 다룰지의 방법이다. UIGestureRecognizer가 나오면 그들이 감자한 제스처에 따라 터치를 다루고 행동을 취하도록 프로세스를 조금 바꾼다. 우리는 제스처 recognizer를 어떻게 사용하는지 살펴보지는 않겠지만 대신에 제스처를 어떻게 터치 이벤트 전달 시스템에 맞추는지 볼 것이다.

제스처는 항상 한 터치 이벤트를 다루기 위해 첫번째 기회를 얻는다. 디폴트로는, 제스처가 터치를 얻고 그 다음 뷰를 얻는다. 제스처 상태가 "recognized" 일때 차이점은 뷰에 있는 터치가 취소된다는 것이다. 여러분은 UIGestureRecognizer:, cancelsTouchesInView, delaysTouchesBegandelaysTouchesEnded에 프로퍼티로 뷰에 이 터치 이벤트를 어떻게 전달할지 정할 수 있다. 이 프로퍼티를 잘 써서 응답하지 않는 뷰에도 응답하는 것처럼 만들 수 있다.

요약
첫번째 파트는, 터치 이벤트가 어떻게 윈도우에서 뷰까지 탐색하는지 보았는데, 뷰는 responder chain을 통해 비슷한 경로로 돌아와 터치가 일어난다. 또한 제스처 recognizer가 어떻게 이 터치 시스템에 맞춰지는지 다루었다. 두번째 파트에서는 지원하는 다른 이벤트와 responder chain이 어디에 들어맞는지 이야기해볼 것이다.

설계와 개발에 대해 더 알고 싶으면 BPXL Craft를 구독하거나 Blick Pixel 트위터를 팔로우 해달라. 


관련 링크


신고

WRITTEN BY
canapio
개인 iOS 개발, canapio