@GestureState와 offset을 활용해서 View 움직이기

SteadySlower·2022년 8월 21일
0

SwiftUI

목록 보기
28/64
post-custom-banner

예전에 DragGesture를 통해서 View를 움직이는 방법을 소개한 적이 있는데요. 그 때는 그냥 @State에 CGFloat를 할당해서 구현했었습니다. 하지만 좀 더 Gesture에 특화된 property wrapper가 있는데요. 바로 @GestureState입니다.

GestureState

@GestureState는 Gesture와 연결하는 특수한 @State 변수입니다. 이 변수를 DragGesture를 통해 업데이트하고 이 값을 View의 offset에 연결하여 View를 움직입니다.

import SwiftUI

struct MainView: View {
    @GestureState var dragPosition = CGSize.zero
    
    var body: some View {
        Image(systemName: "heart")
            .offset(dragPosition)
            .gesture(
                DragGesture()
                    .updating($dragPosition) { value, state, transaction in
                        state = value.translation
                    }
            )
    }
}

@GestureState 변수의 초기값은 CGSize.zero입니다. 따라서 View는 원래 위치에 존재합니다. 그리고 DragGesture에 updating이라는 메소드로 연결합니다. binding으로 연결해주어야 하고요. 어떤 방식으로 업데이트할 것인지 클로저로 정의해주어야 합니다.

해당 클로저는 3가지 인자를 받는데요. 첫 번째는 Gesture.Value 타입으로 드래그 동작에 대한 정보를 담고 있습니다. 두 번째는 State로 이 변수에 값을 할당하면 @GestureState의 값이 변화합니다. 마지막으로 Transaction인데요. Animation과 관련된 타입이라고 합니다. (다음에 좀 더 자세하게 알아보겠습니다.)

state에 value.translation을 할당합니다. 이렇게 하면 @GestureState 변수에 드래그의 시작 위치에서 현재 위치까지의 위치 변화가 CGSize 타입으로 할당이 됩니다. 그렇게 되면 이 변수는 .offset()으로 연결되어 있으므로 이미지가 드래그를 따라서 이동하게 됩니다.

보너스: 좌우로만 이동하기

좌우로만 이동할 수 있도록 구현하고 싶다면 어떻게 하면 될까요? 바로 아래처럼 state.width에만 value.translaction.width만 할당하면 됩니다. 이렇게 하면 CGSize의 width만 업데이트 되므로 위 아래로 움직이는 제스쳐는 무시되고 좌우 이동만 뷰에 반영이 됩니다.

import SwiftUI

struct MainView: View {
    @GestureState var dragPosition = CGSize.zero
    
    var body: some View {
        Image(systemName: "heart")
            .offset(dragPosition)
            .gesture(
                DragGesture()
                    .updating($dragPosition) { value, state, transaction in
                        state.width = value.translation.width
                    }
            )
    }
}

보너스 2: .updating에 사용할 함수 따로 빼기

위에 배웠던 클로저의 정의를 그대로 이용해서 함수를 별도로 구현해서 간단하게 사용할 수도 있습니다.
함수에 인자로 전달할 때는 참조를 전달하는 것이 아니라 값을 복사해서 전달합니다. 따라서 inout을 통해서 참조를 전달해야 할 인자는 참조를 전달할 수 있도록 합니다.

private func dragUpdating(_ value: _EndedGesture<DragGesture>.Value, _ state: inout CGSize, _ transaction: inout Transaction) {
    state.width = value.translation.width
}
someView
  .gesture(dragGesture
          .updating($dragAmount) { dragUpdating($0, &$1, &$2) }
  )
profile
백과사전 보다 항해일지(혹은 표류일지)를 지향합니다.
post-custom-banner

0개의 댓글