[회고] 신입 iOS 개발자가 되기까지 feat. 카카오 자세히보기

🍎 Apple/UIKit

[UIKit] Multiple Gesture Recognizer 처리하기

inu 2022. 2. 5. 23:06
반응형

안녕하세요 이누입니다. 🐶

 

하나의 View에 대해 여러 Gesture Recognizer가 등록되어 있는 경우 원하는 대로 처리되지 않는 경우가 종종 있습니다. 특정 Gesture로 인해 먼저 처리해야 될 다른 Gesture가 무시되는 경우도 많고, 동시에 처리되길 원하는 Gesture가 동시에 동작하지 않는 경우가 그 대표적인 예시입니다.

 

따라서 오늘은 애플 개발자 문서에 등록된 Article인 Coordinating Multiple Gesture Recognizers를 기반으로 하나의 View에 등록된 여러 Gesture Recognizer들의 작업을 원하는대로 처리하는 방법을 알아보겠습니다. 


OverView

Gesture recognizer는 입력된 터치 이벤트를 개별적으로 추적하지만, UIKit은 일반적으로 하나의 View에서 한 번에 하나의 Gesture만 인식하도록 허용합니다. 일반적으로 한 번에 하나의 Gesture만 인식하도록 하는 것은 둘 이상의 작업이 동시에 트리거되는 것을 방지하기 때문에 더 선호됩니다. 하지만 이는 때때로 의도치 않은 부작용을 일으킬 수 있습니다. 예를 들어 Pan Gesture Recognizer와 Swipe Gesture Recognizer가 모두 추가된 View에서는 Swipe Gesture가 인식되지 않는 경우입니다. Pan Gesture가 continuous 하여 항상 Swipe Gesture보다 먼저 Gesture를 인식하기 때문입니다.

 

이런 의도하지 않은 부작용을 방지하기 위해서는 Delegate 객체를 사용하여 특정 순서대로 Gesture를 인식하도록 설정해야 합니다. Delegate 객체의 내부 메서드를 사용하여 특정 Gesture recognizer가 다른 Gesture recognizer 앞에 혹은 뒤에 와야 하는지 결정할 수 있습니다. 또한 여러 Gesture를 동시에 인식하도록 설정할 수도 있습니다.


Preferring One Gesture Over Another

먼저 Gesture Recognizer Delegate 객체를 활용해 gesture들의 인식 순서를 결정하는 방법을 알아봅시다.

 

잠재적 충돌이 예상되는 두 개의 Gesture Recognizer가 있을 때, UIGestureRecognizerDelegate protocol을 준수한 Delegate 객체 하나만 있으면 이들 간의 충돌을 정리할 수 있습니다. 해당 객체에 적절한 메서드를 구현하고 할당해주면 됩니다.

 

예시를 봅시다.

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, 
         shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
   // Don't recognize a single tap until a double-tap fails.
   if gestureRecognizer == self.tapGesture && 
          otherGestureRecognizer == self.doubleTapGesture {
      return true
   }
   return false
}

일반적으로 기본 Tap Gesture는 Double Tap Gesture보다 먼저 인식됩니다. 하지만 위 예시에서는 delegate 내부에 gestureRecognizer(_:shouldRequireFailureOf:) 메서드를 구현해서 Double Tap Gesture Recognizer가 Tap Gesture Recognizer보다 먼저 작동하도록 했습니다. 해당 메서드가 구현된 delegate는 나중에 확인되어야 하는 Gesture Recognizer인 기본 Tap Gesture Recognizer에 할당되어 작동합니다.

gestureRecognizer(_:shouldRequireFailureOf:) 메서드는 해당 Gesture Recognizer가 작동되기 전에 다른 Gesture Recognizer의 실패 여부를 확인해야 하는지를 묻습니다. 따라서 해당 메서드에서 true를 반환하면 otherGestureRecognizer가 실패되기를 기다린 다음 작업을 수행합니다. 하지만 그렇다고 해서 false를 반환하면 무조건 otherGestureRecognizer의 실패를 기다리지 않고 바로 처리되는 것은 아닙니다. 다른 Gesture Recognizer에서 다른 방식으로 실패 요구사항을 구현했을 수 있기 때문입니다.

 

비슷한 작업을 처리할 수 있는 다른 메서드도 있는데요, 다음 예시를 봅시다.

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, 
         shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
   // Do not begin the pan until the swipe fails. 
   if gestureRecognizer == self.swipeGesture && 
          otherGestureRecognizer == self.panGesture {
      return true
   }
   return false
}

Pan Gesture는 continuous 하기 때문에 Swipe Gesture의 동작을 막습니다. 하지만 위 예시에서는 gestureRecognizer(_:shouldBeRequiredToFailBy:) 메서드를 활용해서 swipe gesture가 pan gesture보다 먼저 인식되도록 하고 있습니다. 해당 메서드가 구현된 delegate는 먼저 확인되어야 하는 Gesture Recognizer인 swipe gesture에 할당되어 작동합니다.

gestureRecognizer(_:shouldBeRequiredToFailBy:) 메서드는 해당 Gesture Recognizer가 실패할 때까지 다른 Gesture Recognizer가 동작하지 않아야 하는지 여부를 묻습니다. 따라서 true를 반환하면 otherGestureRecognizer는 해당 Gesture Recognizer가 실패되도록 기다린 다음 작업을 수행합니다. 하지만 gestureRecognizer(_:shouldRequireFailureOf:)의 경우와 마찬가지로 false를 반환했다고 무조건 다른 Gesture Recognizer의 실패를 기다리지 않고 바로 처리되는 것은 아닙니다. 다른 Gesture Recognizer에서 다른 방식으로 실패 요구사항을 구현했을 수 있기 때문입니다.

 

물론 gestureRecognizer(_:shouldRequireFailureOf:)로도 같은 동작을 처리하게 만들 수 있습니다.

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, 
         shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
   if gestureRecognizer == self.panGesture && 
          otherGestureRecognizer == self.swipeGesture {
      return true
   }
   return false
}

gestureRecognizer(_:shouldRequireFailureOf:)로 delegate를 구성해서 이를 나중에 확인되어야 하는 gesture인 Pan Gesture Recognizer에 할당하면 됩니다.

 

두 메서드는 어디에 Delegate 객체를 할당하느냐의 차이정도로 보면 좋을 것 같습니다.


Allowing the Simultaneous Recognition of Multiple Gestures

이제 다음으로 Gesture Recognizer Delegate 객체를 활용해 여러 Gesture들을 한 번에 처리하는 방법을 알아보겠습니다. 먼저 아래 그림을 살펴봅시다.

이 그림에 보이는 3개의 View(yellowView, cyanView, magentaView)는 모두 각자의 Pan Gesture Recognizer, Pinch Gesture Recognizer, Rotate Gesture Recognizer를 보유하여 드래그되고, 크기가 조절되며, 회전됩니다. 각 Gesture Recognizer는 다른 Gesture Recognizer에 의해 event가 충돌하여 정상적으로 처리되지 않고 무시될 수 있습니다. 앞서 설명한 Preferring One Gesture Over Another의 예시와 마찬가지로 UIGestureRecognizerDelegate protocol을 준수한 Delegate 객체의 메서드를 활용하면 이들 간의 충돌을 정리할 수 있습니다.

 

아래 예제 코드를 봅시다.

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
       shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer)
        -> Bool {
   // If the gesture recognizer's view isn't one of the squares, do not
   // allow simultaneous recognition.
   if gestureRecognizer.view != self.yellowView && 
            gestureRecognizer.view != self.cyanView && 
            gestureRecognizer.view != self.magentaView {
      return false
   }
   // If the gesture recognizers are on diferent views, do not allow
   // simultaneous recognition.
   if gestureRecognizer.view != otherGestureRecognizer.view {
      return false
   }
   // If either gesture recognizer is a long press, do not allow
   // simultaneous recognition.
   if gestureRecognizer is UILongPressGestureRecognizer || 
          otherGestureRecognizer is UILongPressGestureRecognizer {
      return false
   }

   return true
}

gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) 메서드를 구현했습니다. gestureRecognizer의 view가 원하는 View(yellowView, cyanView, magentaView)가 아니라면 false를 반환합니다. 또한 하나의 view에 등록된 gesture가 아니라면 false를 반환합니다. 또한 둘 중 하나가 long press gesture recoginzer라면 false를 반환합니다. 이 외의 경우 true를 반환합니다.

gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) 메서드는 두 개의 Gesture Recognizer의 인식을 허용할 것인지 여부를 묻습니다. true를 반환하면 허용하고 false를 반환하면 허용하지 않습니다. 이때 주의해야 할 점은 true를 반환하면 동시 인식이 보장되지만, false를 반환한다고 무조건 동시 인식이 방지되지는 않는다는 것입니다. 다른 Gesture Recognizer의 delegate에서 true를 반환하면 이를 보장받을 수 없기 때문입니다.


Summary

이제 Gesture Recognizer Delegate의 각 메서드의 역할을 정리해봅시다.

  • gestureRecognizer(_:shouldRequireFailureOf:): 해당 Gesture Recognizer의 작업을 다른 Gesture Recognizer의 작업이 실패 처리될 때까지 대기하도록 할 수 있다.
  • gestureRecognizer(_:shouldBeRequiredToFailBy:): 다른 Gesture Recognizer의 작업이 해당 Gesture Recognizer의 작업이 실패 처리될 때까지 대기하도록 할 수 있다.
  • gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:): 해당 Gesture Recognizer의 작업이 다른 작업들과 동시에 처리될 수 있도록 할 수 있다.

이들을 활용하면 앞으로 하나의 View에 여러 Gesture를 등록해도 작업을 의도에 맞게 처리할 수 있겠네요!

 

감사합니다👍 (오류 및 오타 지적 환영합니다!) 

반응형