简体   繁体   中英

SwiftUI DragGesture only in one direction

I'd like SwiftUI DragGesture to start only when the gesture is in a specific direction (horizontal/vertical). Is it possible?

Yes, it is, by applying one of the two components (either horizontal or vertical) of the gesture translation to the view offset.

Here's such behaviour implemented as a ViewModifier .

struct DraggableModifier : ViewModifier {

    enum Direction {
        case vertical
        case horizontal
    }

    let direction: Direction

    @State private var draggedOffset: CGSize = .zero

    func body(content: Content) -> some View {
        content
        .offset(
            CGSize(width: direction == .vertical ? 0 : draggedOffset.width,
                   height: direction == .horizontal ? 0 : draggedOffset.height)
        )
        .gesture(
            DragGesture()
            .onChanged { value in
                self.draggedOffset = value.translation
            }
            .onEnded { value in
                self.draggedOffset = .zero
            }
        )
    }

}

Demo:

struct ContentView: View {

    var body: some View {
        VStack {
            Spacer(minLength: 100)
            HStack {
                Rectangle()
                    .foregroundColor(.green)
                    .frame(width: 100, height: 100)
                    .modifier(DraggableModifier(direction: .vertical))
                Text("Vertical")
            }
            Spacer(minLength: 100)
            Rectangle()
                .foregroundColor(.red)
                .frame(width: 100, height: 100)
                .modifier(DraggableModifier(direction: .horizontal))
            Text(verbatim: "Horizontal")
            Spacer(minLength: 100)
        }
    }
}

Result :

在此处输入图像描述

Rather old question but didn't find the answer elsewhere.

You can easily check wether it was a left- or a right-swipe, because Gestures return their end state, which contains the starting and final position.

Text("Hello").gesture(DragGesture(minimumDistance: 100).onEnded { endedGesture in
                if (endedGesture.location.x - endedGesture.startLocation.x) > 0 {
                    print("Left")
                } else {
                    print("Right")
                }
}

This will only move the view if the gesture moves move horizontally than vertically. The isDragging boolean makes sure after the first condition is met, the view will move vertically too. It's not necessary, but it helps for smoother dragging.

@State var dragOffset: CGSize = .zero
@State var isDragging = false

.offset(x: offset.width, y: offset.height)
        .gesture(DragGesture()
                    .onChanged { if abs($0.translation.width) > abs($0.translation.height) || isDragging {
                        self.offset = $0.translation
                        self.isDragging = true
                        
                    }
                    }
                    .onEnded {
                        isDragging = false
                        
                    })

An example in which, at the start of the movement, a vertical or horizontal movement is determined after which the object moves only in this direction

struct Test: View {

    @State private var offsetX = CGFloat.zero
    @State private var offsetY = CGFloat.zero
    
    var body: some View {
        ZStack {
            Divider()
            Divider().rotationEffect(.degrees(90))
            Divider().rotationEffect(.degrees(45))
            Divider().rotationEffect(.degrees(-45))

            VStack {
                Spacer()
                Text("offsetX: \(offsetX)     offsetY: \(offsetY)")
                    .foregroundColor(.black.opacity(0.5))
                    .frame(maxWidth: .infinity)
                    .padding(.bottom, 18.0)
            }
            
            Rectangle()
                .fill(.green)
                .frame(width: 300.0, height: 200.0)
                .cornerRadius(13.0)
                .offset(x: offsetX, y: offsetY)
                .gesture(drag)
        }
    }
    
    @State private var horizontal = true
    @State private var vertical = true
    
    var drag: some Gesture {
        DragGesture()
            .onChanged { dg in
                var x = dg.translation.width
                var y = dg.translation.height
                
                if ((x < 0 && y < 0 && x < y) || (x < 0 && y > 0 && -x > y) || (x > 0 && y < 0 && x > -y) || (x > 0 && y > 0 && x > y)) && self.horizontal && self.vertical {
                    self.horizontal = true
                    self.vertical = false
                } else if vertical && horizontal {
                    self.horizontal = false
                    self.vertical = true
                }
                
                if self.horizontal {
                    self.offsetX = x
                } else {
                    self.offsetY = y
                }
            }
            .onEnded { _ in
                withAnimation {
                    self.offsetX = .zero
                    self.offsetY = .zero
                }
                self.horizontal = true
                self.vertical = true
            }
        
    }
}

Demo: https://i.stack.imgur.com/Vdbkx.gif

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM