Event Handling Guide for iOS

손명기
42 min readMar 26, 2017

--

UIKit을 사용하면 앱에서 Gesture를 쉽게 감지할 수 있다.
일부 Gesture는 매우 일반적이어서 UIKit에 내장되어 있습니다.

하나의 Event는 특정 경로를 따라 이동하는 event처리 대상을 찾습니다.

iOS가 event를 인식하면 해당 event를 처리하는 데 가장 관련있는 view객체로 event를 전달합니다. 이 view객체가 event를 처리할 수 없는 경우, 계속해서 더 큰 객체에 이벤트를 전달하여 처리합니다.

UIEvent는 하나의 touch event, shake-motion event, remote-control event를 캡슐화 합니다.

손가락이 화면을 터치하고 화면을 가로 질러 이동하는 등 작업이 발생하면 iOS는 계속해서 event 객체를 앱으로 보내 처리합니다.

유저들이 view들을 touch하면 앱이 멀티 touch event들을 수신합니다.

유저들이 유저들의 디바이스를 움직였을 때 앱은 Motion event들을 수신합니다.

motion event는 장치의 위치(location), 방향(orientation), 동작(movement)에 대한 정보를 제공합니다.

유저들이 Multimedia 컨트롤러들을 조작할 때 앱은 Remote Control event들을 수신합니다.

외부 액세서리가 앱에 remote control event를 보낸다. 이러한 event를 통해 사용자는 헤드셋, 오디오, 비디오를 제어할 수 있다.

Gesture recognizer

low-level event 처리를 high-level event처리로 변환한다.

내장된 Gesture recognizer

UITapGestureRecognizer (tap개수 조절 가능)UIPinchGestureRecognizer(view zooming)UIPanGestureRecognizer(dragging)UISwipeGestureRecognizer(모든 방향 가능)UIRotationGestureRecognizerUILongPressGestureRecognizer

gesture recognizer는 하나의 view에 연결된다. 또한 하나의 view에 여러 가지 gesture recognizer를 추가할 수 있다. 사용자가 gesture recognizer가 연결된 해당 view를 touch하면 gesture recognizer는 view객체가 수행하기 전에 touch가 발생했다는 message를 수신하므로 gesture recognizer는 view를 대신 touch에 응답할 수 있습니다.

개별 Gesture(tap와 같은)는 한번 만 발생됨

계속적 Gesture(Pan와 같은)는 연속적으로 발생됨(multitouch sequence가 끝날때 까지)

Gesture Recognizer가 상호작용하는 방법

Gesture Recognizer의 종류가 개별 Gesture Recognizer 인지 계속적 Gesture Recognizer 인지에 따라 정확한 Machine State가 나뉩니다. 모든 Gesture Recognizer는 UIGestureRecognizerStatePossible 상태에서 시작합니다.

개별 Gesture Recognizer Machine State
Possible -> Recognized or Failed

연속적 Gesture Recognizer Machine State
Possible -> Begin or Failed
Begin -> Changed -> Changed or Canceled or Recognized

다른 Gesture Recognizer와 상호작용하기

  • view에 두 개 이상의 Gesture Recognizer을 연결할 수 있다.
    확인 하고 싶으면 view의 gestureRecognizers 속성을 확인하면 된다.
  • view에 여러 Gesture Recognizer이 연결되어 있는 경우, 기본적으로는 Gesture Recognizer가 touch를 먼저 받는 순서는 설정되어 있지 않으므로 touch할 때마다 매번 다른 순서로 Gesture Recognizer로 전달할 수 있습니다. 기본 동작을 다음과 같이 재정의 할 수 있습니다.
  1. 하나의 Gesture Recognizer가 다른 Gesture Recognizer보다 먼저 touch를 분석하도록
  2. 두 개의 Gesture Recognizer가 동시에 작동하도록 허용
  3. Gesture Recognizer가 touch를 분석하지 못하도록

두 Gesture Recognizer에 대한 특정 순서 선언(보통 one tap, double tap일 경우)

기본적으로 사용자가 swipe gesture를 시도하면 gesutre가 pan으로 해석됩니다.
이는 swipe gesture(개별gesture)가 swipe로 해석되는 데 필요한 조건을 충족시키기 전에 pan(계속적gesture)으로 해석되는 데 필요한 조건을 충족시키기 때문입니다. view가 swipe와 pan을 모두 인식 하도록 하려면 swipe gesture recognizer가 pan gesture recognizer가 수행하기 전에 touch event를 분석해야 합니다. swipe gesture recognizer가 touch를 swipe라고 판단하면 pan gesture recognizer는 touch를 분석할 필요가 없습니다. swipe gesture recognizer가 touch가 swipe가 아니라고 판단하면 swipe gesture는 failed 상태로 이동하고, pan gesture recognizer가 touch event를 분석을 시작해야 합니다.

ios7 이전에, gesture recognizer가 다른 gesture recognizer가 실패할 것으로 요구하면 requireGestureRecognizerToFail:을 사용하여 생성 시 두 객체 사이에 영구 관계를 설정합니다.

ios7 에서는 UIGestureRecognizerDelegate가 runtime에 gesture recognizer delegate에 의해 실패 요구사항을 지정할 수 있는 두 가지 메소드를 제공합니다.

 gestureRecognizer:shouldRequireFailureOfGestureRecognizer:
gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:

touch분석으로 부터 gesture recognizer 방지

gesture recognizer에 delegate객체를 추가하여 gesture recognizer의 동작을 변경할 수 있습니다. UIGestureRecognizerDelegate 프로토콜은 gesture recognizer가 touch를 분석하지 못하도록 하는 몇 가지 방법을 제공합니다.

gestureRecognizer:shouldReceiveTouch: 메소드 또는 gestureRecognizerShouldBegin: 메소드 (둘다 optional 이다.)

touch가 시작되면 gesture recognizer가 touch를 고려해야 하는지 여부를 즉시 결정하고 싶다면, gestureRecognizer:shouldReceiveTouch: 메소드를 사용하십시오. 미 메소드는 새로운 touch가 있을 때마다 호출됩니다. NO를 반환하면 gesture recognizer가 touch가 발생했다는 알림을 받지 못합니다. 기본값은 YES이며, 이 메소드는 gesture recognizer의 상태를 변경하지 않습니다.

gesture recognizer가 touch를 분석해야하는지 여부를 결정하기 전에 가능한 한 오래 기다려야하는 경우 gestureRecognizerShouldBegin : 메소드를 사용하십시오. 일반적으로 gesture recognizer와 경쟁하는 custom touch event처리 기능이있는 UIView 또는 UIControl subclass가 있는 경우 이 메서드를 사용합니다. NO를 반환하면 gesture recognizer가 즉시 실패하므로 다른 touch 처리가 진행됩니다. 이 메서드는 gesture recognizer가 view 또는 control이 touch를 수신하는 것을 방해하는 경우 gesture recognizer가 Possible 상태를 벗어나려고 시도 할 때 호출됩니다.

view 또는 view controller가 gesture recognizer의 delegate가 될 수없는 경우. UIView의 gestureRecognizerShouldBegin : 메서드를 사용할 수 있습니다. 메소드 서명 및 구현은 동일합니다.

동시에 gesture 인식들을 허용 (보통 Long, Pan 같이 쓸 경우)

기본적으로 두 gesture recognizer는 각각의 gesture를 동시에 인식 할 수 없습니다. 예를 들어, 사용자가 동시에 view를 집 어서 회전시킬 수 있기를 원한다고 가정합니다. UIGestureRecognizerDelegate 프로토콜의 선택적 메서드 인 gestureRecognizer : shouldRecognizeSimultaneouslyWithGestureRecognizer : 메서드를 구현하여 기본 동작을 변경해야합니다. 이 방법은 한 gesture recognizer의 gesture 분석으로 다른 gesture recognizer가 gesture를 인식하지 못하게 하거나 그 반대의 경우에도 호출됩니다. 이 메소드는 기본적으로 NO를 리턴합니다. 두 개의 gesture recognizer가 동시에 자신의 gesture를 분석하도록하려면 YES를 반환합니다.

주석: gestureRecognizer : shouldRecognizeSimultaneouslyWithGestureRecognizer : 메소드는 기본적으로 false를 반환하고, Possible 상태에 있는 gesture recognizer는 이미 Recognized 상태의 gesture에게 touch를 맡긴다. 이 메소드는 두 gesture recognizer 중 어느 것이 gesture를 인식할지에 대해서 알려주지 않는다. 따라서, 둘 중 하나가 잠재적으로 gesture를 인식할 기회를 빼앗긴다.

두 Gesture Recognizer 간의 단방향 관계 지정

swipe와 pan gesture를 인식하고이 두 동작이 고유 한 동작을 실행 하기를 원한다고 가정 해보십시오. 기본적으로 사용자가 swipe를 시도하면 gesture가 pan으로 해석됩니다. 이는 swipe gesture가 swipe로 해석되는 데 필요한 조건을 충족시키기 전에 swipe gesture가 pan으로 해석되는 데 필요한 조건을 충족시키기 때문입니다.

view가 swipe와 pan을 모두 인식하도록하려면 swipe gesture recognizer가 pan gesture recognizer를 수행하기 전에 터치 이벤트를 분석해야합니다. swipe gesture recognizer 터치가 swipe라고 판단하면 pan gesture recognizer는 터치를 분석 할 필요가 없습니다. swipe gesture recognizer가 터치가 swipe가 아니라고 판단하면 swipe gesture는 Failed 상태로 이동하고 pan gesture recognizer는 터치 이벤트 분석을 시작해야합니다.

iOS 7 이전에 gesture recognizer에서 다른 gesture recognizer가 실패 할 것을 요구하면 requireGestureRecognizerToFail :을 사용하여 생성시 두 객체 간에 영구 관계를 설정합니다. gesture recognizer가 앱의 다른 위치 또는 프레임 워크에서 생성되지 않고 gesture recognizer 세트가 동일하게 유지되면이 기능이 잘 작동합니다.

iOS 7에서 UIGestureRecognizerDelegate는 런타임에 gesture recognizer delegate 객체에 의해 실패 요구 사항을 지정할 수있는 두 가지 메소드를 제공합니다.

gestureRecognizer : shouldRequireFailureOfGestureRecognizer :
gestureRecognizer : shouldBeRequiredToFailByGestureRecognizer :

참고 : UIGestureRecognizerSubclass는 하위 클래스에서 클래스 전체 오류 요구 사항을 정의 할 수있는 유사한 메서드를 정의합니다 (자세한 내용은 UIGestureRecognizer 클래스 참조).
두 가지 방법 모두 gesture recognizer는 인식 시도마다 한 번 호출되므로 실패 요구 사항을 느리게 확인할 수 있습니다. 또한 서로 다른 뷰 계층 구조의 인식 기간에 장애 요구 사항을 설정할 수 있음을 의미합니다.

다른 사용자 인터페이스 control와 상호작용

iOS 6.0 이상에서는 기본 control action들로 인해 gesture recognizer 동작이 중복되지 않습니다. 예를 들어, button의 기본 동작은 single tap입니다. single tap gesture recognizer를 button의 superview에 연결되어 있다면, 사용자가 button을 tap한 경우 button의 동작 메소드는 gesture recognizer 대신 touch event를받습니다. 이 컨트롤은 컨트롤의 기본 동작과 겹치는 gesture 인식에만 적용됩니다.

  • UIButton, UISwitch, UIStepper, UISegmentedControl, UIPageControl (single finger single tap)
  • slider와 평행 한 방향으로 UISlider 손잡이를 한 손가락으로 스와이프 방지
  • switch와 평행 한 방향으로 UISwitch의 손잡이에 단일 손가락 pan gesture 방지

이러한 컨트롤 중 하나의 사용자 지정 하위 클래스가 있고 기본 동작을 변경하려면 부모 뷰 대신 컨트롤에 직접 제스처 인식기를 연결하십시오. 그런 다음, 제스처 인식기는 먼저 터치 이벤트를 수신합니다.

touch가 발생되면 UIApplication객체에서 UIWindow객체로 touch객체가 전달된다. 그런 다음 window는 먼저 touch가 발생한 view(또는 해당 view의 superview)에 첨부된 모든 gesture recognizer로 touch를 전달한 후 view 객체 자체에 touch를 전달 합니다.

gesture recognizer는 touch를 인식할 수 있는 첫 번째 기회를 얻습니다. window는 gesture recognizer가 touch를 먼저 분석할 수 있도록 view에 touch객체 전달을 지연시킵니다. 지연 동안 gesture recognizer가 touch gesture를 인식하면 window는 touch객체를 window는 touch객체를 view에 전달하지 않으며, 이미 인식된 sequence의 일부인 view에 이전에 전송된 모든 touch객체를 취소합니다.

  • delaysTouchesBegan(default NO) — 일반적으로 window는 began 및 moved 단계의 touch객체를 view 및 gesture인식기로 보냅니다.
    하지만, YES로 설정될 경우, window는 시작 단계의 touch객체를 view에 전달할 수 없습니다. 이렇게 되면 gesture recognizer가 gesture를 인식할 때 touch event를 view에 전달할 수 없습니다.
  • delaysTouchesEnded(default YES) — YES로 설정하면 나중에 gesture가 취소할 수 있는 동작이 view에서 완료 되지 않습니다. gesture recognizer가 touch event를 분석할 때 window은 종료된 단계의 touch객체를 첨부된 view로 전달하지 않습니다. gesture recognizer가 gesture를 인식하면 touch객체가 취소됩니다.

Hit-Test

System— event → Event Queue → UIApplication → UIWindow → UIView

window객체는 touch가 발생한 view에 event를 전달하려고 한다. 이 view를 Hit-test view라고 한다. Hit-test view를 찾는 process를 hit-test라고 하며, hit-test는 touch가 발생한 위치의 view를 반환합니다.

iOS는 hit-testing을 사용하여 touch가 발생된 view를 찾습니다. hit-testing은 touch가 관련된 view객체의 범위 내에 있는지 확인하는 것을 포함합니다. view의 모든 하위 뷰를 재귀적으로 검사합니다. view 계층구조에 있는 가장 낮은 view가 touch 지점이 포함된 view라면 hit-test view가 됩니다. iOS가 hit-test view를 결정한 후에, touch event를 hit-test view로 전달하여 처리합니다. 예를들어, 사용자가 그림 2–1의 E View를 touch한다

고 가정하면, iOS는 subview들을 다음 순서대로 확인하여 hit-test view를 찾습니다.

1. Touch는 view A 범위내에 있으므로 subview B와 C를 확인합니다.

2. Touch가 view B 범위내에 있지 않지만, view C의 범위 내에 있으므로 하위 뷰 D, E를 확인합니다.

3. Touch가 view D의 경계에 있지 않지만, view E의 경계 내에 있습니다. View E는 touch가 포함된 view 계층구조에서 가장 낮은 view이므로 hit-test view가 됩니다.

hitTest:withEvent: 메소드는 지정된 CGPoint와 UIEvent에 대한 hit test view를 리턴합니다.

hitTest:withEvent: 메소드는 pointInside:withEvent 메소드를 호출하여 시작됩니다. 만약 hitTest:withEvent:로 전달된 point가 view 경계 안에 있으면 pointInside:withEvent:가 YES를 반환합니다. 그런 다음 이 메소드는 YES를 리턴하는 모든 subview의 hitTest:withEvent: 메소드를 호출합니다.

hitTest:withEvent:로 전달된 point가 경계 안에 있지 않으면, pointInside:withEvent: 메소드는 NO를 반환하고 point는 무시되며 hitTest:withEvent:는 nil을 반환합니다. 만약 subview가 NO를 리턴하면, 해당 subview에서 touch가 발생하지 않았으므로 해당 subview의 subview에서도 발생하지 않았기 때문에 view 계층구조의 전체 분기가 무시됩니다. 즉, superview 외부에 있는 모든 subview의 point는 touch point가 superview와 subview의 경계 내에 있어야하므로 touch event를 받을 수 없습니다. subview의 clipsToBounds 속성이 NO로 설정된 경우 발생할 수 있습니다.

Hit test process에 관련된 메소드

  • hitTest:withEvent
  • pointInside:withEvent

test된 point가 범위 내에 있으면 하위 view에서 pointInside:withEvent:를 호출합니다.

https://www.google.co.kr/search?q=ios+photos&newwindow=1&source=lnms&sa=X&ved=0ahUKEwj8nLPViuXSAhWBuZQKHdpaBxMQ_AUIBSgA&biw=1068&bih=572&dpr=1#newwindow=1&q=iOS+hittest&*

Hit-test view는 touch event를 처리할 수 있는 첫 번째 기회를 얻습니다. Hit-test view에서 event를 처리할 수 없는 경우 event는 처리할 수 있는 객체를 system에서 찾을 때까지 해당 응답의 체인을 응답기 체인으로 구성됩니다.

Responder Chain은 Responder객체들로 구성됩니다.

많은 유형의 event들은 event 전달을 위해 responder chain에 의존합니다. Responder chain은 일련의 연결된 responder객체들입니다. 첫 번째 responder로 시작하여 application객체로 끝나게 됩니다. 첫 번째 responder가 event를 처리 할 수 없는 경우 event는 responder chain의 다음 responder에게 전달 됩니다.

responder객체는 event에 응답하며 event를 처리할 수 있는 객체입니다. UIResponder 클래스는 모든 responder객체의 기본 클래스이며 event처리 뿐만 아니라 공통 responder 동작을 위한 프로그래밍 인터페이스를 정의합니다. UIApplication, UIViewController, UIView 클래스의 인스턴스는 responder입니다. 즉, 모든 view와 대부분의 핵심 view controller객체가 responder입니다. Core Animation layer는 responder가 아닙니다.

첫 번째 responder는 첫 번째로 event들을 수신받도록 지정됩니다. 일반적으로 첫 번째 responder는 view객체입니다. 객체는 두 가지 작업을 수행하여 첫 번째 responder가 됩니다.

  1. canBecomFirstResponder 메소드를 재정의하여 YES를 반환합니다.
  2. becomFirstResponder 메시지 수신.

Event들은 responder chain에 의존하는 유일한 객체는 아닙니다. Responder chain은 다음 모든 경우에 사용됩니다.

  • touch events. 만약 hit-test view가 touch event를 처리 할 수 없는 경우, event는 hit-test view로 시작하는 일련의 responder 위로 전달됩니다.
  • motion events. UIKit을 사용하여 흔들기 event를 처리하려면 첫 번째 응답자가 UIResponder 클래스의 motionBegan:withEvent 또는 motionEnded:withEvent 메소드 중 하나를 구현해야 합니다.
  • remote control events. Remote control event를 처리하려면 첫 번째 응답자가 UIResponder 클래스의 remoteControlReceivedWithEvent: 메소드를 구현해야 한다.
  • action messages. 사용자가 button이나 switch와 같은 control을 조작하고 action 메소드의 대상이 nil이면 첫 번째 responder로 시작하는 responder chain을 통해 message가 전송됩니다. 이 responder는 control view 자체일 수도 있습니다.
  • editing-menu messages. 사용자가 edit menu의 명령을 탭하면 iOS는 responder chain을 사용하여 필요한 메소드(예 cut, copy, paste 등)를 구현하는 객체를 찾습니다.
  • Text editing. 사용자가 text filed 혹은 text view를 누르면 자동적으로 첫 번째 responder가 됩니다. 기본적으로 가상 키보드가 나타나고 text filed 혹은 text view가 편집의 초점이 됩니다. 앱에 적합한 경우 키보드 대신 custom input view를 추가할 수도 있습니다. 모든 responder객체에 custom input view를 추가할 수도 있습니다.

UIKit은 사용자가 첫 번째 responder가 되는 text filed 혹은 text view를 자동으로 설정합니다. 앱은 반드시 다른 모든 first responder객체를 becomeFirstResponder 메소드로 명시적으로 설정해야 합니다.

Responder Chain 특정 전달 경로를 따른다

초기 객체(hit-test view 또는 첫 번째 responder)가 event를 처리하지 않으면 UIKit은 event를 chain에 있는 다음 responder에게 전달합니다. 각 responder들은 nextResponder 메소드를 호출하여 event를 처리할지 아니면 다음 responder에게 전달할지 결정합니다. 이 프로세스는 responder객체가 event를 처리하거나 responder가 더 이상 없을 때까지 계속됩니다.

Responder chain sequence는 iOS가 event를 감지하여 이 event를 일반적으로 view인 초기 객체에 전달할 때 시작됩니다. 초기 view는 event를 처리할 수 있는 첫 번째 기회를 갖습니다. 그림 2–2는 두 가지 앱 구성에 대한 두 가지 다른 event 전달 경로를 보여줍니다. 앱의 event 전달 경로는 구체적인 구성에 따라 다르지만 모든 event 전달 경로는 동일한 휴리틱스를 따릅니다.

Multitouch Events

앱의 custom touch event 처리를 구현하려면 먼저 responder 클래스의 subclass로 만듭니다. 이 subclass는 UIView, UIViewController, UIControl, UIApplication, UIWindow일 수 있습니다. 그런 다음 subclass의 인스턴스가 multitouch event를 수신하도록 하려면 다음을 수행해야 합니다.

  1. subclass는 Touch event를 처리하기 위해 UIResponder 메소드를 구현해야 합니다.
  2. touch들을 수신하는 view의 userInteractionEnabled 속성을 YES로 설정해야 합니다. View controller를 subclass싱하는 경우 view controller가 관리하는 view는 반드시 user interaction을 지원해야 합니다.
  3. touch를 받는 view는 반드시 화면에 보여야 하며, 숨기거나 투명할 수 없습니다.

subclass에서 touch-event 처리 메소드들을 구현하기

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;

UIResponder의 서브클래스 생성

Custom touch-event 처리를 구현하기 위해서 먼저 responder class의 서브클래스로 만든다. 이 서브클래스는 다음 중 하나 일 수 있다.

UIView — custom 그리는 view를 구현하기 위해서 UIView의 서브클래스로 만들어라.

UIViewController — 만약 다른 타입의 event(예)shake motion event)를 처리한다면 UIViewController의 서브클래스로 만들어라.

UIControl — touch 행동을 가지고 custom control를 만든다면 UIControl를 서브클래스로 만들어라.

UIApplication or UIWindow — 일반적으로 UIApplication or UIWindow의 서브클래스를 만들지 않기 때문에 드물다.

그런 다음 서브클래스의 인스턴스가 multi touch event를 수신하도록 하려면 다음을 수행하십시오:

  1. 하위 클래스는 반드시 UIResponder의 touch 처리 메소드를 구현해야 한다.
  2. touch들을 수신받고 있는 view는 반드시 userInteractionEnabled 프로퍼티가 YES로 설정되어 있어야 한다.
  3. touch들을 수신받고 있는 view는 반드시 보여(visible)야 한다. Transparent or hidden이 될 수 없다.

서브클래스에서 Touch-event 처리 메소드를 구현하기

iOS는 multi touch sequence의 일부로 touch를 인식합니다. multi touch sequence동안, 앱은 target responder에게 연속적으로 event message를 보냅니다. 이 event message들을 수신받고 처리하기 위해서 responder 객체의 class는 반드시 다음 UIResponder 메소드들을 구현해야 합니다:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;

각 touch 메소드들은 touch 단계(phase)에 대응됩니다: Began, Moved, Ended, Canceled. 주어진 단계에 대한 touch들이 새롭거나 변경될때, 앱 객체는 각 touch 메소드들을 호출합니다. 각 touch 메소드들은 두 가지 파라미터를 가집니다: touch들의 묶음과 하나의 event

touch들의 묶음은 UITouch객체들의 묶음(NSSet)이다. 예를들어, 하나의 touch가 Began 단계에서 Moved 단계로 변경될 때, 앱은 touchesMoved:withEvent: 메소드를 호출한다. touchesMoved:withEvent: 메소드 안에 전달된 touch들의 묶음은 Began단계의 touch 및 다른 Moved 단계에서 발생된 touch들을 포함합니다. 다른 파라미터는 event안에 있는 모든 touch객체들 포함하는 event(UIEvent객체)이다.

touch들을 처리하는 모든 view들은 완전한 touch event stream을 수신하길 기대합니다. 그래서 서브클래스를 생성할 때, 다음 룰을 준수해야 합니다:

  • 만약 custom responder가 UIView 이거나 UIViewController이라면, 여러분은 반드시 모든 event 처리 메소드를 구현해야 합니다.
  • 만약 다른 responder 클래스를 서브클래스한다면, 여러분은 event method들에 대한 구현은 null을 가질 수 있습니다.
  • 모든 메소드들을 안에서, 슈퍼 클래스의 메소드의 구현을 호출해야 합니다.

만약 여러분이 responder객체가 event의 특정 단계(phase)에 대한 접촉을 받지 못하게 되면 결과 동작이 정의되지 않고, 바람직하지 않을 수도 있습니다.

만약 responder가 event를 처리하는 동안 지속적 객체를 만드는 경우 system이 sequence를 취소할때 해당 객체를 처리하기 위해 touchesCancelled:withEvent: 메소드를 구현해야 합니다. 취소는 외부 event(ex)수신 전화 통화)가 현재 앱의 event 처리를 방해 할때 발생합니다.

Touch event의 위치와 단계 추적

iOS는 multi touch sequence에 있는 touch들을 추적합니다. iOS는 각 touch들에 대한 view안에 있는 위치(location), 이전 위치(location), touch 단계(phase), 스탬프(timstamp)를 포함한 attribute을 기록합니다. 이 프로퍼티들을 이용해 touch에 응답 방법을 결정하기 위해 사용하십시오.

하나의 touch객체는 phase 프로퍼티에 phase 정보를 저장합니다. 그리고 각 phase는 touch event 메소드의 하나에 대응됩니다. 하나의 touch객체는 3가지 방법으로 위치(location)을 저장합니다: touch가 발생된 window, window안에 있는 view, view안에 있는 touch의 외부 위치

손가락이 화면에 touch될 때, 해당 touch는 나중에 다른 view로 전달되어 처리되는 경우에도 event의 수명 동안 기본 window와 view 모두 연관됩니다. touch에 대한 응답 방법을 결정하려면 touch의 위치 정보를 사용하십시오.

touch객체들을 검색 및 질의

Event 처리 메소드안에서, 여러분은 touch객체를 검색하여 event에 대한 정보를 얻습니다:

  • The set object. 전달 된 NSSet에는 touchBegan:withEvent메소드에 대한 UITouchPhaseBegan와 같은 메소드가 나타내는 단계에서 새로 추가되었거나 변경된 모든 touch가 포함됩니다.
  • The event object. 전달된 UIEvent객체에는 주어진 multi touch sequence에 대한 모든 touch가 포함됩니다.

multipleTouchEnabled 프로퍼티는 기본적으로 NO값으로 설정됩니다. 이 의미는 view는 오직 multi touch sequence에 있는 첫 번째 touch만 수신받을 수 있습니다.

만약 touch의 위치를 알고 싶다면 locationInView: 메소드를 사용하십시오. 이 메소드에 파라미터로 self를 보내면, 여러분은 수신하는 view의 coordinate system에 있는 touch 위치를 얻습니다. 비슷하게, previousLocationInView: 메소드는 touch의 이전 위치를 알려줍니다.

복잡한 multi touch sequence 처리

Multi touch들을 추적하고 처리하고 싶다면, 다음이 필요합니다:

  • multipleTouchEnabled property를 YES로 설정하십시오.
  • Core Foundation dictionary객체를 사용하여 event가 진행되는 동안 단계의 변화를 추적합니다.

Multi touch을 가지고 event를 처리할 때, 나중에 touch를 비교할 수 있도록 touch 상태(state)에 대한 정보를 저장하는 경우가 많습니다. 예를 들어, 각 touch의 최종 위치를 원래 위치와 비교하려한다고 가정한다면, touchBegan:withEvent: 메소드에서 locationInView: 메소드에 각 touch의 원래 위치를 가져와서 UITouch객체의 주소를 key로 사용하여 dictionary객체에 저장합니다. 그런 다음 touchEnded:withEvent: 메소드에서 전달된 각 touch 객체의 주소를 사용하여 객체의 원래 위치를 가져와 현재 위치와 비교할 수 있습니다.

Custom touch event 동작 지정

특정 gesture, 특정 view, 앱의 모든 touch event의 동작을 변경하여 앱이 처리하는 방식을 custom할 수 있습니다. 다음과 같은 방법으로 touch event stream을 변경할 수 있습니다.

  • turn on delivery of multiple touch. 기본적으로 view는 multi touch sequence동안 첫 번째 touch를 제외한 모든 touch을 무시합니다. view가 multi touch를 처리하도록 하려면 multipleTouchEnabled 프로퍼티를 YES로 설정하여 view에 대해 이 기능을 활성화 해야 합니다.
  • Restrict event delivery to a single view. 기본적으로 view의 exclusiveTouch 프로퍼티는 NO로 설정되어 있습니다. 이 의미는 하나의 view가 window에 있는 다른 view가 touch를 차단하지 않는 의미입니다. 만약 YES로 설정하면 해당 view는 유일한 view 추적인 경우에만 해당 view를 받습니다.

만약 view들이 비독점적(nonexclusive)인 경우, 사용자는 하나의 view에서 한 손가락만 다른 view에서 다른 손가락을 만질 수 있으며 각 view는 동시에 해당 touch를 추적할 수 있습니다.

multi touch event 처리를 위한 최상의 방법

touch 및 Motion event를 모두 처리할 때, 따라야할 몇가지 권장 기법과 패턴이 있습니다:

  • 항상 event cancel 메소드를 구현하십시오.
  • 만약 UIView, UIViewController 또는 UIResponder의 서브클래스에서 event를 처리하는 경우:
    * 모든 event 처리 메소드를 구현하십시오. Event 처리 메소드에서 아무것도 하지 않더라도 반드시 구현하십시오.
    * 절때 superclass 구현 메소드를 호출하지 마십시오.
  • 만약 UIKit responder class의 서브 클래스에서 event를 처리하는 경우:
    * 모든 event 처리 메소드를 구현하지 마십시오.
    * 구현하는 메소드에서 superclass 구현을 호출해야 합니다. 예를 들어, [super touchesBegan:withEvent:event].
  • event를 UIKit framework의 다른 responder 객체들에 전달하지 마십시오.대신, event를 사용자 고유의 UIView 서브클래스의 인스턴스로 전달하십시오. 또한 이러한 responder객체가 event를 전달이 발생하고 해당 event에 바인딩되지 않은 touch를 받을 수 있다는 것을 알고 있는지 확인 하십시오.
  • nextResponder 메소드를 통해 responder-chain에 event를 명시적으로 보내지 마십시오. 대신 superclass 구현을 호출하고 UIKit이 responder-chain에 통과하도록 처리합니다.
  • 정밀도를 잃어버리기 때문에 touch 처리에 정수로 코드를 사용하지 마십시오.

Motion Events

accelerometer(가속계)

  • x, y, z축 마다 세 개의 accelerometer로 구성된다.
  • 각각 accelerometer는 선형 경로를 따라 시간에 따른 속도의 변화를 측정
  • 세 가지 accelerometer를 모두 결합하면 어떤 방향으로든 장치 움직임을 감지하고 장치의 현재 방향을 알 수 있음

Gyroscope

  • 세 축(x, y, z)을 중심으로 한 회전 속도를 측정

hardward data에 접근할 수 있는 여러 가지 방법:

  • 디바이스의 방향만을 감지하고 싶을 경우 UIDevice클래스를 사용
  • 사용자가 디바이스를 흔들 때 앱이 응답하게 하려면 UIKit motion event 처리 메소드를 사용하여 전달된 UIEvent객체에서 정보를 가져올 수 있다.
  • UIDevice나 UIEvent클래스가 충분하지 않다면, Core Motion framework를 사용하여 accelerometer, gyroscope 및 device motion 클래스에 접근하십시오.

UIDevice를 사용하여 현재 디바이스 방향 얻기

오직 장치의 방향만 알필요가 있고, 방향의 정확한 vector가 아닌 경우 UIDevice클래스의 메소드를 사용해라.

현재 방향을 가져오기 전에, UIDevice클래스에 beginGeneratingDeviceOrientationNotifications 메소드를 호출하여 장치 방향 알림을 생성하도록 지시해야 합니다. Accelerometer hardware가 켜지면 배터리 전원을 절약하기 위해 꺼질 수 있습니다.

방향 알림을 활성화 한 후 UIDevice객체의 orientation 프로퍼티에서 현재 방향을 가져옵니다. 장치 방향이 변경될 때 알림을 받으려면 UIDeviceOrientationDidChangeNotification 알림을 수신하도록 등록하십시오. 장치 방향은 UIDeviceOrientation 상수를 사용하여 장치가 가로 모드인지, 세로 모드인지, 화면면이 위인지, 아래인지 등을 나타내며 보고 됩니다.

장치의 방향을 더 이상 알 필요가 없으면, UIDevice 메소드 중 endGeneratingDeviceOrientationNotifications를 호출하여 방향 알림을 비활성화 합니다. 이렇게하면 accelerometer hardware가 다른 곳에서 사용되지 않는 경우 system은 accelerometer hardware 비활성화 시킨다. 이렇게 하면 배터리를 아낄 수 있다.

예시

-(void) viewDidLoad {// Request to turn on accelerometer and begin receiving accelerometer events[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];}- (void)orientationChanged:(NSNotification *)notification {// Respond to changes in device orientation}-(void) viewDidDisappear {// Request to stop receiving accelerometer events and turn off accelerometer[[NSNotificationCenter defaultCenter] removeObserver:self];[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];}

UIEvent를 가진 shake-motion event들을 발견

사용자가 디바이스를 흔들면, iOS가 accelerometer data를 평가합니다. data가 특정 기준을 충족하면 iOS가 떨리는 gesture를 해석하고 이를 나타내는 UIEvent객체를 만듭니다. 그런 다음 처리를 위해 현재 활성 앱에 event 객체를 보냅니다.

Motion event들은 touch event들 보다 훨씬 간단합니다. system은 motion이 시작 및 중지할때 app에 알려주지만, 개별 motion이 발생할 때 마다는 알리지 않습니다. 또한 motion event에는 event type(UIEventTypeMotion), event subtype(UIEventSubtypeMotionShake) 및 timestamp만 포함됩니다.

motion event에 대한 first responder 지정

Motion event를 수신하려면 responder객체를 first responder로 지정합니다. 이는 motion event를 처리하기를 원하는 응답 객체를 의미합니다.

- (BOOL)canBecomeFirstResponder { return YES; }- (void)viewDidAppear:(BOOL)animated { [self becomFirstResponder]; }

Motion event들은 responder chain을 사용하여 event를 처리할 수 있는 객체를 찾습니다. user가 device를 shake 시작하면 iOS가 첫 번째 motion event를 first responder에게 보냅니다. First responder가 event를 처리하지 않으면 responder chain을 진행합니다

motion처리 메소드들 구현하기

3가지 motion event 처리 메소드가 있습니다:

motionBegan : withEvent:,

motionEnded : withEvent :,

motionCancelled : withEvent :

Motion event를 처리하려면 motionBegan : withEvent : 메서드 또는 motionEnded : withEvent : 메서드 중 하나 또는 둘 다를 구현해야합니다. responder는 iOS가 motion event를 취소 할때 대응할 motionCancelled:withEvent: 메소드도 구현해야 합니다. Shake 동작이 중단되거나 iOS가 동작이 유효하지 않다고 결정한 경우(ex)shake가 너무 오래 지속되는 경우)event가 취소됩니다.

- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake)
{
// User was shaking the device. Post a notification named “shake.” [[NSNotificationCenter defaultCenter] postNotificationName:@“shake”
object: self];
}}

Motion event에 필요한 hardware 기능 설정 및 확인

app이 accelerometer data와 같은 기기 관련 기능에 액세스하려면 app에 필요한 기능 목록을 추가해야합니다. 특히, app의 Info.plist 파일에 키를 추가합니다. 런타임시 iOS는 장치에 필요한 기능이있는 경우에만 앱을 실행합니다. 예를 들어 앱이 gyroscope data에 의존하는 경우 gyroscope가 없는 기기에서 앱이 실행되지 않도록 필요에 따라 gyroscope로 를 나열합니다. 또한 App Store는이 목록을 사용하여 사용자에게 실행할 수없는 app 다운로드를 피할 수 있도록합니다.

app의 property list에 key를 추가하여 app의 필수 기능을 선언 하십시오. Hardware source를 기반으로 motion event에 대한 두 개의 UIRequiredDeviceCapabilities key가 있습니다.

  • accelerometer
  • gyroscope

노트: 만약 오직 device 방향의 변형만 알아내고 싶다면, accelerometer key를 추가하지 말아라.

Remote Control Events

Remote control event는 유저가 앱의 멀티미디어를 조작할 수 있는 방법입니다.

Remote control event는 외부 액세서리 혹은 system에 의해 표시되는 transport control 에서 발생하며 Media Player framework 클래스들을 통해 앱에 전달됩니다. Audio 또는 Video 컨텐츠를 재생하는 앱은 이 event를 사용하여 재생, 시작, 중지, 트랙 변경, 항목 평가를 합니다. 모든 미디어 앱에서 이러한 event를 처리할 수 있어야 합니다.

앱은 remote control event를 지원하는 것 외에도 Media Player framework를 사용하여 트랙에 대한 재생 정보를 제공할 수 있습니다. system은 잠금화면 및 제어 센터와 같은 적절한 위치에 재생 정보를 표시합니다.

remote control event처리를 위한 준비

Remote control event를 수신받기 위해 다음과 같이 해야 합니다:

  • Register handlers for each action you support. 공유되는 MPRemoteCommandCenter객체를 사용하여 다른 타입의 event들에 대한 handler들을 등록해라.
  • Begin playing audio. 앱은 반드시 “Now Playing”앱이 되어야 한다. 앱이 audio 실행을 시작할 때까지 remote control event들은 수신받을 수 없다.

또한 만약 앱이 현재 트랙에 대한 정보를 포함하는 Now Playing 정보를 제공해야한다면, MPNowPlayingInfoCenter객체를 사용하여 적절한 타이밍에 업데이트 하십시오.

Remote Control Event 처리

특정 remote control event를 처리하기 위해서, 적절한 MPRemoteCommand객체을 가지고 action hanlder를 등록하십시오. Media Player framework는 미디어와 관련된 event들에 대한 MPRemoteCommand객체들의 묶음을 정의합니다. iOS interface 및 액세서리가 remote control를 생성시킬 때, system은 일치하는 MPRemoteCommand객체를 알립니다. 이 객체는 첨부된 handler 실행에 따라 응답합니다.

MPRemoteCommandCenter객체를 통해 remote command객체들을 검색할 수 있습니다. Remote command center의 프로퍼티들은 재생 관리, 트랙 스위칭, 트랙 레이팅에 대한 정보를 포함합니다.

예제

MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];[commandCenter.playCommand addTargetUsingBlock:^(MPRemoteCommandEvent *event) {// 현재 트랙 실행[[MyPlayer sharedPlayer] play];}

Now Playing 정보 제공

Audio 혹은 video 컨텐츠를 실행할 때, MPNowPlayingInfoCenter객체를 사용하여 현재 실행중인 정보를 제공하십시오. MPNowPlayingInfoCenter객체는 실행중인 item을 설명하는 nowPlayingInfo dictionary를 포함합니다. system은 적절한 위치에 dictionary의 정보를 보여줍니다(디바이스 락화면에서)

예제- (void)configureNowPlayingInfo:(MPMediaItem*)item {MPNowPlayingInfoCenter *info = [MPNowPlayingInfoCenter defaultCenter];NSMutableDictionary *newInfo = [NSMutableDictionary dictionary];NSSet *itemProperties = [NSSet setWithObjects: MPMediaItemPropertyTitle, MPMediaItemPropertyArtist, MPMediaItemPropertyPlaybackDuration, MPNowPlayingInfoPropertyElapsedPlaybackTime, nil];[item enumerateValuesForProperties:itemPropertiesusingBlock:^(NSString *property, id value, BOOL *stop) {[newInfo setObject:value forKey:property];}];info.nowPlayingInfo = newInfo;}

--

--