Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to detect which View currently falls under the location of a DragGesture?

Tags:

swiftui

I would like to be able to detect if there are any views that fall under the location of a DragGesture.

I have tried adding DragGesture to each view (circle in this case) hoping the action would transfer across views for a continuous DragGesture motion. But that didn't work.

Surely there is a way?

Screenshot

import SwiftUI

struct ContentView2: View {
    
    var body: some View {
        ZStack {
            Image(.background)
                .resizable()
                .scaledToFill()
                .ignoresSafeArea()
                
                .gesture(
                    DragGesture(minimumDistance: 5)
                        .onChanged { value in
                        
                        print ("DG detetected at \(value.location.x) : \(value.location.y)")
                        })
                
            HStack {
                Circle()
                    .fill(.red)
                    .frame(width: 100, height: 100)
                    .gesture(
                        DragGesture(minimumDistance: 0)
                            .onChanged { value in
                            print ("DG in red")
                        })
            Circle()
                .fill(.white)
                .frame(width: 100, height: 100)
            Circle()
                .fill(.blue)
                .frame(width: 100, height: 100)
            }
        }
        
    }
    
    
}

#Preview {
    ContentView2()
}
like image 834
Bartender1382 Avatar asked Dec 06 '25 07:12

Bartender1382


2 Answers

I would suggest saving the drag location in a GestureState variable. Then you can use a GeometryReader behind each of the circles to detect whether the drag location is inside the circle.

  • It is important that the drag gesture and the GeometryReader are referring to the same coordinate space.

  • A GestureState variable is automatically reset at the end of drag, so there is no need to reset it manually. In the example below, the only other variable that needs to be reset when drag ends is the text info.

struct ContentView: View {
    @GestureState private var dragLocation = CGPoint.zero
    @State private var dragInfo = " "

    private func dragDetector(for name: String) -> some View {
        GeometryReader { proxy in
            let frame = proxy.frame(in: .global)
            let isDragLocationInsideFrame = frame.contains(dragLocation)
            let isDragLocationInsideCircle = isDragLocationInsideFrame &&
                Circle().path(in: frame).contains(dragLocation)
            Color.clear
                .onChange(of: isDragLocationInsideCircle) { oldVal, newVal in
                    if dragLocation != .zero {
                        dragInfo = "\(newVal ? "entering" : "leaving") \(name)..."
                    }
                }
        }
    }

    var body: some View {
        ZStack {
            Color(white: 0.2)
            VStack(spacing: 50) {
                Text(dragInfo)
                    .foregroundStyle(.white)
                HStack {
                    Circle()
                        .fill(.red)
                        .frame(width: 100, height: 100)
                        .background { dragDetector(for: "red") }
                    Circle()
                        .fill(.white)
                        .frame(width: 100, height: 100)
                        .background { dragDetector(for: "white") }
                    Circle()
                        .fill(.blue)
                        .frame(width: 100, height: 100)
                        .background { dragDetector(for: "blue") }
                }
            }
        }
        .gesture(
            DragGesture(coordinateSpace: .global)
                .updating($dragLocation) { val, state, trans in
                    state = val.location
                }
                .onEnded { val in
                    dragInfo = " "
                }
        )
    }
}

For versions of iOS before iOS 17, you would need to use the version of .onChange that only takes 1 parameter (just omit the parameter oldVal).

Animation

like image 132
Benzy Neez Avatar answered Dec 11 '25 22:12

Benzy Neez


I've solved it using the DragGesture on the ZStack and embedded it all in a GeometryReader to split the screen in different portions inside the gesture. The solution might be bit rough around the edges, of that I'm sure, but it is the closest I could get, I did my best. Here's the code:

GeometryReader { proxy in
        let size = proxy.size
        ZStack {
            
            
            Color.black
                .ignoresSafeArea()
            
            HStack {
                Circle()
                    .fill(.red)
                    .frame(width: 100, height: 100)
                
                Circle()
                    .fill(.white)
                    .frame(width: 100, height: 100)
                
                Circle()
                    .fill(.blue)
                    .frame(width: 100, height: 100)
            }
        } //: ZSTACK
        .frame(width: size.width)
        .gesture(
            DragGesture(minimumDistance: 0)
                .onChanged({ value in
                    let locationX = value.location.x
                    let locationY = value.location.y
                    
                    /// Check if we are in the middle
                    if locationY >= (size.height / 2) - 50 && locationY <= (size.height / 2) + 50 {
                        
                        let startRedCircle = locationX <= (size.width / 3 + 10)
                        let startWhiteCircle = locationX >= (size.width / 3 + 10) && locationX <= (2 *  size.width / 3 - 10)
                        let startBlueCircle = locationX >= (2 * size.width / 3 - 10) && locationX <= (3 *  size.width / 3 - 30)
                        
                        if (startRedCircle) {
                            print("Red")
                        } else if (startWhiteCircle) {
                            print("White")
                        } else if(startBlueCircle) {
                            print("Blue")
                        }
                    }
                    
                    
                    
                    
                })
        )
        
    } //: GEOMETRY

Of course you may need some adjustments based on your specific needs. And here's the result: Detect circle color

Let me know if it was of any help!

like image 36
MatBuompy Avatar answered Dec 11 '25 22:12

MatBuompy



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!