핵심 요점
UIGestureRecognizer는 제스처 인식 프로세스를 개별 객체로 분해하는 UIKit의 추상 기본 클래스입니다. 각 서브클래스는 하나의 제스처 유형을 처리합니다. UITapGestureRecognizer는 특정 터치 수의 탭을 처리하고, UISwipeGestureRecognizer는 지정된 방향의 스와이프를 처리합니다. 개발자는 addGestureRecognizer(:) 메서드를 통해 UIView에 인식기를 추가하고, UIKit이 자동으로 터치를 추적하고 상태를 업데이트하며 인식 시 액션을 호출합니다.
UIGestureRecognizer(iOS 3.2, 2010) 이전에는 개발자가 UIResponder 메서드(touchesBegan, touchesMoved, touchesEnded)를 재정의하고 터치 궤적을 수동으로 분석했습니다. 이로 인해 코드 중복과 동시 터치 처리 시 오류가 발생했습니다. Apple은 이 로직을 UIGestureRecognizer에 캡슐화하여 멀티터치, 제스처 취소 및 하나의 뷰에서 여러 인식기의 동시 작동을 지원했습니다.
WWDC 세션에 따르면 Gesture Recognizer는 iPad에서 최대 11개의 동시 터치, iPhone에서 5개를 처리합니다. IT Sectr에서는 모든 UIKit 프로젝트에서 사용자 입력 처리를 위한 표준 방법으로 UIGestureRecognizer를 사용합니다. 이를 통해 수동 touchesBegan 추적과 관련된 버그가 제거되었습니다.
각 UIGestureRecognizer는 7가지 가능한 상태를 거칩니다. 이는 UIGestureRecognizer.State 열거형에 정의되어 있습니다. 이러한 상태는 터치 감지부터 완료 또는 취소까지의 인식 수명 주기를 반영합니다. 상태를 이해하는 것은 사용자 정의 인식기 구현 및 충돌 디버깅에 중요합니다.
| 상태 | 의미 | 발생 시점 |
|---|---|---|
| .possible | 초기 상태, 제스처가 아직 인식되지 않음 | 뷰에 추가된 직후 |
| .began | 제스처가 인식되고 실행 시작됨 | pan/longPress의 첫 손가락 움직임 시 |
| .changed | 제스처 매개변수 변경(좌표, 각도) | 손가락이 움직일 때마다 |
| .ended | 사용자가 손가락을 뗌, 제스처 완료 | touchesEnded 시 |
| .cancelled | 시스템에 의해 제스처 중단(수신 전화, 방향 변경) | touchesCancelled 시 |
| .failed | 조건에 따라 제스처가 인식되지 않음 | 인식 없이 touchesCancelled 시 |
| .recognized | .ended의 동의어, 제스처가 성공적으로 인식됨 | .ended와 동일 |
이산 제스처(탭, 스와이프)는 .possible에서 직접 .ended 또는 .failed로 전환됩니다. 연속 제스처(팬, 핀치, 회전, 길게 누르기)는 .possible → .began → .changed(반복) → .ended를 거칩니다. 액션 메서드에서 gestureRecognizer.state를 확인하여 제스처의 시작, 변경 및 끝을 구분할 수 있습니다.
UIKit은 7개의 내장 UIGestureRecognizer 서브클래스를 제공하며 대부분의 상호작용 시나리오를 커버합니다. 각 서브클래스에는 특정 설정이 있습니다: 탭의 numberOfTapsRequired, 스와이프의 direction, 길게 누르기의 minimumPressDuration 등입니다.
사용자 정의 제스처(예: 지그재그 그리기)의 경우 touchesBegan, touchesMoved, touchesEnded를 재정의하고 상태를 업데이트하는 UIGestureRecognizer 서브클래스를 만듭니다. Apple은 가능한 경우 내장 클래스를 사용할 것을 권장합니다. 최적화되어 있고 서로 올바르게 상호작용합니다.
하나의 UIView에서 여러 UIGestureRecognizer를 사용할 때(예: 탭과 더블 탭), 인식 충돌이 발생합니다. 더블 탭 시 단일 탭이 먼저 발동됩니다. Apple은 require(toFail:) 메서드를 제공하여 다른 제스처가 실패할 때까지 한 제스처의 인식을 지연시킵니다.
메커니즘은 다음과 같이 작동합니다: tapRecognizer.require(toFail: doubleTapRecognizer)를 호출하면 doubleTapRecognizer가 .failed로 종료된 후에만 tapRecognizer가 .recognized 상태로 전환되도록 지정합니다. 이로 인해 단일 탭 실행 전에 약 0.3초의 지연이 추가됩니다. 사용자가 두 번 탭하면 첫 번째 탭은 무시됩니다. 대안적인 접근 방식은 UIGestureRecognizerDelegate의 gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) 메서드를 사용하여 동시 인식을 허용하는 것입니다(예: 지도의 pan + pinch).
WWDC 2020에 따르면 UIKit 애플리케이션 버그의 약 15%가 제스처 충돌 설정 오류와 관련되어 있습니다. IT Sectr에서는 접근 방식을 표준화했습니다. 각 화면에 require(toFail:) 우선순위가 있는 제스처 스키마를 설정하여 더블 탭 발동 버그를 완전히 제거했습니다.
UIImageView에 단일 탭 핸들러를 추가합니다. 탭하면 이미지 투명도가 변경됩니다. 뷰에 제스처 바인딩을 보여주는 간단한 예제입니다.
import UIKit
class ImageViewController: UIViewController {
@IBOutlet private var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(
target: self,
action: #selector(handleTap(_:))
)
tap.numberOfTapsRequired = 1
imageView.addGestureRecognizer(tap)
imageView.isUserInteractionEnabled = true
}
@objc private func handleTap(_: UITapGestureRecognizer) {
UIView.animate(withDuration: 0.2) {
self.imageView.alpha = self.imageView.alpha == 1.0 ? 0.5 : 1.0
}
}
}
핵심 포인트: UIImageView의 isUserInteractionEnabled는 기본값이 false입니다. 이 플래그가 없으면 Gesture Recognizer가 터치를 받지 못합니다. UIView와 UIButton의 경우 이 플래그가 기본적으로 활성화됩니다. 액션 메서드는 UITapGestureRecognizer 매개변수를 받아들이며, location(in:)을 통해 탭 좌표를 확인할 수 있습니다.
이전 화면으로 돌아가기 위해 왼쪽 스와이프를 구현합니다. 방향 구성과 컨트롤러의 루트 뷰에 제스처 바인딩을 보여줍니다.
import UIKit
class DetailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let swipeLeft = UISwipeGestureRecognizer(
target: self,
action: #selector(handleSwipe(_:))
)
swipeLeft.direction = .left
view.addGestureRecognizer(swipeLeft)
}
@objc private func handleSwipe(_: UISwipeGestureRecognizer) {
navigationController?.popViewController(animated: true)
}
}
UISwipeGestureRecognizer는 이산 제스처입니다. 인식 후 즉시 .recognized로 전환되며 중간 .changed 상태가 없습니다. 따라서 액션에서 상태를 확인할 필요가 없습니다. 제스처가 인식되거나(액션 발동) 인식되지 않거나입니다. direction 속성은 .left, .right, .up, .down 네 가지 값 중 하나를 받습니다. 여러 방향을 지원하려면 각각 별도의 인식기를 만드세요.
충돌 없이 하나의 뷰에서 단일 탭과 더블 탭을 구성하는 방법을 보여줍니다. 더블 탭 인식기에 우선순위가 있으며 단일 탭은 더블 탭이 인식되지 않을 때만 발동됩니다.
import UIKit
class TapViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let singleTap = UITapGestureRecognizer(
target: self,
action: #selector(handleSingleTap)
)
singleTap.numberOfTapsRequired = 1
let doubleTap = UITapGestureRecognizer(
target: self,
action: #selector(handleDoubleTap)
)
doubleTap.numberOfTapsRequired = 2
singleTap.require(toFail: doubleTap)
view.addGestureRecognizer(singleTap)
view.addGestureRecognizer(doubleTap)
}
@objc private func handleSingleTap() {
print("Single tap — after 0.3s delay")
}
@objc private func handleDoubleTap() {
print("Double tap — instant")
}
}
require(toFail:)가 없으면 더블 탭 시 handleSingleTap이 먼저 발동된 후 handleDoubleTap이 발동되어 UX가 손상됩니다. require(toFail:)를 사용하면 단일 탭이 약 0.3초 대기하여 두 번째 탭이 오지 않는지 확인합니다. IT Sectr에서는 이미지 편집기와 갤러리에서 이 패턴을 사용하며, 더블 탭은 확대, 단일 탭은 요소 선택을 수행합니다.
자주 묻는 질문
네, UIView는 여러 UIGestureRecognizer 인스턴스를 동시에 지원합니다. 충돌을 해결하려면 인식 순서를 설정하는 require(toFail:) 메서드를 사용하세요. 제스처 병렬 작동(예: 지도의 pan + pinch)을 위해서는 true를 반환하는 gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) 델리게이트 메서드를 구현하세요.
UIGestureRecognizer는 터치 패턴을 자동으로 인식하고 상태를 관리하는 고수준 추상화입니다. touchesBegan은 UIResponder의 저수준 메서드로 수동 좌표 추적, 타이밍 및 터치 취소가 필요합니다. Gesture Recognizer는 더 간단하고 안정적이며 표준 제스처에 적합합니다. touchesBegan은 사용자 정의 그래픽에서만 사용해야 합니다.
네이티브 SwiftUI는 모디파이어( onTapGesture, onLongPressGesture, DragGesture, MagnificationGesture, RotationGesture)를 사용합니다. 이들은 SwiftUI 계층에 통합된 UIGestureRecognizer의 선언적 아날로그입니다. 필요한 경우 UIViewRepresentable을 통해 UIKit 인식기를 래핑할 수 있지만 Apple은 네이티브 SwiftUI 제스처를 사용할 것을 권장합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.