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

[iOS Study] UIGestureRecognizer 와 UIMenuController

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

 [iOS Study] UIGestureRecognizer 와 UIMenuController


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


addGestureRecognizer:, becomeFirstResponder, canBecomeFirstResponder, cancelsTouchesInView, canPerformAction:withSender:, CGPoint, CGPointZero, cut:, delaysTouchBegan, delaysTouchesEnded, delayTouchesBegan, delegate, first responder, gesture, gesture recognizer, gesturerecognizer, gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer, initWithTarget:action:, initWithTitle:action:, intercept, ios study, ios tutorial, locationInView, MenuItems, minimumPressDuration, numberOfTapsRequired, override, panning, pinch, protocol, requireGestureRecognizerToFail, requireGestureRecognizerToFail:, return yes, setMenuVisible:animated:, setTargetRect:inView:, setTranslation:inView:, sharedMenuController, Singleton, state, swipe, target action, touchesBegan:withEvent, touchesCancelled:withEvent, translationInView:, tutorial, UIGestureRecognizer, UIGestureRecognizerDelegate, UIGestureRecognizerStateBegan, UIGestureRecognizerStateCancelled, UIGestureRecognizerStateChanged, UIGestureRecognizerStateEnded, UIGestureRecognizerStateFailed, UIGestureRecognizerStatePossible, UIGestureRecognizerStateRecognized, UILongPressGestureRecognizer, UIMenuController, UIMenuItem, UIPanGestureRecognizer, UIPinchGestureRecognizer, uiresponder, UIResponderStandardEditActions, UIRotationGestureRecognizer, UISwipeGestureRecognizer, UITextField, [iOS Study] UIGestureRecognizer 와 UIMenuController, 가로채기, 가용 상태, 기본 메뉴 아이템, 기본값, 메뉴 컨트롤러, 메시지, 변경 상태, 복사하기, 붙이기, 뷰, 스와이프, 시작 상태, 실패, 실패 상태, 싱글톤, 아론 힐리가스, 액션 메시지, 오려두기, 윈도우, 의존성, 이벤트, 인식 상태, 재정의, 정교한 제어, 제스처, 제스처 발생 좌표, 제스처 인식기, 종료 상태, 취소, 취소 상태, 타깃-액션, 타깃-액션 쌍, 터치, 터치 이벤트, 패닝, 패턴, 퍼스트 리스폰더, 편집, 프로토콜, 핀치


-

때때로 핀치나 스와이프와 같은 제스처를 만들기 위해 특정 패턴의 터치를 감지해야 한다.

이 때 UIGestureRecognizer 인스턴스를 사용할 수 있다.



-

UIGestureRecognizer 는 뷰의 방식에 따라 처리되는 터치를 가로챈다.


UIGestureRecognizer 가 특정 제스처를 인식하면 선택한 객체에 메시지를 보낸다.



-

UIGestureRecognizer 자체는 인스턴스를 만들 수 없다.

대신에 UIGestureRecognizer 는 많은 하위 클래스를 가지며 그 각각은 특정 제스처를 인식하는 역할을 한다.


UIGestureRecognizer 하위 클래스의 인스턴스를 사용하려면 그 인스턴스에 타깃-액션쌍을 전달하고 뷰에 연결해야 한다.

제스처 인식기가 뷰에서 제스처를 인식할 때마다 액션 메시지는 타깃에 전해진다.

모든 UIGestureRecognizer 액션 메시지는 다음과 같은 형태를 가진다.


- (void)action:(UIGestureRecognizer *)gestureRecognizer;



-

제스처 인식기가 제스처를 인식하면 뷰로 가야 할 터치를 가로챈다.

따라서 그 뷰는 보통 touchesBegan:withEvent: 와 같은 UIResponder 메시지를 받지 못하게 된다.



-

touchesBegan:withEvent: 메시지가 보내지는 것을 지연시키도록 UIGestureRecognizer 에 요청할 수 있다.


UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];

doubleTapRecognizer.numberOfTapsRequired = 2;

doubleTapRecognizer.delayTouchesBegan = YES;

[self addGestureRecognizer:doubleTapRecognizer];



-

제스처 인식기는 특정 제스처가 발생하면 터치 이벤트를 검사하는 것으로 작동한다.

제스처를 인식하기 전에는 모든 UIResponder 메시지는 뷰에 일반적인 형태로 전해진다.

그리고 기본적으로는 제스처를 인식하면 touchesCancelled:withEvent: 메시지가 뷰에 보내진다.



-

여러 개의 제스처 인식기를 가진 상황에서 특정 작업을 위해 실행할 다른 제스처 인식기를 원하는 경우는 흔한 일이다.

이럴 경우에 “잠깐 기다려, 이 제스처는 내 거야!”와 같은 식의 인식기들 사이의 의존성을 설정해야 한다.


tapRecognizer 가 반드시 doubleTapRecognizer 의 실패를 기다리도록 만들어야 한다.

싱글 탭이 더블 탭의 일부가 될 수도 있기 때문이다.


[tapRecognizer requireGestureRecognizerToFail:doubleTapRecognizer];



-

모든  UIGestureRecognizer 는 locationInView: 메소드를 가진다.

이 메시지를 제스처 인식기에 보내면 인자로 전달된 뷰의 좌표계에서 제스처가 발생한 좌표를 전달할 것이다.



-

UIMenuController 라는 내장 클래스가 있다.

메뉴 컨트롤러는 UIMenuItem 객체의 목록을 가지고 기존 뷰에 나타낸다.

각 항목은 제목(메뉴에서 보이는)과 액션(윈도우의 퍼스트 리스폰더에 보내지는 메시지)을 가진다.



-

UIMenuController 는 프로그램에 하나만 존재한다.

이 인스턴스를 나타내길 원하면 메뉴 아이템을 채워 넣고 표시할 영역을 주고 보이도록 설정해야 한다.


[self becomeFirstResponder];

UIMenuController *menu = [UIMenuController sharedMenuController];

UIMenuItem *deleteItem = [[UIMenuItem alloc] initWithTitle:@“Delete” action:@selector(deleteLine:)];

menu.menuItems = @[deleteItem];


[menu setTargetRect:CGRectMake(point.x, point.y, 2, 2) inView:self];

[menu setMenuVisible:YES animated:YES];







-

표시할 메뉴 컨트롤러를 위해, UIMenuController 의 메뉴 아이템에서 액션 메시지에 응답하는 뷰는 반드시 윈도우의 퍼스트 리스폰더여야 한다.

만약 퍼스트 리스폰더가 돼야 할 커스텀 뷰 클래스를 가지고 있다면 canBecomeFirstResponder 메소드를 재정의해야 한다.

return YES; 를 하도록



-

메뉴를 표시할 때, 메뉴 컨트롤러는 각 메뉴 아이템을 살펴보고 퍼스트 리스폰더가 그 아이템에 관한 액션 메시지를 구현했으면 그 퍼스트 리스폰더를 요청한다. 만약 그 퍼스트 리스폰더가 메소드를 구현하지 않았으면 메뉴 컨트롤러는 관련 메뉴 아이템을 보여주지 못할 것이다. 퍼스트 리스폰더에 의해 구현된 액션 메시지를 가진 메뉴 아이템이 하나도 없다면 적어도 그 메뉴는 보이지 않는다.



-

UILongPressGestureRecognizer 와 UIPanGestureRecognizer 가 있다.

선을 오래 누르고 있으면 long press, 그 선이 선택되고 손가락으로 끌어 주변으로 드래그하면 pan.



-

터치가 롱 프레스가 되려면 0.5초동안 누르고 있어야 한다.

하지만 원한다면 제스처 인식기의 minimumPressDuration 값을 변경할 수 있다.



-

롱 프레스는 일정 시간 동안 발생하고 세 가지 이벤트로 정의된 제스처이다.

이들 이벤트 각각은 제스처 인식기의 state 프로퍼티에 변화를 가져온다.

롱 프레스 인식기의 state 는 UIGestureRecognizerStatePossible 에서 UIGestureRecognizerStateBegan 으로,

그리고 마지막으로 UIGestureRecognizerStateEnded 로 전환된다.



-

제스처 인식기가 가용 상태에서 다른 상태로 전환되면 자신의 타깃에 액션 메시지를 보낸다.

이는 롱 프레스 인식기의 타깃이 롱 프레스가 시작할 때와 끝날 때 같은 액션 메시지를 받는다는 것을 의미한다.



-

화면에서 손가락 움직임을 인식하기 위한 제스처 인식기가 필요하다.

이 제스처는 패닝(panning)이라 하고 제스처 인식기의 하위 클래스인 UIPanGestureRecognizer 를 사용한다.



-

일반적으로 제스처 인식기는 자신이 가로챈 터치를 공유하지 않는다.

제스처를 인식하고 나면 그 터치를 삼킨다(터치가 소멸된다)

따라서 다른 제스처 인식기는 그 터치를 처리할 기회가 없다.



-

UIGestureRecognizerDelegate 프로토콜에는 많은 메소드가 있다.


제스처 인식기는 자신의 제스처를 인식하면 이 메시지를 델리게이트에 보낸다.

또한 그 제스처를 다른 제스처 인식기가 인식했다는 것을 알아챌 수도 있다.

만약 gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: 메소드가 YES 를 반환하면 다른 제스처 인식기와 터치를 공유할 수 있다.



-

손가락이 움직이기 시작하면 팬 인식기는 시작 상태로 전환된다.

팬 제스처 인식기는 변경(changed) 상태를 제공한다.

팬 인식기는 손가락이 움직이기 시작하면 시작 상태로 진입하고 타깃에 메시지를 보낸다.

손가락이 화면에서 움직이는 동안, 인식기는 변경 상태로 바뀌고 그 액션 메시지가 타깃에 반복적으로 보내진다.

마지막으로 손가락이 화면에서 떨어지면 인식기의 상태는 종료로 바귀고 타깃에 마지막 메시지가 전달된다.



-

UIPanGestureRecognizer 메소드는 인자로 전달된 뷰의 좌표계상에서 팬이 얼마나 움직였는지 CGPoint 단위로 반환한다.

팬 제스처가 시작되면 이 프로퍼티는 제로 포인트(x 와 y 모두 0)으로 설정된다.

이 값은 팬의 움직임에 따라 갱신된다.

만약 팬이 매우 멀리 오른쪽으로 가면 높은 x값을 가진다.

팬이 시작된 곳으로 돌아가면 그 값은 제로 포인트로 돌아간다.


CGPoint translation = [gr translationInView:self];

[gr setTranslation:CGPointZero inView:self];



-

cancelsTouchesInView 프로퍼티는 기본값으로 YES 이다.

이것은 제스처 인식기가 인식한 터치를 먹는다는 것을 의미한다.

그래서 뷰는 touchesBegan:withEvent: 와 같은 일반적인 UIResponder 메소드를 통해 처리할 기회가 없을 것이다.


cancelsTouchesInView 프로퍼티를 NO 로 설정하면,

제스처 인식기가 인식한 터치는 UIResponder 메소드를 통해 뷰에 전달된다.

이것은 인식기와 뷰의 UIResponder 메소드 둘 다 같은 터치를 처리할 수 있도록 한다.



-

UIMenuController 는 보통 사용자에게 “편집” 메뉴가 표시될 때 이를 보여주는 역할을 한다.

변경되지 않은 메뉴 컨트롤러는(메뉴 아이템을 설정하지 않은 것) 오려두기, 복사하기, 붙이기 등과 같은 기본 메뉴 아이템을 가지고 있다.

각 아이템은 액션 메시지와 연결되어 있다.

“오려두기” 메뉴 아이템이 선택되면 메뉴 컨트롤러가 존재하는 뷰에 cut: 메시지가 보내진다.


모든 UIResponder 인스턴스는 이들 메소드를 구현한다.

하지만 기본적으로 이들 메소드는 아무것도 하지 않는다.

UITextField 처럼 UIResponder 의 하위 클래스들은 이들 메소드를 필요에 따라 적절하게 재정의한다.

이 메소드들은 모두 UIResponderStandardEditActions 프로토콜에 선언된다.







-

만약 뷰에서 UIResponderStandardEditActions 의 메소드를 재정의하면 그 메뉴 아이템은 뷰에서 특정 메뉴에 자동으로 나타날 것이다.

이것은 메뉴 컨트롤러가 canPerformAction:withSender: 메시지를 자신의 뷰에 보내기 때문에 동작한다.

뷰가 구현한 메소드의 내용에 따라 YES 나 NO를 반환한다.



-

만약 이들 메소드 중에 하나를 메뉴에 나타나지 않도록 구현하려면 canPerformAction:withSender: 메소드를 NO 를 반환하도록 재정의한다.


- (BOOL)canPerformAction:(SEL)action withSender:(id)sender{

     if (action == @selector(copy:))

          return NO;

     return [super canPerformAction:action withSender:sender];

}



-

제스처 인식기가 뷰에 있으면 실제로 touchesBegan:withEvent: 와 같은 모든 UIResponder 메소드를 처리해준다.

제스처 인식기들은 꽤나 욕심쟁이이기 때문에 일반적으로 뷰가 터치 이벤트를 받지 못하게 하고

이벤트를 전달하는 지연을 최소화해주지도 않는다.

이러한 행동을 바꾸기 위해 인식기에서 delaysTouchBegan, delaysTouchesEnded, cancelsTouchesInView 와 같은 프로퍼티를 설정할 수 있다.

이러한 양자 택일의 접근 방식보다 정교한 제어가 필요하다면 인식기의 델리게이트 메소드를 구현할 수 있다.



-

때로는 유사한 제스처를 찾기 위해 두 제스처 인식기를 가져야 할지 모른다.

실패해야 할 것과 시작할 인식기를 가지고 requireGestureRecognizerToFail: 메소드를 사용할 때 이들 인식기를 묶을 수 있다.


이 때 마스터 제스처 인식기가 상태를 해석하는 방법을 반드시 이해해야 한다.

인식기는 전부 일곱 가지 상태에 진입할 수 있다.


UIGestureRecognizerStatePossible

UIGestureRecognizerStateFailed

UIGestureRecognizerStateBegan

UIGestureRecognizerStateChanged

UIGestureRecognizerStateCancelled

UIGestureRecognizerStateRecognized

UIGestureRecognizerStateEnded



-

인식기는 대부분의 시간을 가용 상태에 머물러 있는다.

인식기가 자신의 제스처를 인식하면 시작 상태로 바뀐다.

만약 제스처가 팬처럼 계속할 수 있는 것이라면 종료될 때까지 변경 상태에 머문다.

어떤 프로퍼티가 변하면 타깃에 다른 메시지를 보낸다.

제스처가 끝나면(보통 손가락을 뗄 때) 종료 상태에 진입한다.



-

모든 인식기가 시작, 변경, 종료되는 것은 아니다.

탭처럼 분리된 제스처의 인식기에서는 인식 상태(종료 상태와 동일한 값을 가진다)만 볼 수 있을 것이다.



-

끝으로 인식기는 취소(전화가 걸려오는 등)나 실패(손가락이 일그러져 특정 제스처를 만들 수 없는 등)할 수 있다.

이러한 상태로 전환되면 인식기의 액션 메시지를 보내지고 이유를 확인하기 위해 상태 프로퍼티를 검사할 수 있다.



-

UIPinchGestureRecognizer, UISwipeGestureRecognizer, UIRotationGestureRecognizer 도 있다.

이들 인식기는 그 동작을 미세하게 조율하는 프로퍼티들을 가지고 있다.



-

UIGestureRecognizer 의 내장 하위 클래스에 의해 구현되지 않은 제스처를 인식하게 하려면

UIGestureRecognizer 의 하위 클래스를 만들면 된다.

이 작업은 굉장히 어렵다.

UIGestuerRecognizer 문서의 Subclassing Notes 를 읽어보길 바란다.








반응형

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

[iOS Study] 오토 레이아웃 소개  (0) 2016.03.03
[iOS Study] 디버그 도구  (0) 2016.02.26
[iOS Study] 터치 이벤트와 UIResponder  (0) 2016.02.24
[iOS Study] 카메라  (0) 2016.02.23
[iOS Study] UINavigationController  (0) 2016.02.22

댓글