Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: Path's coordinateSpace

So I've got the following code:

import SwiftUI

struct ContentView : View {
    @State private var draggingLocation = CGPoint.zero
    @State private var startLocation = CGPoint.zero
    @State private var dragging = false

    var body: some View {
        let GR = DragGesture(minimumDistance: 10, coordinateSpace: .global)
            .onEnded { value in
                self.dragging = false
                self.draggingLocation = CGPoint.zero
                self.startLocation = CGPoint.zero
            }
            .onChanged { value in
                if !self.dragging {
                    self.dragging = true
                }
                if self.startLocation == CGPoint.zero {
                    self.startLocation = value.startLocation
                }
                self.draggingLocation = value.location
            }

        return ZStack {
            if self.dragging {
                Path { path in
                    path.move(to: CGPoint(x: self.startLocation.x-5, y: self.startLocation.y-5))
                    path.addLine(to: CGPoint(x: self.draggingLocation.x-5, y: self.draggingLocation.y+5))
                    path.addLine(to: CGPoint(x: self.draggingLocation.x+5, y: self.draggingLocation.y-5))
                    path.addLine(to: CGPoint(x: self.startLocation.x+5, y: self.startLocation.y+5))
                }
                .fill(Color.black)
            }

            Circle()
            .fill(self.dragging ? Color.blue : Color.red)
            .frame(width: 100, height: 100)
            .gesture(GR)
            .offset(
                x: 75,
                y: 75
            )

            Circle()
            .fill(self.dragging ? Color.blue : Color.red)
            .frame(width: 100, height: 100)
            .gesture(GR)
            .offset(
                x: -75,
                y: -75
            )
        }
        .frame(width: 400, height: 400)
        .background(Color.gray)
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

Which results in this behavior:

ios

I'd like to be able to drag the edge out from one circle and into the other, the problem of course is that the coordinate space of the Path is relative to the gray box (ContentView) and not global. A Path has a property coordinateSpace in the documentation but there's very little information how to use it, and googling the term with SwiftUI literally returns three results, all of which are really just links to Apple's currently sparse docs. Anyone have an idea on how to best approach this?

like image 433
adammenges Avatar asked Jul 13 '19 00:07

adammenges


People also ask

What is CGPoint?

The data structure CGPoint represents a point in a two-dimensional coordinate system. The data structure CGRect represents the location and dimensions of a rectangle. The data structure CGSize represents the dimensions of width and height.

What is path SwiftUI?

SwiftUI enables custom drawing with two subtly different types: paths and shapes. A path is a series of drawing instructions such as “start here, draw a line to here, then add a circle there”, all using absolute coordinates.


1 Answers

Coordinate spaces come in three flavours: .local, .global and .named. The first two are obvious. The third, named coordinate spaces, are extremely useful in cases like yours. They are also useful in combination with GeometryReader. For more details on that, check https://swiftui-lab.com/geometryreader-to-the-rescue/

Named coordinate spaces let you express a coordinate of one view, in the coordinate space of another. For that, SwiftUI let you specify a name for a view's coordinate space. Then, in other places of your code, you can make a reference of it. In your example, naming the coordinate of your ZStack is the way to go.

Here's the refactored code:

Note: I moved the Path below, only so that it draws in front of the circles. And also, pay attention to the first Path, which is only there to prevent what I think is a bug in ZStack.

enter image description here

import SwiftUI

struct ContentView : View {
    @State private var draggingLocation = CGPoint.zero
    @State private var startLocation = CGPoint.zero
    @State private var dragging = false

    var body: some View {
        let GR = DragGesture(minimumDistance: 10, coordinateSpace: .named("myCoordinateSpace"))
            .onEnded { value in
                self.dragging = false
                self.draggingLocation = CGPoint.zero
                self.startLocation = CGPoint.zero
        }
        .onChanged { value in
            if !self.dragging {
                self.dragging = true
            }

            if self.startLocation == CGPoint.zero {
                self.startLocation = value.startLocation
            }
            self.draggingLocation = value.location
        }

        return ZStack(alignment: .topLeading) {

            Circle()
                .fill(self.dragging ? Color.blue : Color.red)
                .frame(width: 100, height: 100)
                .overlay(Text("Circle 1"))
                .gesture(GR)
                .offset(x: 75, y: 75)

            Circle()
                .fill(self.dragging ? Color.blue : Color.red)
                .frame(width: 100, height: 100)
                .overlay(Text("Circle 2"))
                .gesture(GR)
                .offset(x: 200, y: 200)

            if self.dragging {
                Path { path in
                    path.move(to: CGPoint(x: self.startLocation.x-5, y: self.startLocation.y-5))
                    path.addLine(to: CGPoint(x: self.draggingLocation.x-5, y: self.draggingLocation.y+5))
                    path.addLine(to: CGPoint(x: self.draggingLocation.x+5, y: self.draggingLocation.y-5))
                    path.addLine(to: CGPoint(x: self.startLocation.x+5, y: self.startLocation.y+5))
                }
                .fill(Color.black)
            }

        }
        .coordinateSpace(name: "myCoordinateSpace")
        .frame(width: 400, height: 400, alignment: .topLeading)
        .background(Color.gray)
    }
}
like image 171
kontiki Avatar answered Sep 28 '22 04:09

kontiki