모바일 제스처·햅틱 피드백: 터치 인터랙션의 물리적 감각 설계

UX 디자인

모바일 UX제스처 인터랙션햅틱 피드백iOS 터치Android 햅틱

이 글은 누구를 위한 것인가

  • 모바일 앱 제스처 UX를 처음 설계하는 디자이너
  • 햅틱 피드백을 어떤 상황에 써야 하는지 모르는 팀
  • 스와이프 삭제·롱프레스 메뉴 구현 방법을 알고 싶은 개발자

들어가며

좋은 모바일 UX는 눈으로만 피드백을 주지 않는다. 버튼을 눌렀을 때의 미세한 진동, 스와이프가 한계에 닿을 때의 탄성감 — 이 물리적 피드백이 앱을 "살아있게" 만든다.

이 글은 bluefoxdev.kr의 모바일 인터랙션 설계 를 참고하여 작성했습니다.


1. 제스처 패턴과 용도

[표준 제스처 패턴]

탭 (Tap):
  용도: 버튼, 링크, 선택
  주의: 최소 44×44pt 터치 영역 확보

더블 탭 (Double Tap):
  용도: 좋아요, 확대/축소 토글
  주의: 탭과 더블탭이 충돌하지 않게

롱프레스 (Long Press):
  용도: 컨텍스트 메뉴, 아이템 선택 모드
  기준: iOS 0.5초, Android 0.4초
  주의: 롱프레스 가능하다는 힌트 필요

스와이프 (Swipe):
  수평: 카드 넘기기, 리스트 항목 액션
  수직: 스크롤, 시트 닫기
  주의: 시스템 제스처와 충돌 피하기

핀치 (Pinch):
  용도: 확대/축소 (이미지, 지도)
  주의: 스크롤과 동시에 동작해야 함

드래그 (Drag):
  용도: 순서 변경, 슬라이더
  주의: 드래그 핸들 시각적으로 명확하게

[제스처 발견가능성 (Discoverability)]
  제스처는 발견이 어려움 → 힌트 필요
  - 리스트 아이템: 스와이프 힌트 애니메이션
  - 롱프레스: 첫 방문 시 코치마크
  - 상단 시트: 핸들 바(drag indicator) 표시

2. 햅틱 피드백 구현

// iOS 햅틱 피드백 사용 가이드
import UIKit

class HapticManager {
    
    // 가벼운 탭 피드백 (버튼 클릭, 토글)
    static func light() {
        let generator = UIImpactFeedbackGenerator(style: .light)
        generator.prepare()
        generator.impactOccurred()
    }
    
    // 중간 강도 (카드 스와이프 임계점)
    static func medium() {
        let generator = UIImpactFeedbackGenerator(style: .medium)
        generator.prepare()
        generator.impactOccurred()
    }
    
    // 강한 피드백 (삭제 확인, 중요 액션)
    static func heavy() {
        let generator = UIImpactFeedbackGenerator(style: .heavy)
        generator.prepare()
        generator.impactOccurred()
    }
    
    // 성공 피드백 (결제 완료, 주문 확인)
    static func success() {
        let generator = UINotificationFeedbackGenerator()
        generator.prepare()
        generator.notificationOccurred(.success)
    }
    
    // 오류 피드백 (결제 실패, 입력 오류)
    static func error() {
        let generator = UINotificationFeedbackGenerator()
        generator.prepare()
        generator.notificationOccurred(.error)
    }
    
    // 선택 피드백 (피커 스크롤)
    static func selection() {
        let generator = UISelectionFeedbackGenerator()
        generator.prepare()
        generator.selectionChanged()
    }
}

// 사용 예시
class AddToCartButton: UIButton {
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        HapticManager.medium()  // 장바구니 담기 시 중간 강도
    }
}

3. 스와이프 삭제 패턴

// SwiftUI 리스트 스와이프 액션
struct OrderListView: View {
    @State private var orders: [Order] = []
    
    var body: some View {
        List {
            ForEach(orders) { order in
                OrderRowView(order: order)
                    .swipeActions(edge: .trailing, allowsFullSwipe: false) {
                        // 삭제 액션
                        Button(role: .destructive) {
                            HapticManager.medium()
                            withAnimation {
                                deleteOrder(order)
                            }
                        } label: {
                            Label("취소", systemImage: "trash")
                        }
                        
                        // 보조 액션
                        Button {
                            HapticManager.light()
                            archiveOrder(order)
                        } label: {
                            Label("보관", systemImage: "archivebox")
                        }
                        .tint(.orange)
                    }
            }
        }
    }
}

마무리

햅틱 피드백의 원칙은 "의미 있는 순간에만 사용"이다. 모든 버튼에 햅틱을 넣으면 무감각해진다. 결제 완료·오류·중요한 상태 변경에만 써야 의미가 있다. iOS에서는 UIFeedbackGenerator.prepare()를 미리 호출해 지연을 없애는 것이 중요하다.