SwiftUI 제스쳐

개요

SwiftUI에서는 사용자가 특정 제스쳐를 하면 그거에 따라 다른 작업을 할 수 있다.

제스쳐를 알아보겠다.




Gestures


뷰에 제스쳐 modifier를 추가하면 제스쳐에 따라 반응할 수 있다.

탭, 드래그, pinches 다른 표준 제스쳐등을 인식 할 수 있다.

simultaneously(with:), sequenced(before:), or exclusively(before:) modifiers들로 커스텀 제스쳐들을 구성할 수 있다.




.gesture


터치와 같은 유저 입력을 처리하기 위한 모든 로직을 다룬다. 그리고 long press나 rotation과 같은 패턴들도 인식한다.

제스쳐를 인식하면 SwiftUI는 뷰의 상태를 업데이트하거나 특정 작업을 수행하는 callback을 실행한다.

callback을 우리나라말로 적당한게 뭘까

내가 추가한 각각의 제스쳐는 특정 뷰 계층에만 적용된다. .gesture(제스쳐)를 사용하면 된다.

1
2
3
4
5
6
let tap = TabGesture()
            .onEnded { _ in
                print("View tapped!")
            }
Circle()
    .gesture(tap)



callbacks


내가 제스쳐 modifier에 추가한 callback에 따라서 SwiftUI는 제스처 상태가 변경될 때마다 코드에 report back을(적용?) 한다.

.gesture는 변경사항을 감지하기 위해서 3가지 방법을 제공한다.

  • uptdating()
  • onChanged()
  • onEnded()



.udating()


영구적이지 않은 UI 상태를 업데이트한다.

제스쳐에 따라서 뷰의 상태를 업데이트 시키고 싶을 때 GestureState프로퍼티를 추가한다.

그리고 그것을 udating(_:body:)callback 안에서 업데이트 시키면된다.

SwiftUI는 값을 변경시키는 TapGesture를 제외한 제스쳐를 인식하자마자 udating callback을 호출한다.

@GestureState는 @State와 달리 읽기전용 속성이기에 그 값을 직접 변경할 수 없고 이 modifier를 사용해야한다.

GestureState는 제스처가 동작중일 때만 활용될 임시값을 저장하는 것으로 제스처가 종료된 이후에는 다시 초깃값으로 되돌린다.

1
2
3
4
5
6
7
8
9
@GestureState var isDetectingLongPress = false

let press = LongPressGesture(minimumDuration: 1)
                .updating($isDetectingLongPress) { 
                    currentState, gestureState, transaction in
                gestureState = currentState}
                }
Circle()
    .gesture(press)



.onChanged()


제스처를 하는중에 영구적인 상태로 만들고 싶을 때 사용한다.

제스처가 끝날 때 값을 리셋하지 않고 영구적으로 저장한다.

이것은 udating이후에 호출된다.

영구적으로 할 것이기 때문에 GestureState대신 State를 사용해 값을 저장한다.

1
2
3
4
5
6
7
8
9
@State var totalTabCount = 0

let press = LongPressGesture(minimumDuration: 1)
                .onchanged { _ in
                    self.totalTabCount += 1
                }

Circle()
    .gesture(press)



.onEnded()


제스쳐가 끝난것을 인식하고 제스처가 마지막에 가졌던 값을 검색하기 위해서 사용한다.

SwiftUI는 제스처가 끝났을 때만 호출한다.

영구적으로 바꾸기 위한것이므로 @State가 사용된다.




제스쳐 종류


제스쳐의 종류에는 탭, 길게누르기, 드래그, 확대, 로테이션이 있다.



TapGesture


탭. 0.1초 미만으로 터치한거

onTapGesture modifier를 사용하거나 TapGeture의 인스턴스를 사용해서 gesture의 매개변수로 넘기는 방법이 있다.

callback으로 onEnded만 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
Circle()
    .onTapGesture {
        print("tap")
    }

var tap: some Gesture{ 
    TapGesture(count: 2)
        .onEnded { 
            print("tap")
        }
}
Circle()
    .gesture(tap)



.LongPressGesture


길게 누르기. 0.5초보다 길게

onLongPressGesture를 사용하거나 LongpressGesture()인스턴스를 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
Circle()
    .onLongPressGesture {
        print("longPress")
    }

var longPress: some Gesture{  
    LongPressGesture()
        .onEnded { 
            print("longPress")
        }
}
Circle()
    .gesture(longPress)



.DragGesture


드래그.

DragGesture()인스턴스를 만든다.

minimumDistance로 최소 드래그 거리를 넣을 수 있고, coordinateSpace로 특정 좌표를 설정할 수 있다.

아래 예제에서 드래그 제스처는 최소 인식한 시점과 제스처의 상태가 바뀔때마다 updating을 한다.

@GestureState이므로 드래그가 끝나면 위치는 리셋된다.

1
2
3
4
5
6
7
8
9
10
11
@GestureState var translation: CGSize = .zero

var drag: some Gesture {
    DragGesture()
        .updating($translation) { value, state, _ in
            state = value.translation
        }
}
Circle()
    .offset(translation)
    .gesture(drag)



.MagnificationGesture


확대.

MagnificationGesture()인스턴스를 만든다.

1
2
3
4
5
6
7
8
9
10
11
@GestureState var scale: CGFloat = 1

var magnificant: some Gesture {
    MagnificationGesture()
        .updating($scale) { value, state, _ in
            state = value
        }
}
Circle()
    .scaleEffect(scale)
    .gesture(magnificant)



.RotationGesture


두손가락으로 회전시킬때 사용.

1
2
3
4
5
6
7
8
9
10
@GestureState var angle: Angle = .zero

var rotate: some Gesture {
    RotationGesture()
        .updating($angle) {}
}

Capsule()
    .rotationEffect(angle)
    .gesture(rotate)