본문 바로가기
프로그래밍 놀이터/iOS

[iOS Study] 터치 이벤트와 UIResponder

by 돼지왕 왕돼지 2016. 2. 24.
반응형


 [iOS Study] 터치 이벤트와 UIResponder


출처 : 아론 힐리가스의 iOS 프로그래밍


addTarget:action:forControlEvent:, anyObject, CGPoint, CGRect, cgrectcontainspoint, CLASS, Cocoa Touch, constant, control event, COPY, Drag, event queue, first responder, FRAME, ios study, ios tutorial, locationInView, Method, multi touch, multipletouchenabled, nextresponder, nil, nscopying, NSDictionary, NSSet, nsstringfromselector, NSValue, option, override, Point, protocol, Queue, RECT, responder chain, sendActionsForControlEvents, simulator, Singleton, subclass, Target, target-action pair, touch event, Touches, touchesbegan, touchesbegan:withevent:, touchesCancelled, touchesCancelled:withEvent:, touchesEnded, touchesEnded:withEvent:, touchesMoved, touchesMoved:withEvent:, UIApplication, UIButton, UIControl, uicontroleventtouchupinside, UIControlEventTouchUpOutside, uiresponder, uislider, uitouch, UIView, UIViewController, UIWindow, valuewithnonretainedobject, window, [iOS Study] 터치 이벤트와 UIResponder, _cmd, 객체, 기본 구현, 단일 터치뷰, 리스폰더 체인, 멀티 터치, 메시지, 상수, 상위뷰, 손가락, 시뮬레이터, 싱글톤, 액션 메시지, 윈도우, 재정의, 집합, 참조, 최상위뷰, 컨트롤, 코코아 터치, 타깃, 타깃-액션 쌍, 터치, 터치 시작, 터치 이동, 터치 이벤트, 터치 종료, 퍼스트 리스폰더, 하위 클래스


-

UIView 는 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;



-

화면을 손가락으로 터치하면 UITouch 인스턴스가 만들어진다.

터치한 UIView 는 touchesBegan:withEvent: 메시지를 받고 이 때 만들어진 UITouch 는 NSSet 타입의 touches 인자에 담긴다.



-

화면에서 손가락을 움직이면 터치 객체는 손가락의 현재 위치를 저장하기 위해 갱신된다.

그리고 터치가 시작된 UIView 에 touchesMoved:withEvent: 메시지가 보내진다.

이 메소드에 인자로 전달된 NSSet 은 화면을 터치한 손가락을 나타내는 원래 생성된 UITouch 를 가진다.



-

화면에서 손가락을 떼면 터치 객체는 마지막으로 손가락의 위치를 저장하기 위해 갱신된다.

그리고 터치가 시작된 뷰에 touchesEnded:withEvent: 메시지가 보내진다.

이 메소드가 완료된 후 UITouch 객체는 소멸된다.



-

화면에서 하나의 UITouch 는 한 개의 손가락에 대응한다.

이 터치 객체는 손가락이 화면에 있는 한 존재하고 항상 화면상의 손가락 위치를 포함한다.



-

터치가 시작된 뷰는 무슨 일이 있어도 그 손가락에 관한 모든 터치 이벤트 메시지를 받는다.

만약 손가락을 처음 터치한 UIView 의 frame 밖으로 움직이더라도 그 뷰는 여전히 touchesMoved:withEvent: 와 touchesEnded:withEvent: 메시지를 받는다.

따라서 터치가 뷰에서 시작되면 그 뷰는 터치 객체의 생존 동안 그 터치를 소유한다.



-

절대 UITouch 객체에 대한 참조를 유지해서는 안 된다.

프로그램은 터치 상태가 변경될 때 터치 객체에 대한 접근 기회를 줄 것이다.



-

터치가 뭔가를 할 때마다(터치 시작, 이동, 종료 등) 터치 이벤트가 UIApplication 객체가 관리하는 이벤트 큐에 추가된다.

실제로 큐는 좀처럼 가득 차지 않고 이벤트는 즉각 전달된다.

터치 이벤트의 전달은 터치를 소유한 뷰에 UIResponder 메시지 중 하나를 보내는 것을 포함한다.

(터치가 느리게 반응하면 메소드 중 하나가 CPU 를 점유하고 있는 것이다.

그리고 이벤트는 전달되기를 기다리며 대기한다.)







-

만약 정확히 같은 시간에 하나의 뷰에 여러 손가락으로 똑같이 터치한다면 이들 터치 이벤트 모두가 한 번에 전달된다.

손가락 수만큼의 터치 객체가 NSSet 에 포함되어 UIResponder 메시지에 인자로 전달된다.

하지만 윈도우에서 “정확히 같은 시간”이란 굉장히 짧다.

따라서 모든 터치를 가진 하나의 리스폰더 메시지 대신에 보통 하나 이상의 터치를 가진 여러 리스폰더 메시지가 올 것이다.



-

UITouch *t = [touches anyObject]; // UITouch 얻어오기

CGPoint location = [t locationInView:self]; // View 에서의 터치 위치 얻기



-

기본적으로 뷰는 한 번에 하나의 터치만을 허용한다.

만약 손가락 하나가 이미 touchesBegan:withEvent: 메시지를 보냈지만 끝나지 않았다면 touchesEnded:withEvent: 는 실행되지 않는다.

그리고 뒤이은 터치들은 무시된다.

여기서 “무시”는 View 에 touchesBegan:withEvent: 메시지나 추가적인 터치와 관련된 다른 UIResponder 메시지가 보내지지 않는 것을 의미한다.



-

멀티터치를 허용하려면. self.multipleTouchEnabled = YES; 를 수행해줘야 한다.



-

단일 터치뷰에서는 집합에 오직 한 객체만 들어있다.

따라서 어떤 객체를 위한 요청은 항상 이벤트가 발생한 터치를 전해줄 것이다.

반면 멀티 터치뷰에서는 집합이 하나 이상의 터치를 포함하고 있다.



-

NSDictionary 에서 키로 사용되는 객체는 반드시 NSCopying 프로토콜을 따라야 한다.

이 프로토콜은 copy 메시지를 보내서 그 객체들을 복사하도록 허용한다.

UITouch 인스턴스는 복사될 필요가 없기 때문에 이 프로토콜을 따르지 않는다.

따라서 NSValue 인스턴스가 이후에 같은 UITouch 를 가진 동일한 NSValue 인스턴스가 만들어질 수 있으므로 UITouch 의 주소를 가진다.

( [NSValue valueWithNonretainedObject:UITouch] )



-

touchesMoved:withEvent: 와 같은 UIResponder 메시지가 뷰에 보내질 때,

오직 이동된 터치들만 NSSet 에 존재한다는 것을 알고 있어야 한다.



-

UITouch 가 일단 뷰에서 시작되면 모든 터치 이벤트는 터치를 시작한 뷰에서 터치가 벗어나더라도 터치의 생존 기간 동안 항상 같은 뷰에 보내진다.



-

NSLog(@“%@“, NSStringFromSelector(_cmd));


위의 명령문은 호출되는 class 와 method 등을 출력하는 유용한 녀석



-

UIResponder 는 터치 이벤트를 받을 수 있다.

UIView 도 UIResponder 하위 클래스의 한 예이다.

그 밖에 UIViewController, UIApplication, UIWindow 를 포함해 많은 클래스들이 있다.



-

UIViewController 에 직접 터치 이벤트를 보낼 수 없다.

하지만 뷰 컨트롤러는 리스폰더 체인(responder chain)을 통해 이벤트를 받을 수 있다.



-

모든 UIResponder 는 nextResponder 라는 포인터를 가지며 이들 객체가 함께 리스폰더 체인을 구성한다

터치 이벤트는 터치된 뷰에서 시작한다.

뷰의 nextResponder 는 보통 자신의 UIViewController(뷰 컨트롤러가 있다면)이거나 상위뷰(뷰 컨트롤러가 없다면)이다.

뷰 컨트롤러의 nextResponder 는 보통 자신의 뷰의 상위뷰이다.

최상위뷰는 윈도우이다.

윈도우의 nextResponder 는 UIApplication 의 싱글톤 인스턴스이다.







-

UIResponder 는 동일한 메시지를 nextResponder 에게 전달한다.

이것이 바로 touchesBegan:withEvent: 와 같은 메소드의 기본 구현이 하는 일이다.

메소드를 재정의하지 않으면 그 다음 리스폰더가 터치 이벤트의 처리를 시도한다.

만약 프로그램(리스폰더 체인의 마지막 객체인)이 그 이벤트를 처리하지 못하면 터치 이벤트는 버려진다.



-

다음 리스폰터에게 명시적으로 메시지를 보낼 수도 있다.


[[self nextResponder] touchesBegan:touches withEvent:event];



-

코코아 터치에서 UIControl 클래스는 UIButton 과 UISlider 를 포함해 여러 클래스의 상위 클래스이다.

UIControl 에서 각각의 가능한 컨트롤 이벤트(control event)는 상수와 관련 있다.

버튼은 일반적은 UIControlEventTouchUpInside 컨트롤 이벤트에 관한 액션 메시지를 보낸다.

이 컨트롤 이벤트에 등록된 타깃은 사용자가 컨트롤을 터치했다가 그 컨트롤 프레임 안에서 손가락을 뗀 경우에만 액션 메시지를 받는다.


타깃과 액션을 할당하는 프로그램 코드는..


[aButton addTarget:tempController

     action:@selecotr(resetTemperature:)

     forControlEvent:UIControlEventTouchUpInside | UIControlEventTouchUpOutside];



-

CGRectContainsPoint(CGRect, CGPoint) 를 통해서 rect 안에 point 가 들어있는지 확인할 수 있다.



-

sendActionsForControlEvents: 는 컨트롤이 가진 모든 타깃-액션 쌍을 확인한다.

그리고 만약 어떤 타깃-액션이 인자로 전달된 컨트롤 이벤트에 등록되어 있다면 그 타깃은 액션 메시지를 받는다.



-

컨트롤은 절대 타깃에 직접 메시지를 보내지 않는다.

그 대신에 UIApplication 객체를 통해 이들 메시지를 보낸다.

컨트롤이 nil 타깃 액션을 가질 수 있기 때문이다.

UIControl 의 타깃이 nil 이면 UIApplication 은 그 UIWindow 의 퍼스트 리스폰더를 찾아 액션 메시지를 보낸다.



cf) 시뮬레이터에서는 option 키를 누른채 드래그하여 멀티 터치를 흉내 낼 수 있다.









반응형

'프로그래밍 놀이터 > iOS' 카테고리의 다른 글

[iOS Study] 디버그 도구  (0) 2016.02.26
[iOS Study] UIGestureRecognizer 와 UIMenuController  (0) 2016.02.25
[iOS Study] 카메라  (0) 2016.02.23
[iOS Study] UINavigationController  (0) 2016.02.22
[iOS Study] UITableView 편집  (0) 2016.02.21

댓글