Preventing SwiftUI rectangles from going outside active area during drag gesture

I'm working on a SwiftUI project where I have one rectangle that I want to be draggable within an active area. However, I'm facing an issue where the rectangle can go outside the active area during the drag gesture. I want to constrain the position of the rectangle so that it stays within the active area during the drag gesture. How can I achieve this? Any insights or suggestions would be greatly appreciated. Thank you in advance!

Here's a simplified version of my code:

struct ContentView: View {
    @State var position = CGSize.zero
    @State var lastPosition = CGSize.zero
    var body: some View {
        ZStack {
            Rectangle()
                .fill(Color(red: 0.5450980392156862, green: 0.5450980392156862, blue: 0.5450980392156862))
                .aspectRatio(0.75, contentMode: .fit)
                .frame(height: 550)
                .cornerRadius(30)
                .offset(x: position.width, y: position.height)
                .animation(.spring(response: 0.4, dampingFraction: 0.4, blendDuration: 0.4))
                .gesture(
                    DragGesture()
                        .onChanged({ value in
                            position = CGSize(width: lastPosition.width + value.translation.width, height: lastPosition.height + value.translation.height)
                        })
                        .onEnded({ value in
                            lastPosition = position
                        })
                )
                .padding()
        }
    }
}

Accepted Reply

A simple way is to compute those limits yourself and forbid to overpass.

Here is some sample code you could adapt:

    @State private var location: CGPoint = CGPoint(x: 700, y: 500) // Set a default value
    @GestureState private var startLocation: CGPoint? = nil 
    
    var parentViewSize: CGSize = CGSize(width: 600, height: 400)    // The size of your parentView
    
    .gesture(
        DragGesture()
            .onChanged { value in
                
                var newLocation = startLocation ?? location 
                newLocation.x += value.translation.width
                newLocation.y += value.translation.height
                if newLocation.x < 200 { newLocation.x = 200 }   // 200 to be replaced by a value depending on Rectangle width
                if newLocation.x > parentViewSize.width - 100 { newLocation.x = parentViewSize.width - 100 } // 100 to be replaced by a value depending on Rectangle height
                if newLocation.y < 200 { newLocation.y = 200 } // 200 to be replaced by a value depending on Rectangle height
                if newLocation.y > parentViewSize.height - 100 { newLocation.y = parentViewSize.height - 100 } // 200 to be replaced by a value depending on Rectangle height
                self.location = newLocation
            }
            .updating($startLocation) { (value, startLocation, transaction) in
                startLocation = startLocation ?? location
            }
    )

You can also use geometryReader as described here: https://stackoverflow.com/questions/62512494/how-to-restrict-drag-gesture-to-particular-frame-only-in-swiftui

Replies

A simple way is to compute those limits yourself and forbid to overpass.

Here is some sample code you could adapt:

    @State private var location: CGPoint = CGPoint(x: 700, y: 500) // Set a default value
    @GestureState private var startLocation: CGPoint? = nil 
    
    var parentViewSize: CGSize = CGSize(width: 600, height: 400)    // The size of your parentView
    
    .gesture(
        DragGesture()
            .onChanged { value in
                
                var newLocation = startLocation ?? location 
                newLocation.x += value.translation.width
                newLocation.y += value.translation.height
                if newLocation.x < 200 { newLocation.x = 200 }   // 200 to be replaced by a value depending on Rectangle width
                if newLocation.x > parentViewSize.width - 100 { newLocation.x = parentViewSize.width - 100 } // 100 to be replaced by a value depending on Rectangle height
                if newLocation.y < 200 { newLocation.y = 200 } // 200 to be replaced by a value depending on Rectangle height
                if newLocation.y > parentViewSize.height - 100 { newLocation.y = parentViewSize.height - 100 } // 200 to be replaced by a value depending on Rectangle height
                self.location = newLocation
            }
            .updating($startLocation) { (value, startLocation, transaction) in
                startLocation = startLocation ?? location
            }
    )

You can also use geometryReader as described here: https://stackoverflow.com/questions/62512494/how-to-restrict-drag-gesture-to-particular-frame-only-in-swiftui