I'm trying to learn SwiftUI, and I had a question about how to make a component that has a handle on it which you can use to drag around. There are several tutorials online about how to make a draggable component, but none of them exactly answer the question I have, so I thought I would seek the wisdom of you fine people.
Lets say you have a view that's like a window with a title bar. For simplicity's sake, lets make it like this:
struct WindowView: View {
var body: some View {
VStack(spacing:0) {
Color.red
.frame(height:25)
Color.blue
}
}
}
I.e. the red part at the top is the title bar, and the main body of the component is the blue area. Now, this window view is contained inside another view, and you can drag it around. The way I've read it, you should do something like this (very simplified):
struct ContainerView: View {
@State private var loc = CGPoint(x:150, y:150);
var body: some View {
ZStack {
WindowView()
.frame(width:100, height:100)
.position(loc)
.gesture(DragGesture()
.onChanged { value in
loc = value.location
}
)
}
}
}
and that indeed lets you drag the component around (ignore for now that we're always dragging by the center of the image, it's not really the point):
However, this is NOT what I want: I don't want you to be able to drag the component around by just dragging inside the window, I only want to drag it around by dragging the red title bar. But the red title-bar is hidden somewhere inside of WindowView. I don't want to move the @State variable containing the position to inside the WindowView, it seems to me much more logical to have that inside ContainerView. But then I need to somehow forward the gesture into the embedded title bar.
I imagine the best way would be for the ContainerView to look something like this:
struct WindowView: View {
@State private var loc = CGPoint(x:150, y:150);
var body: some View {
VStack {
Color.red
.frame(height:25)
.gesture(DragGesture()
.onChanged { value in
loc = value.location
}
)
Color.blue
}
.frame(width:100, height:100)
.offset(loc)
}
}
but I don't know how you would implement that .titleBarGesture in the correct way (or if this is even the proper way to do it. should the gesture be an argument to the WindowView constructor?). Can anyone help me out, give me some pointers?
Thanks in advance!
I consulted with Apple Technical Support Incident.
I think that's probably the best official solution at the moment.
struct WindowView: View {
@State private var translation = CGSize.zero
@State private var lastTranslation = CGSize.zero
var body: some View {
VStack {
// None gesture on the child view
Color.red
.frame(height: 100)
Spacer()
}
.background(.blue)
.frame(width: 300, height: 300)
.cornerRadius(10)
// Added content shape matching child view
.contentShape(
Rectangle()
.size(width: 300, height: 100)
)
.offset(
x: lastTranslation.width + translation.width,
y: lastTranslation.height + translation.height
)
// Moved gesture to the parent view
.gesture(dragGesture)
}
var dragGesture: some Gesture {
DragGesture()
.onChanged { value in
translation = value.translation
}
.onEnded { value in
lastTranslation.width += value.translation.width
lastTranslation.height += value.translation.height
translation = .zero
}
}
}