Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

can I get the position of a `View` after layout in SwiftUI?

Tags:

ios

swift

swiftui

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:

enter image description here

It seems I need something like measure in React.

like image 256
Taylor Avatar asked Jun 09 '19 21:06

Taylor


People also ask

How do I change the position of a view in SwiftUI?

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.

What does offset do in SwiftUI?

All views have a natural position inside your hierarchy, but the offset() modifier lets you move them relative to that natural position.

How do I use GeometryReader in SwiftUI?

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.

What is the purpose of GeometryReader?

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.


2 Answers

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)")
        }
    }
}
like image 136
Jake Avatar answered Oct 16 '22 21:10

Jake


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

like image 35
grapeffx Avatar answered Oct 16 '22 22:10

grapeffx