Creating a drag handle inside of a SwiftUI view (for draggable windows and such)

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!

Answered by ylqylq001 in 746355022

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
            }
    }
}
Accepted Answer

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
            }
    }
}
Creating a drag handle inside of a SwiftUI view (for draggable windows and such)
 
 
Q