[iOS Study] UIGestureRecognizer 와 UIMenuController |
출처 : 아론 힐리가스의 iOS 프로그래밍
-
때때로 핀치나 스와이프와 같은 제스처를 만들기 위해 특정 패턴의 터치를 감지해야 한다.
이 때 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 |
댓글