swiftPunk
swiftPunk

Reputation: 1

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

I am trying to read the width of my Text depending on size of Text Font, As we know GeometryReader takes all possible given place to him, in this codes it just take himself the given frame size, that I passed it, but it does not take size of my Text! what I am doing Wrong? I what GeometryReader start reading my Text size only! not himself frame width.

enter image description here

Here is my code:

struct ContentView: View {
    @State var fontSize: CGFloat = 20.0

    var body: some View {
        Spacer()

        textWidthGeometryReader(fontSize: $fontSize)

        Spacer()

        Text("Font size:" + "\(fontSize)")

        Slider(value: $fontSize, in: 20...40, step: 1)
            .padding()

        Spacer()
    }
}

struct textWidthGeometryReader: View {
    @Binding var fontSize: CGFloat

    var body: some View {
        GeometryReader { inSideGeometry in

            Text("width of Text:" + String(format: "%.0f", inSideGeometry.size.width))
                .font(.system(size: fontSize))
                .background(Color.yellow)
                .position(x: inSideGeometry.size.width / 2, y: inSideGeometry.size.height / 2)
        }
        .frame(width: 400, height: 300, alignment: .center)
        .background(Color.gray)
        .cornerRadius(20)
    }
}

Upvotes: 13

Views: 18493

Answers (4)

Shaybc
Shaybc

Reputation: 3147

another approach is not to render at all (this will allow you to do calculations from stateless code like extension):

extension String
{
   func sizeUsingFont(usingFont font: UIFont) -> CGSize
    {
        let fontAttributes = [NSAttributedString.Key.font: font]
        return self.size(withAttributes: fontAttributes)
    }
}

and use it like this:

// calculate render width and height of text using provided font (without actually rendering)
let sizeOfText: CGSize = "test string".sizeUsingFont(usingFont: UIFont.systemFont(ofSize: 40, weight: UIFont.Weight.bold))

source i used: How to figure out the width of a string dynamically in SwiftUI

and if you still decide to use GeometryReader, remember that you can put the GeometryReader inside the .background() and save it to a state var on-appear or on-whatever event fits you:

import Foundation
import SwiftUI

struct TestView: View
{
    @State var sizeOfText: CGSize = CGSize(width: 0, height: 0)
    
    var body: some View
    {
        Text("Hello again")
            .font(Font.system(size:60, design: Font.Design.rounded))
            .background(GeometryReader { (geometryProxy : GeometryProxy) in
                HStack {}
                .onAppear {
                    sizeOfText = geometryProxy.size
                    print("sizeOfText: \(sizeOfText)")
                }
            })
            .offset(x: sizeOfText.width, y: sizeOfText.height)
    }
}

Upvotes: 8

swiftPunk
swiftPunk

Reputation: 1

I found another way to solving this issue, this time you are able to work with .onReceive as well, see my code and make it better, this way is Version 3.0.0! LoL

Code:

class TextModel: ObservableObject
{
    @Published var sizeOfFont: CGFloat = 20.0
    @Published var sizeOfText: CGSize = .zero
}




struct ContentView: View
{
    
    @StateObject var textModel = TextModel()
    
    var body: some View
    {
        
        Spacer()
        
        Text("Size of Text: " + String(format: "%.0f", textModel.sizeOfText.width) + "⭐︎" +  String(format: "%.0f", textModel.sizeOfText.height))
            .font(.system(size: textModel.sizeOfFont))
            .background(Color.yellow)
            .background( sizeOfViewOptionA(textModel: textModel) )
        
        Spacer()
        
        Text("Size of Text: " + String(format: "%.0f", textModel.sizeOfText.width) + "⭐︎" +  String(format: "%.0f", textModel.sizeOfText.height))
            .font(.system(size: textModel.sizeOfFont))
            .background(Color.red)
            .background( sizeOfViewOptionB(textModel: textModel) )
  
        
        Spacer()
        
        Text("Font size:" + "\(textModel.sizeOfFont)")
        
        Slider(value: $textModel.sizeOfFont, in: 20...40, step: 1)
            .padding()
        
        Spacer()
  
    }
  
}



struct sizeOfViewOptionA: View
{
    
    @ObservedObject var textModel: TextModel

    var body: some View {
        
        GeometryReader { proxy in
            
            HStack{}
                .onAppear() {textModel.sizeOfText = proxy.size}
                .onChange(of: textModel.sizeOfFont) { _ in textModel.sizeOfText = proxy.size}
        }
 
    }
}


struct sizeOfViewOptionB: View
{
    
    @ObservedObject var textModel: TextModel

    var body: some View {
        
        GeometryReader { proxy in
            
            HStack{ Color.clear }
                .onReceive(textModel.$sizeOfFont) { _ in textModel.sizeOfText = proxy.size}
        }
 
    }
}

Upvotes: 0

swiftPunk
swiftPunk

Reputation: 1

After spending time on GeometryReader I find a easer way to get the size of any view also Text and I just wanted Answer my Question, Go ahead try my Code or Refactor it or make it more smaller if you can, I am pleased to see your way, here what I made:

struct ContentView: View {
    @State var sizeOfText: CGSize = .zero
    @State var fontSizeOfText: CGFloat = 20.0

    var body: some View {
        Text("Size of Text: " + String(format: "%.0f", sizeOfText.width) + "⭐︎" + String(format: "%.0f", sizeOfText.height))
            .font(.system(size: fontSizeOfText))
            .background(Color.yellow)
            .background(sizeOfView(fontSizeOfText: $fontSizeOfText, sizeOfText: $sizeOfText))

        Spacer()
        Text("Font size:" + "\(fontSizeOfText)")
        Slider(value: $fontSizeOfText, in: 20...40, step: 1)
            .padding()
        Spacer()
    }
}

struct sizeOfView: View {
    @Binding var fontSizeOfText: CGFloat
    @Binding var sizeOfText: CGSize

    var body: some View {
        GeometryReader { proxy in

            HStack {}
                .onAppear { sizeOfText = proxy.size }
                .onChange(of: fontSizeOfText) { _ in sizeOfText = proxy.size }
        }
    }
}

Upvotes: 3

pawello2222
pawello2222

Reputation: 54466

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:

Upvotes: 18

Related Questions