Is there a way to get a View
's frame after layout? I'd like to draw a line connecting two views after layout has positioned them:
It seems I need something like measure
in React.
Shift the location of a view's content For example, the offset(x:y:) modifier uses the parameters of x and y to represent a relative location within the view's coordinate space. In SwiftUI, the view's coordinate space uses x to represent a horizontal direction and y to represent a vertical direction.
All views have a natural position inside your hierarchy, but the offset() modifier lets you move them relative to that natural position.
GeometryReader works by wrapping our view inside the view builder closer, it acts as a container. For accessing the frame properties, you could just wrap the view around a geometry reader and use the geometry proxy in it.
GeometryReader is a view that returns a flexible preferred size to its parent layout. We can give our views sizes using GeometryReader. GeometryReader provides us the available width and height.
Use a GeometryReader
to get the frame of each view and use the frame to determine the points for a path between the two views.
struct ContentView : View {
var body: some View {
GeometryReader { geometry -> Text in
let frame = geometry.frame(in: CoordinateSpace.local)
return Text("\(frame.origin.x), \(frame.origin.y), \(frame.size.width), \(frame.size.height)")
}
}
}
To export the "as rendered" view coordinates, I created a set of arrays for the x,y coordinates, and saved them in the Model object. However, I had to NOT encapsulate them in @Published var, instead kept them as just "var", otherwise you get into an infinite loop of updating the coordinates and then re-rendering the view.
Using Apple's 'landmark' tutorial, the Model object was modified as follows:
import SwiftUI
import Combine
final class UserData: ObservableObject {
@Published var showFavoritesOnly = false
@Published var landmarks = landmarkData
var circleImageX = Array(repeating: 0.0, count:20)
var circleImageY = Array(repeating: 0.0, count:20)
}
Then, write to those arrays each time the CircleImage.swift is rendered using the following code, again from the 'landmark.swift' tutorial, saving the frame midpoints.
import SwiftUI
struct CircleImage: View {
@EnvironmentObject var userData: UserData
var landmark: Landmark
var landmarkIndex: Int {
userData.landmarks.firstIndex(where: {$0.id == landmark.id})!
}
var body: some View {
ZStack {
landmark.image
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 10)
VStack {
GeometryReader { geometry -> Text in
let frame = geometry.frame(in: CoordinateSpace.global)
self.userData.circleImageX[self.landmarkIndex] = Double(frame.midX)
return
Text("\(frame.midX)")
.foregroundColor(.red)
.bold()
.font(.title)
}
.offset(x: 0.0, y: 50.0)
GeometryReader { geometry -> Text in
let frame = geometry.frame(in: CoordinateSpace.global)
self.userData.circleImageY[self.landmarkIndex] = Double(frame.midY)
return
Text("\(frame.midY)")
.foregroundColor(.red)
.bold()
.font(.title)
}
.offset(x: 0.0, y: -50.0)
}
}
}
}`
Not only does this save the rendered coordinates, it also renders them as a Text view overlaid with the image as suggested by Jake. Naturally you can delete the Text overlay view once you're satisfied the coordinates are correct. Hope this helps
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With