소개

SwiftUI에서 일반 탭 외에 길게 눌렀을때의 동작을 어떻게 처리하는지 알아보겠습닌다. 먼저 Long Press라는 동작에 대한 이해가 선행되어야 합니다.

  • Long Press는 누르는 동안 동작이 지속되지 않습니다. minimum duration(최소 지속 시간)동안 press가 되었다면 계속 누르고 있다 하더라도 minimum duration 후에 최후의 동작 (onEnded)을 1회 실행하고 끝이 납니다.
  • 지금은 사용하지 않는 아이폰의 3D 포스 터치와 행동 양식이 같습니다. 누르는 강도를 시간(duration)으로 치환한 것 뿐입니다.

아래 그림을 보면 일반적인 minimum duration이 1초로 설정된 Long Press의 동작이 어떤 것인지 알 수 있습니다. 일반적인 Long Press는 아무리 길게 눌러도 1초가 지나면 동작이 끝납니다. 아래 Continous Press Button은 특수한 처리를 한 경우로 구현 방법은 후술합니다.

Animated GIF - Find & Share on GIPHY

 

Long Press 

some Gesture 타입의 longPress 계산된 변수를 SwiftUI View 구조체 내에 추가합니다.

@GestureState private var isDetectingLongPress = false

var longPress: some Gesture {
    LongPressGesture(minimumDuration: 1)
        .updating($isDetectingLongPress) { value, gestureState, _ in
            gestureState = value
        }.onEnded { value in
            print("LongPress OnEnded")
        }
}
  • @GestureState
    • 제스처 전용 State입니다. 나중에 if문 등에서 사용할 수 있습니다.
  • updating에 누르고 있을 때의 동작을 추가합니다.
    • updatingminimumDuration동안 지속됩니다.
  • onEnded에 동작을 완료했을 때, 즉 누른 채로 minimumDuration이 되었을 때 마무리해야 할 동작을 설정합니다.

 

위의 longPress는 제스처와 관련된 다양한 곳에서 사용할 수 있습니다.

VStack {
    Image(systemName: isDetectingLongPress ? "globe" : "pause.fill")
    Button {} label: {
        Text("Long Press Button")
    }.simultaneousGesture(longPress)
}

아래 그림에서 Long Press Button이 해당 예입니다.

Animated GIF - Find & Share on GIPHY

 

특수한 경우: Continuous Press

위의 예는 아무리 버튼을 길게 눌러도 minimum duration이 지나면 동작이 완료됩니다. 이것 대신 만약 버튼을 누르고 있을 때 updating이 지속되고 버튼을 떼야만 onEnded가 실행되는 것으로 하고 싶다면 어떻게 해야할까요?

sequenced gesture 기능을 이용해 Long Press 전에 Drag Gesture를 추가하면 이 문제를 해결할 수 있습니다.

@GestureState private var isDetectingContinuousPress = false

var continuousPress: some Gesture {
    LongPressGesture(minimumDuration: 0.1)
        .sequenced(before: DragGesture(minimumDistance: 0, coordinateSpace: .local))
        .updating($isDetectingContinuousPress) { value, gestureState, _ in
            switch value {
            case .second(true, nil):
                gestureState = true
                print("updating: Second")
            default:
                break
            }
        }.onEnded { value in
            switch value {
            case .second(_, _):
                print("onended: Second")
            default:
                break
            }
        }
}
  • sequenced before
    • DragGesture를 첫 번째(first) 제스처로 삽입합니다.
  • DragGesture
    • minimumDistance0으로 설정하면 길게 탭한 것도 드래그로 취급됩니다.
  • updating에서 case .second(First.Value, Second.value?)
    • 첫 번째 제스처가 실행되었다면(true) 두 번째 제스처(롱프레스)에서 실행할 작업을
    • case 내부에 지정합니다.
  • onEnded에서 case .second(First.Value, Second.value?)
    • 프레스를 종료했을 때 해야 할 작업을 지정합니다.

 

위의 continuousPress 제스처 또한 다양한 곳에서 사용할 수 있습니다.

VStack {
    Image(systemName: isDetectingContinuousPress ? "globe" : "pause.fill")
    Button {} label: {
        Text("Continuous Press Button")
    }.simultaneousGesture(continuousPress)
}

아래 그림에서 Continuous Press Button이 해당 예입니다.

Animated GIF - Find & Share on GIPHY

 

전체 코드


import SwiftUI
struct ContinuousLongPressView: View {
@GestureState private var isDetectingLongPress = false
var longPress: some Gesture {
LongPressGesture(minimumDuration: 1)
.updating($isDetectingLongPress) { value, gestureState, _ in
gestureState = value
}.onEnded { value in
print("LongPress OnEnded")
}
}
@GestureState private var isDetectingContinuousPress = false
var continuousPress: some Gesture {
LongPressGesture(minimumDuration: 0.1)
.sequenced(before: DragGesture(minimumDistance: 0, coordinateSpace: .local))
.updating($isDetectingContinuousPress) { value, gestureState, _ in
switch value {
case .second(true, nil):
gestureState = true
print("updating: Second")
default:
break
}
}.onEnded { value in
switch value {
case .second(_, _):
print("onended: Second")
default:
break
}
}
}
var body: some View {
VStack {
Image(systemName: isDetectingLongPress ? "globe" : "pause.fill")
Button {} label: {
Text("Long Press Button")
}.simultaneousGesture(longPress)
Divider()
Image(systemName: isDetectingContinuousPress ? "globe" : "pause.fill")
Button {} label: {
Text("Continuous Press Button")
}.simultaneousGesture(continuousPress)
}
// Bool 값이 변경되었을 때의 처리는 onChange에서
// .onChange(of: isDetectingLongPress) { newValue in
// print("onChange", newValue)
// }
}
}

 

출처

문의 | 코멘트 또는 yoonbumtae@gmail.com


카테고리: Swift


0개의 댓글

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다