skip to Main Content

I need to calculate the upper bound for the maximum font size, at which the text with the given font fits on one line.

In order to calculate the upper bound for the font size picker, I need to calculate the width of the Text. And I am stuck here.
How can I calculate the width of the Text? Please help.

If there’s another solution to my problem, I would this appreciate as well.

enter image description here

Here’s my code:

extension Text {
    var width: CGFloat {
        return 0 // MARK: Calculate Text width somehow?
    }
}

struct EditView: View {
    @State private var string: String = "Hello, world!"
    @State private var fontSize: CGFloat = 20
    @State private var fontWeight: Font.Weight = .regular
    @State private var fontWidth: Font.Width = .standard
    @State private var fontDesign: Font.Design = .default
    
    let squareWidth: CGFloat = 150
    
    var font: Font {
        Font.system(size: fontSize, weight: fontWeight, design: fontDesign).width(fontWidth)
    }
    var fontSizeRange: ClosedRange<CGFloat>  {
        let upperBound = maximumFontSizeThatFitsOnOneLineFor(string: string, with: font, in: squareWidth)
        return 10...upperBound
    }
    
    func maximumFontSizeThatFitsOnOneLineFor(string: String, with font: Font, in maximumTextWidth: CGFloat) -> CGFloat {
        var fontSize: CGFloat = 100
        var text = Text(string).font(font)
        var textWidth: CGFloat = text.width

        while textWidth > maximumTextWidth {
            fontSize -= 1
        }
        return fontSize
    }
        
    var body: some View {
        VStack {
            HStack {
                Spacer()
                AnotherView(string: string, font: font)
                        .frame(maxWidth: squareWidth, maxHeight: squareWidth)
                        .clipShape(RoundedRectangle(cornerRadius: 20))
                Spacer()
            }
            .padding()

            Form {
                TextField("Enter text", text: $string)
                Stepper("Font Size: (fontSize.formatted())", value: $fontSize, in: fontSizeRange)
                Picker("Weight", selection: $fontWeight) {
                    Text("Ultra Light").tag(Font.Weight.ultraLight)
                    Text("Regular").tag(Font.Weight.regular)
                    Text("Heavy").tag(Font.Weight.heavy)
                }
                Picker("Width", selection: $fontWidth) {
                    Text("Compressed").tag(Font.Width.compressed)
                    Text("Standard").tag(Font.Width.standard)
                    Text("Expanded").tag(Font.Width.expanded)
                }
                Picker("Design", selection: $fontDesign) {
                    Text("Default").tag(Font.Design.default)
                    Text("Monospaced").tag(Font.Design.monospaced)
                    Text("Serif").tag(Font.Design.serif)
                }
            }
        }
    }
}

struct AnotherView: View {
    var string: String
    var font: Font
    
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(string)
                    .lineLimit(1)
                    .font(font)
                Spacer(minLength: 0)
                Text("🌍")
                    .font(.largeTitle)
            }
            Spacer(minLength: 0)
        }
        .foregroundColor(.white)
        .padding(16)
        .background { Color.red }

    }
}

struct EditView_Previews: PreviewProvider {
    static var previews: some View {
        EditView()
    }
}

2

Answers


  1. You could try something like this. Use a GeometryReader on a clear background of the text. It is a bit hacky but works:

    @State var textWidth: CGFloat = 0
    @State private var fontSize: CGFloat = 20
    
    var body: some View {
        Text("My Text")
            .font(.system(size: fontSize))
            .background {
                GeometryReader { proxy in
                    Color.clear
                        .onAppear {
                            textWidth = proxy.size.width
                        }
                        .onChange(of: fontSize) { _ in
                            textWidth = proxy.size.width
                        }
                }
            }
    }
    
    Login or Signup to reply.
  2. You can just slap on a large font size, then let Swift scale it down to the largest size that fits:

        Text(string)
            .lineLimit(1)
            .allowsTightening(true) // <-- ADDED
            .minimumScaleFactor(0.1) // <-- ADDED
            .font(font)
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search