Stack Overflow Asked by rliebert on December 16, 2021
I’m trying to make a view draggable and/or zoomable only within its clipping container view (otherwise it can run into and conflict with other views’ gestures), but nothing I’ve tried so far keeps the gesture from extending outside the visible boundary of the container.
Here’s a simplified demo of the behavior I don’t want…
When the red Rectangle goes partially outside the green VStack area (clipped), it responds to drag gestures beyond the green area:
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
@State var position: CGPoint = CGPoint(x: 100, y: 150)
@State var lastPosition: CGPoint = CGPoint(x: 100, y: 150)
var body: some View {
let drag = DragGesture()
.onChanged {
self.position = CGPoint(x: $0.translation.width + self.lastPosition.x, y: $0.translation.height + self.lastPosition.y)
}
.onEnded {_ in
self.lastPosition = self.position
}
return VStack {
Rectangle().foregroundColor(.red)
.frame(width: 150, height: 150)
.position(self.position)
.gesture(drag)
.clipped()
}
.background(Color.green)
.frame(width: 200, height: 300)
}
}
PlaygroundPage.current.setLiveView(ContentView())
How would you limit this gesture to only work inside the container (green area in the example above)?
UPDATE:
@Asperi’s solution to the above works well, but when I add a second draggable container next to the one above, I get a "dead area" in the first container inside which I can’t drag (it appears to be where the second square would cover the first one if it were not clipped). The problem only happens to the original/left side, not to the new one. I think that has to do with it having higher priority since it is drawn second.
Here’s an illustration of the new issue:
And here’s the updated code:
struct ContentView: View {
@State var position1: CGPoint = CGPoint(x: 100, y: 150)
@State var lastPosition1: CGPoint = CGPoint(x: 100, y: 150)
let dragArea1: CGRect = CGRect(x: 0, y: 0, width: 200, height: 300)
@State var position2: CGPoint = CGPoint(x: 100, y: 150)
@State var lastPosition2: CGPoint = CGPoint(x: 100, y: 150)
let dragArea2: CGRect = CGRect(x: 0, y: 0, width: 200, height: 300)
var body: some View {
let drag1 = DragGesture(coordinateSpace: .named("dragArea1"))
.onChanged {
guard self.dragArea1.contains($0.startLocation) else { return }
self.position1 = CGPoint(x: $0.translation.width + self.lastPosition1.x, y: $0.translation.height + self.lastPosition1.y)
}
.onEnded {_ in
self.lastPosition1 = self.position1
}
let drag2 = DragGesture(coordinateSpace: .named("dragArea2"))
.onChanged {
guard self.dragArea2.contains($0.startLocation) else { return }
self.position2 = CGPoint(x: $0.translation.width + self.lastPosition2.x, y: $0.translation.height + self.lastPosition2.y)
}
.onEnded {_ in
self.lastPosition2 = self.position2
}
return HStack {
VStack {
Rectangle().foregroundColor(.red)
.frame(width: 150, height: 150)
.position(self.position1)
.gesture(drag1)
.clipped()
}
.background(Color.green)
.frame(width: dragArea1.width, height: dragArea1.height)
VStack {
Rectangle().foregroundColor(.blue)
.frame(width: 150, height: 150)
.position(self.position2)
.gesture(drag2)
.clipped()
}
.background(Color.yellow)
.frame(width: dragArea2.width, height: dragArea2.height)
}
}
}
Any ideas of how to keep dragging disabled outside any containers, as already achieved, but also allow dragging within the full bounds of each container regardless of what happens with others?
Two days I was looking for a solution to a similar problem @Asperi's solution helps, but it is not universal for 3 or more figures
My solution: I adding
.contentShape(Rectangle())
before
.gesture(DragGesture().onChanged {
this article helped me. https://www.hackingwithswift.com/quick-start/swiftui/how-to-control-the-tappable-area-of-a-view-using-contentshape
I hope it will be useful to someone.
Sample code:
var body: some View {
VStack {
Image("my image")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(200, 200)
.clipShape(Rectangle())
.contentShape(Rectangle()) // <== this code helped me
.gesture(
DragGesture()
.onChanged {
//
}
.onEnded {_ in
//
}
)
}
}
For the example above, the code could be like this:
struct ContentView: View {
@State var position1: CGPoint = CGPoint(x: 100, y: 150)
@State var lastPosition1: CGPoint = CGPoint(x: 100, y: 150)
let dragArea1: CGSize = CGSize(width: 200, height: 300)
@State var position2: CGPoint = CGPoint(x: 100, y: 150)
@State var lastPosition2: CGPoint = CGPoint(x: 100, y: 150)
let dragArea2: CGSize = CGSize(width: 200, height: 300)
var body: some View {
let drag = DragGesture()
.onChanged {
if $0.startLocation.x <= self.dragArea1.width {
self.position1 = CGPoint(x: $0.translation.width + self.lastPosition1.x, y: $0.translation.height + self.lastPosition1.y)
} else {
self.position2 = CGPoint(x: $0.translation.width + self.lastPosition2.x, y: $0.translation.height + self.lastPosition2.y)
}
}
.onEnded {_ in
self.lastPosition1 = self.position1
self.lastPosition2 = self.position2
}
return HStack {
VStack {
Rectangle().foregroundColor(.red)
.frame(width: 150, height: 150)
.position(self.position1)
.clipped()
}
.background(Color.green)
.frame(width: dragArea1.width, height: dragArea1.height)
VStack {
Rectangle().foregroundColor(.blue)
.frame(width: 150, height: 150)
.position(self.position2)
.clipped()
}
.background(Color.yellow)
.frame(width: dragArea2.width, height: dragArea2.height)
}
.clipShape(Rectangle()) //<=== This
.contentShape(Rectangle()) //<=== and this
.gesture(drag)
} }
Answered by John on December 16, 2021
For the 2nd part (i.e. update to the original question), here's what I ended up with. Basically, I combined the two separate drag gestures into one gesture that covers the whole HStack, and then directed the gesture to the appropriate @State variable depending on where in the HStack it started.
Demo of the Result:
Code:
struct ContentView: View {
@State var position1: CGPoint = CGPoint(x: 100, y: 150)
@State var lastPosition1: CGPoint = CGPoint(x: 100, y: 150)
let dragArea1: CGSize = CGSize(width: 200, height: 300)
@State var position2: CGPoint = CGPoint(x: 100, y: 150)
@State var lastPosition2: CGPoint = CGPoint(x: 100, y: 150)
let dragArea2: CGSize = CGSize(width: 200, height: 300)
var body: some View {
let drag = DragGesture()
.onChanged {
guard $0.startLocation.y >= 0 && $0.startLocation.y <= self.dragArea1.height else { return }
if $0.startLocation.x <= self.dragArea1.width {
self.position1 = CGPoint(x: $0.translation.width + self.lastPosition1.x, y: $0.translation.height + self.lastPosition1.y)
} else {
self.position2 = CGPoint(x: $0.translation.width + self.lastPosition2.x, y: $0.translation.height + self.lastPosition2.y)
}
}
.onEnded {_ in
self.lastPosition1 = self.position1
self.lastPosition2 = self.position2
}
return HStack {
VStack {
Rectangle().foregroundColor(.red)
.frame(width: 150, height: 150)
.position(self.position1)
.clipped()
}
.background(Color.green)
.frame(width: dragArea1.width, height: dragArea1.height)
VStack {
Rectangle().foregroundColor(.blue)
.frame(width: 150, height: 150)
.position(self.position2)
.clipped()
}
.background(Color.yellow)
.frame(width: dragArea2.width, height: dragArea2.height)
}
.gesture(drag)
}
}
Notes:
Answered by rliebert on December 16, 2021
Here is possible solution. The idea is to have drag coordinates in container coordinate space and ignore drag if start location is out of that named area.
Tested with Xcode 11.4 / iOS 13.4
struct ContentView: View {
@State var position: CGPoint = CGPoint(x: 100, y: 150)
@State var lastPosition: CGPoint = CGPoint(x: 100, y: 150)
var body: some View {
let area = CGRect(x: 0, y: 0, width: 200, height: 300)
let drag = DragGesture(coordinateSpace: .named("area"))
.onChanged {
guard area.contains($0.startLocation) else { return }
self.position = CGPoint(x: $0.translation.width + self.lastPosition.x, y: $0.translation.height + self.lastPosition.y)
}
.onEnded {_ in
self.lastPosition = self.position
}
return VStack {
Rectangle().foregroundColor(.red)
.frame(width: 150, height: 150)
.position(self.position)
.gesture(drag)
.clipped()
}
.background(Color.green)
.frame(width: area.size.width, height: area.size.height)
.coordinateSpace(name: "area")
}
}
Answered by Asperi on December 16, 2021
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP