9

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)
    }
}
pawello2222
  • 46,897
  • 22
  • 145
  • 209
ios coder
  • 1
  • 4
  • 31
  • 91

4 Answers4

15

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:

pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • what a nice time we have you here! thanks. How I can make GeometryReader goes inside Text because I what this GeometryReader takes the size of Text? – ios coder Oct 20 '20 at 20:35
  • @Omid A view placed in the `background` has the same size as the view it's attached to. That's why the GeometryReader placed in the background has the same size as the Text. – pawello2222 Oct 20 '20 at 20:38
  • All of your code working and good, but for it is not best way to take it as the best way! because width of Text is a child of 2 things, 1-String of Text, 2.The font of Text, So Xcode for creating our Text use does parents to make that child(width), why apple make use to Re-Doing the calculation that already Done in stage of Text creation? what is all points of GeometryReader? all the answers is inside Text already finished. we just read the Text value for its width, I mean Text definitely knows its width, why we should ask from GeometryReader? – ios coder Oct 20 '20 at 20:55
  • @Omid 1) *what is all points of GeometryReader?* - to read the size of the view. However, GeometryReader consumes all available space - that's why it needs to be put in the background (to have the same size as Text). 2) *I mean Text definitely knows its width* - nope, it doesn't. SwiftUI is not UIKit. – pawello2222 Oct 20 '20 at 21:03
  • I know the usage of GeometryReader in general, I meant a philosophical question when I said : "what is all points of GeometryReader?" I wanted to tell why it should even exist! You said Text does not know his own Length! I believe Text is a view, and every view has own special size, and also special id in memory to find it, if Text as view does not know his own size, who knows then? – ios coder Oct 20 '20 at 21:10
  • @Omid *if Text as view does not know his own size, who knows then?* - GeometryReader. I understand what you mean, just SwiftUI is not designed this way. In most cases you don't need to get the width of the Text. You let SwiftUI to layout views for you. – pawello2222 Oct 20 '20 at 21:13
  • I am trying to find a understandable thing about width of Text, forget about GeometryReader. when we build a new SwiftUI Project, what we get? We get "Hello World!" Text. for rendering and showing process our app or Xcode needs to answer serious questions, Like: 1-String, 2-Background, 3-ForgroundColor, . . . Now I am coming to your word "SwiftUI is not UIKit", I know it, SwiftUI is a Professional Bar Keeper which you just can Order White Russian, and you just order it, but in UIKit you should exactly say how much Vodka or Baileys must mixed, So we can ask SwiftUI the detail of Drink!why not? – ios coder Oct 20 '20 at 21:24
  • Why I should go order another White Russian for getting info about Drink from GeometryReader? I could ask that Pro bar keeper for my question! because he already done that Drink(Text) and he knows all. – ios coder Oct 20 '20 at 21:26
  • I just found another way, please see my Answer – ios coder Oct 21 '20 at 01:49
6

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)
    }
}
Shaybc
  • 2,628
  • 29
  • 43
2

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 }
        }
    }
}
pawello2222
  • 46,897
  • 22
  • 145
  • 209
ios coder
  • 1
  • 4
  • 31
  • 91
  • Yes, this also works, the preferences approach is just more generic. As you see you *have to* use GeometryReader :) Btw you can combine `onAppear` and `onChange` - see [Combine onChange and onAppear events in SwiftUI view?](https://stackoverflow.com/a/64392848/8697793) – pawello2222 Oct 21 '20 at 08:01
  • thanks, I wanted to make it with .onReceive At first but I was not successful! any Idea? – ios coder Oct 21 '20 at 15:04
  • You can do `onReceive(Just(fontSizeOfText)) { ... }` and you need to import `Combine` – pawello2222 Oct 21 '20 at 15:12
  • You can first `import Combine` and then in your code `.onReceive(Just(fontSizeOfText)) { _ in sizeOfText = proxy.size }` But if you still have problems please create a new question for this. – pawello2222 Oct 21 '20 at 17:11
  • @pawello2222: plz see my new answer. – ios coder Oct 21 '20 at 23:17
0

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}
        }
 
    }
}
ios coder
  • 1
  • 4
  • 31
  • 91