I have a black color, let's say a black screen, and I want to make this black screen the base coordinate system for my app—not the GeometryReader, but the black screen itself. I created the code below to test if the black screen is being used as the true coordinate system. I want the top-left corner of the black screen to be the origin, and for it to work like SwiftUI's Path coordinate system, where the axes are consistent.
However, my code has some issues. First, when I move my circle to the top-left corner of the black screen, I don't get (0, 0) as the CGPoint, which suggests the custom coordinate system isn't working as expected. Second, when I place my circle on the screen, I want it to maintain its position relative to the top and left sides of the black screen, even if I resize the window. Currently, the circle's position changes when I resize the window.
import SwiftUI
struct ContentView: View {
let circleSize: CGFloat = 50.0
let randomValue = CGFloat.random(in: 50.0...100.0)
var body: some View {
GeometryReader { geometryValue in
ZStack {
Color.black
.coordinateSpace(name: "BlackScreen")
CircleView(circleSize: circleSize) { newValue in
let localPoint = CGPoint(
x: newValue.x + geometryValue.frame(in: .named("BlackScreen")).width/2.0 - circleSize/2.0,
y: newValue.y + geometryValue.frame(in: .named("BlackScreen")).height/2.0 - circleSize/2.0
)
print(localPoint)
}
}
}
.padding(randomValue)
}
}
struct CircleView: View {
let circleSize: CGFloat
var point: (CGPoint) -> Void
@State private var lastOffset: CGSize = CGSize()
@State private var currentOffset: CGSize = CGSize()
var body: some View {
Circle()
.fill(Color.red.opacity(0.5))
.frame(width: circleSize, height: circleSize)
.overlay(Circle().fill(Color.white.opacity(0.5)).frame(width: 4.0, height: 4.0))
.offset(currentOffset)
.gesture(dragGesture)
}
private var dragGesture: some Gesture {
DragGesture(minimumDistance: 0, coordinateSpace: .named("BlackScreen"))
.onChanged { gestureValue in
currentOffset = CGSize(
width: gestureValue.translation.width + lastOffset.width,
height: gestureValue.translation.height + lastOffset.height
)
point(gestureValue.location)
}
.onEnded { gestureValue in
lastOffset = currentOffset
}
}
}
First issue: Let's say our tolerance with hand and mouse is 1.0. As you can see, almost zero-zero has a significant difference when being used as the base coordinate system.
The first problem is because CircleView
is returning the location of the mouse pointer. That is what gestureValue.location
means. It is not the location of the center of the circle.
The second problem is because the ZStack
is using .center
alignment. The center of the ZStack
moves as you resize the window, but the circle's offset does not change, so the circle moves as well.
Both of these problems can be solved, if you just align the circle in such a way that a currentOffset
of .zero
means the center of the circle is at the top left.
First, use .topLeading
as the ZStack
alignment. The "top leading" point of the ZStack
never changes as you resize the window.
ZStack(alignment: .topLeading) {
Color.black
CircleView(circleSize: circleSize) {
print($0)
}
}
.padding(randomValue)
Then in CircleView
you can use alignmentGuide
to make the center
of the circle to be the alignment guides for .top
and .leading
.
var body: some View {
Circle()
.fill(Color.red.opacity(0.5))
.frame(width: circleSize, height: circleSize)
.overlay(Circle().fill(Color.white.opacity(0.5)).frame(width: 4.0, height: 4.0))
.alignmentGuide(.top) { $0[VerticalAlignment.center] }
.alignmentGuide(.leading) { $0[HorizontalAlignment.center] }
.offset(currentOffset)
.gesture(dragGesture)
}
Finally, convert currentOffset
to a CGPoint
before passing it to point
.
private var dragGesture: some Gesture {
DragGesture(minimumDistance: 0, coordinateSpace: .named("BlackScreen"))
.onChanged { gestureValue in
currentOffset = CGSize(
width: gestureValue.translation.width + lastOffset.width,
height: gestureValue.translation.height + lastOffset.height
)
point(CGPoint(x: currentOffset.width, y: currentOffset.height))
}
.onEnded { gestureValue in
lastOffset = currentOffset
}
}
There is no need for coordinateSpace
s and GeometryReader
s, because Color.black
fills the whole ZStack
.