[SwiftUI] DragGesture

Junyoung Park·2022년 8월 18일
0

SwiftUI

목록 보기
4/136
post-thumbnail
post-custom-banner

How to use DragGesture to move, drag, swipe in SwiftUI | Continued Learning #4

DragGesture

  • 탭 제스처와 함께 가장 자주되는 제스처 중 하나. 특정 오브젝트를 move, drag, swipe하는 데 사용되는 제스처. 커스텀 모달 뷰를 구현하거나 카드 뷰 스와이프를 할 때에도 사용되는 것 같다.

구현 목표 1

  • 카드 뷰를 드래그했을 때 이동한다.
  • 좌측, 우측 경계까지 갔을 때 카드 뷰가 작아진다.
  • 좌측, 우측으로 이동할 때 조금씩 그 방향으로 회전한다.

구현 태스크

  1. 드래그 제스처를 감지해 현재 오브젝트의 절대적 위치를 알아낸다.
  2. 드래그 제스처를 따라 오브젝트의 위치를 offset으로 변경한다.
  3. scalEffect, rotationEffect는 각각 확대/축소의 크기, 회전 효과를 주자.
  4. 드래그 제스처가 끝났을 때 다시 상태를 원상복구한다.

핵심 코드

                .offset(currentOffset)
                .scaleEffect(currentScaleAmount)
                .rotationEffect(currentRotationAngle)
                .gesture(
                    DragGesture()
                        .onChanged({ value in
                            withAnimation(.spring()) {
                                currentOffset = value.translation
                            }
                        })
                        .onEnded({ value in
                            withAnimation(.spring()) {
                                currentOffset = .zero
                            }
                        })
            )

소스 코드

import SwiftUI

// DRAG GESTURE -> MAKE IT SWIPABLE USING DRAG GESTURE
struct DragGestureBootCamp: View {
    @State private var currentOffset: CGSize = .zero
    var currentScaleAmount: CGFloat {
        let max = UIScreen.main.bounds.width / 2
        let currentAmount = abs(currentOffset.width)
        let percentage = currentAmount / max
        return 1.0 - min(percentage, 0.5) * 0.5
    }
    var currentRotationAngle: Angle {
        let max = UIScreen.main.bounds.width / 2
        let currentAmount = currentOffset.width
        let percentage = currentAmount / max
        let percentageAsDouble = Double(percentage)
        let maxAngle = 10.0
        return Angle(degrees: percentageAsDouble * maxAngle)
    }
    var body: some View {
        ZStack {
            
            VStack {
                Text("\(currentOffset.width)")
                Spacer()
            }
            RoundedRectangle(cornerRadius: 20)
                .frame(width: 300, height: 500)
                .offset(currentOffset)
                .scaleEffect(currentScaleAmount)
                .rotationEffect(currentRotationAngle)
                .gesture(
                    DragGesture()
                        .onChanged({ value in
                            withAnimation(.spring()) {
                                currentOffset = value.translation
                            }
                        })
                        .onEnded({ value in
                            withAnimation(.spring()) {
                                currentOffset = .zero
                            }
                        })
            )
        }
    }
}

구현 화면 1

구현 목표 2

  • 밑에서 페이지를 드래그해서 올렸을 때 특정 지점까지 올리게 되면 한 번에 올라간다.
  • 특정 지점까지 올리지 못했다면 다시 내려간다.
  • 애니메이션을 통해 위로/아래로 자연스럽게 움직인다.

구현 태스크

  1. 드래그 제스처가 끝나는 시점의 오브젝트 위치가 해당 기준에 다다랐는지 검사한다.
  2. 페이지가 아래에서 올라오고 / 해당 기준보다 위에 있다면 곧바로 페이지를 업시킨다.
  3. 페이지가 위에서 내려가고 / 해당 기준보다 아래에 있다면 곧바로 페이지를 다운시킨다.
  4. 이밖의 요소는 기준을 만족하지 못했기 때문에 기존의 상태(위, 아래)로 원상복구한다.

핵심 코드

.onEnded({ value in
                            withAnimation(.spring()) {
                                if currentDragOffsetY < -150 {
                                    endingOffsetY = -startingOffsetY
                                    currentDragOffsetY = .zero
                                } else if endingOffsetY != 0 && currentDragOffsetY > 150 {
                                    endingOffsetY = .zero
                                    currentDragOffsetY = .zero
                                } else {
                                    currentDragOffsetY = .zero
                                }
                            }
                        })

소스 코드

import SwiftUI

struct DragGestureBootCamp2: View {
    @State private var startingOffsetY: CGFloat = UIScreen.main.bounds.height * 0.85
    @State private var currentDragOffsetY: CGFloat = 0
    @State private var endingOffsetY: CGFloat = 0
    var body: some View {
        ZStack {
            Color.green.ignoresSafeArea()
            MySignUpView()
                .offset(y: startingOffsetY)
                .offset(y: currentDragOffsetY)
                .offset(y: endingOffsetY)
                .gesture(
                    DragGesture()
                        .onChanged({ value in
                            withAnimation(.spring()) {
                                currentDragOffsetY = value.translation.height
                            }
                        })
                        .onEnded({ value in
                            withAnimation(.spring()) {
                                if currentDragOffsetY < -150 {
                                    endingOffsetY = -startingOffsetY
                                    currentDragOffsetY = .zero
                                } else if endingOffsetY != 0 && currentDragOffsetY > 150 {
                                    endingOffsetY = .zero
                                    currentDragOffsetY = .zero
                                } else {
                                    currentDragOffsetY = .zero
                                }
                            }
                        })
                )
//            Text("\(currentDragOffsetY)")
        }
        .ignoresSafeArea(edges: .bottom)
    }
}

구현 화면 2

profile
JUST DO IT
post-custom-banner

0개의 댓글