"Learn how to handle events that propagate through your app."
앱을 통해 전개되는 이벤트를 어떻게 다루는지 학습하게 되는 자료입니다.
앱은 리스폰더 객체를 사용해 이벤트를 받거나 처리합니다. 리스폰더 객체는 UIResponder
클래스의 모든 인스턴스이며, 일반적인 하위클래스는 UIView
, UIViewController
, UIApplication
을 포함합니다. 리스폰더는 가공되지 않은 이벤트 데이터를 받고 이벤트를 처리하거나 다른 리스폰더 객체에게 전달해야 합니다. 앱이 이벤트를 받을 때, UIKit
은 첫 번째 리스폰더로 알려진 가장 적합한 리스폰더 객체에게 해당 이벤트를 연결시킵니다.
처리되지 않은 이벤트는 활성화된 리스폰더 체인에서 리스폰더로 전달됩니다. 리스폰더 체인은 앱 리스폰더 객체의 동적인 설정입니다. Figure 1은 인터페이스가 레이블, 텍스트필드, 버튼, 두 가지 배경 뷰를 갖고 있는 앱에서 리스폰더를 보여주고 있습니다. 다이어그램은 리스폰더 체인을 따라 어떻게 이벤트가 하나의 리스폰더로부터 다음 리스폰더로 이동하는지를 보여줍니다.
Figure 1 Responder chains in an app
텍스트 필드가 이벤트를 직접 다루지 않는다면, UIKit
은 이 이벤트를 텍스트 필드의 상위 UIView
에게 전달합니다. 이벤트가 전달되는 방향은 윈도우에 전달되기에 앞서 가지고 있던 뷰 컨트롤러를 향하게 되고, 이렇게 방향을 전환하는 것 역시 리스폰더 체인이 합니다. 윈도우가 이벤트를 다룰 수 없다면 UIKit
은 UIApplcation
객체에 전달하고, 만약 해당 객체가 UIResonder
의 인스턴스가 아니면서 리스폰더 체인의 일부가 아니라면 앱 딜리게이트에 전달합니다.
UIKit
은 어떤 객체를 특정 이벤트에 대한 첫 번째 리스폰더로 지정하며, 이는 해당 이벤트의 타입을 기초로 합니다. 아래가 대표적입니다.
Event type | First responder |
---|---|
Touch events | 터치가 발생한 뷰입니다. |
Press events | 초점을 갖는 객체입니다. |
Shake-motion events | 직접 지정했거나 UIKit 이 지정한 객체입니다. |
Remote-control events | 직접 지정했거나 UIKit 이 지정한 객체입니다. |
Editing menu messages: | 직접 지정했거나 UIKit 이 지정한 객체입니다. |
Note
가속 움직임, 자이로스콥, 자력 탐지와 같은 모션 이벤트는 리스폰더를 따르지 않는 대신 코어 모션이 지정된 객체에 이런 이벤트를 전달합니다.
컨트롤은 액션 메시지를 사용해 직접 관련이 있는 목표 객체와 소통합니다. 사용자가 컨트롤과 상호작용하면, 컨트롤은 목표 객체에 액션 메시지를 보냅니다. 액션 메시지는 이벤트가 아닙니다. 그러나 여전히 리스폰더 체인을 이용합니다. 컨트롤의 목표 객체가 nil
일 때, UIKit
은 목표 객체로부터 시작해 적합한 액션 메소드를 구현한 객체를 찾을 때까지 리스폰더 체인을 통과합니다. 예를 들어 UIKit
편집 메뉴는 cut(:)
, copy(
:)
, paste(_:)
와 같은 이름을 가진 메소드를 구현한 리스폰더 객체를 탐색하기 위해 앞서 설명한 동작을 사용합니다.
제스쳐 리코그나이저는 터치와 프레스 이벤트를 뷰가 받기 전에 받습니다. 만약 뷰의 제스쳐 리코그나이저가 터치의 연속을 인식하는 것에 실패한다면, UIKit
은 뷰에 터치를 보냅니다. 만약 뷰가 터치를 처리하지 않는다면, UIKit
은 터치를 리스폰더 체인으로 전달합니다. 제스처 리코그나이저의 이벤트 처리와 관련한 내용은 Handling UIKit Gestures를 살펴보시기 바랍니다.
Handling UIKit Gestures
https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/handling_uikit_gestures
UIKit
은 터치 이벤트가 어디에서 발생했는지 결정하기 위해 view-based hit-testing을 사용합니다. 구체적으로 UIKit
은 뷰 계층구조에서 뷰 객체의 bounds에 터치 위치를 비교합니다. UIView
의 hitTest(_:with:)
메소드는 뷰 계층구조를 따라 터치 이벤트에 대한 첫 번째 리스폰더가 될 수 있는 특정 터치를 포함한 가장 깊은 하위뷰를 탐색합니다.
Note
만약 터치 위치가 뷰의 bounds 밖에 있다면,hitTest(:with:)
메소드는 해당 뷰와 그 뷰의 모든 하위뷰를 무시합니다. 결과적으로 뷰의clipsToBounds
속성이false
일 경우 뷰 bounds의 하위뷰 밖은 터치가 발생했더고 하더라도 반환되지 않습니다. hit-testing 동작에 대한 더 많은 정보는UIView
에서hitTest(
:with:)
메소드를 살펴보시기 바랍니다.
hitTest(_:with:)
https://developer.apple.com/documentation/uikit/uiview/1622469-hittest
터치가 발생하면 UIKit
은 UITouch
객체를 생성하고 이를 뷰에 연관시킵니다. 터치 위치 혹은 다른 파라미터가 변경되면 UIKit
은 같은 UITouch
객체에 새로운 정보를 업데이트합니다. 변경되지 않는 속성은 오직 뷰입니다. (터치 위치가 기존 뷰 밖으로 이동하더라도 터치의 뷰 속성에서 값은 변경되지 않습니다.) 터치가 끝나면 UIKit
은 UITouch
객체를 해제합니다.
리스폰더 객체의 다음 속성을 오버라이드함으로써 리스폰더 체인을 변경할 수 있습니다. 이렇게 할 경우 다음 리스폰더는 반환한 객체가 됩니다.
많은 UIKit
클래스가 이미 이 속성을 오버라이드하고 있으며 특정 객체를 반환합니다. 아래 내용을 포함합니다.
UIView
객체입니다. 만약 뷰가 뷰 컨트롤러의 루트 뷰이면, 다음 리스폰더는 뷰 컨트롤러입니다. 그렇지 않다면 다음 리스폰더는 뷰의 슈퍼뷰입니다.UIViewController
객체입니다.UIWindow
객체입니다. 윈도우의 다음 리스폰더는 UIApplication
객체입니다.UIApplication
객체입니다. 다음 리스폰더는 앱 딜리게이트입니다. 앱 딜리게이트가 UIResponder
의 인스턴스일 때에만 그렇습니다. 뷰, 뷰 컨트롤러, 앱 객체 자체일 때는 그렇지 않습니다.