How We Can Get and Read Size of a Text with Geometryreader in Swiftui

How we can get and read size of a Text with GeometryReader in SwiftUI?

You can use view preferences.

  1. First create a custom PreferenceKey for the view size:
struct ViewSizeKey: PreferenceKey {
static var defaultValue: CGSize = .zero

static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}

  1. Create a view which will calculate its size and assign it to the ViewSizeKey:
struct ViewGeometry: View {
var body: some View {
GeometryReader { geometry in
Color.clear
.preference(key: ViewSizeKey.self, value: geometry.size)
}
}
}

  1. Use them in your view:
struct ContentView: View {
@State var fontSize: CGFloat = 20.0
@State var textSize: CGSize = .zero

var body: some View {
Spacer()
Text("width of Text:" + String(format: "%.0f", textSize.width))
.font(.system(size: fontSize))
.background(ViewGeometry())
.onPreferenceChange(ViewSizeKey.self) {
textSize = $0
}
Spacer()
Text("Font size:" + "\(fontSize)")
Slider(value: $fontSize, in: 20...40, step: 1)
.padding()
Spacer()
}
}

View Preferences is quite an advanced topic. You can find a more detailed explanation here:

  • The magic of view preferences in SwiftUI
  • Inspecting the View Tree – Part 1: PreferenceKey

How to Align GeometryReader in SwiftUI?

It looks like you placed .frame modifier in wrong place (placement of modifier is important)

Here is modified code (assuming I understood your intention)

demo

var body: some View {
GeometryReader { geo in
Text("\(randomRoll)")
.font(.title)
.padding(.horizontal)
.frame(maxWidth: .infinity, maxHeight: .infinity) // << before background !!
.background(
Rectangle()
.foregroundColor(Color(hue: min(1, geo.frame(in: .global).minY/CGFloat(randomNumber) ), saturation: 1, brightness: 1))
.cornerRadius(10))

}
.frame(height: 150)
}

How can I read width of view inside a Array of View without using GeometryReader?

Unfortunately, it does not seem possible to get the width and height of the Rectangle without geometry reader in pure SwiftUI. I tried the methods that were attached to that Shape, but nothing resulted in the size.

Unknown what the use case is, but it is possible to make a custom object that holds the width and height of the rectangle.

struct Rectangle_TEST {
var rectangle: Rectangle
var width: CGFloat
var height: CGFloat
init(width: CGFloat, height: CGFloat) {
self.width = width
self.height = height
self.rectangle = Rectangle()
}
func getView(color: Color) -> some View {
return self.rectangle
.fill(color)
.frame(width: self.width, height: self.height)
}
}

struct ContentView: View {
var body: some View {
let arrayOfRec: [Rectangle_TEST] = [Rectangle_TEST(width: 100, height: 100), Rectangle_TEST(width: 200, height: 200)]
VStack {
ForEach(0..<arrayOfRec.count) { index in
Text("Width: \(arrayOfRec[index].width), Height: \(arrayOfRec[index].height)")
arrayOfRec[index].getView(color: .red)
}
}
}
}

Above I made the custom object and stores the width and height that can be accessed individually, and then a function that will return a view, and you can pass in whatever modifiers are needed. But, currently, I don't think it is possible to get the dimensions without geometry reader in SwiftUI.

Get width of a view using in SwiftUI

The only way to get the dimensions of a View is by using a GeometryReader. The reader returns the dimensions of the container.

What is a geometry reader? the documentation says:

A container view that defines its content as a function of its own size and coordinate space. Apple Doc

So you could get the dimensions by doing this:

struct ContentView: View {

@State var frame: CGSize = .zero

var body: some View {
HStack {
GeometryReader { (geometry) in
self.makeView(geometry)
}
}

}

func makeView(_ geometry: GeometryProxy) -> some View {
print(geometry.size.width, geometry.size.height)

DispatchQueue.main.async { self.frame = geometry.size }

return Text("Test")
.frame(width: geometry.size.width)
}
}

The printed size is the dimension of the HStack that is the container of inner view.

You could potentially using another GeometryReader to get the inner dimension.

But remember, SwiftUI is a declarative framework. So you should avoid calculating dimensions for the view:

read this to more example:

  • Make a VStack fill the width of the screen in SwiftUI
  • How to make view the size of another view in SwiftUI

SwiftUI - How to get GeometryReader size/height from different View?

You can:

  1. Make a @State property to store the height
  2. Set it using an .onAppear { attached to Color.clear
  3. Replace ??? with \(textHeight)
struct ContentView: View {
@State var textHeight = CGFloat(0) /// 1.

var body: some View {
VStack {
Text("Hello world!")
.background(
GeometryReader { proxy in
Color.clear
.onAppear { /// 2.
textHeight = proxy.size.height
}
}
)
/// 3.
Text("Height of first text is \(textHeight)")
}
}
}

Result:

"Hello world!" above "Height of first text is 20.333333"

SwiftUI - Get size of child?

Basically, the answer at this point is to use a GeometryReader inside of the child's background(...) modifier.

// This won't be valid until the first layout pass is complete
@State var childSize: CGSize = .zero

var body: some View {
ZStack {
Text("Hello World!")
.background(
GeometryReader { proxy in
Color.clear
.preference(
key: SizePreferenceKey.self,
value: proxy.size
)
}
)
}
.onPreferenceChange(SizePreferenceKey.self) { preferences in
self.childSize = preferences
}
}

struct SizePreferenceKey: PreferenceKey {
typealias Value = CGSize
static var defaultValue: Value = .zero

static func reduce(value: inout Value, nextValue: () -> Value) {
value = nextValue()
}
}

How to get the actual view rendered size and position in SwiftUI?

Actual Rendered Size

The workaround is to get the actual rendered size via .background modifier with an nested GeometryReader. The size information inside the new geometry proxy can then be stored in a temporary @State variable defined in the View.

struct FooSizePreferenceKey: PreferenceKey {
static let defaultValue = CGSize.zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}

struct FooView: View {
@State private var fooSize: CGSize = .zero

var body: some View {
GeometryReader { geometryProxy in
VStack {
Text("\(self.fooSize.height), \(self.fooSize.width)")
Text("Foo")
.background(
GeometryReader { fooProxy in
Color
.green
.preference(key: FooSizePreferenceKey.self,
value: fooProxy.size)
.onPreferenceChange(FooSizePreferenceKey.self) { size in
self.fooSize = size
}
}
)
}
}
.frame(width: 300, height: 300)
.background(Color.blue)
}
}

Sample Image

Actual Rendered Position

The actual rendered position for the view can be calculated using Anchor and anchorPreference. Using the anchor and the parent geometryProxy, we can easily get the position .bound information of the target view.

struct FooAnchorData: Equatable {
var anchor: Anchor<CGRect>? = nil
static func == (lhs: FooAnchorData, rhs: FooAnchorData) -> Bool {
return false
}
}

struct FooAnchorPreferenceKey: PreferenceKey {
static let defaultValue = FooAnchorData()
static func reduce(value: inout FooAnchorData, nextValue: () -> FooAnchorData) {
value.anchor = nextValue().anchor ?? value.anchor
}
}

struct FooView: View {
@State private var foo: CGPoint = .zero

var body: some View {
GeometryReader { geometryProxy in
VStack {
Text("\(self.foo.x), \(self.foo.y)")
Text("Foo")
.background(
GeometryReader { fooProxy in
Color
.green
.anchorPreference(key: FooAnchorPreferenceKey.self,
value: .bounds,
transform: {FooAnchorData(anchor: $0) })
.onPreferenceChange(FooAnchorPreferenceKey.self) { data in
self.foo = geometryProxy[data.anchor!].origin
}
}
)
}
}
.frame(width: 300, height: 300)
.background(Color.blue)
}
}

Sample Image



Related Topics



Leave a reply



Submit