Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to scale text to fit parent view with SwiftUI?

Tags:

swift

swiftui

I'd like to create a text view inside a circle view. The font size should be automatically set to fit the size of the circle. How can this be done in SwiftUI? I tried scaledToFill and scaledToFit modifiers, but they have no effect on the Text view:

struct ContentView : View {
    var body: some View {
        ZStack {
            Circle().strokeBorder(Color.red, lineWidth: 30)
            Text("Text").scaledToFill()
        }
    }
}
like image 845
G. Marc Avatar asked Jul 15 '19 08:07

G. Marc


4 Answers

One possible "hack" is to use a big font size and a small scale factor so it will shrink itself:

ZStack {
    Circle().strokeBorder(Color.red, lineWidth: 30)

    Text("Text")
        .padding(40)
        .font(.system(size: 500))
        .minimumScaleFactor(0.01)
     }
}
like image 65
szemian Avatar answered Oct 17 '22 20:10

szemian


One can use GeometryReader in order to make it also work in landscape mode.

It first checks if the width or the height is smaller and then adjusts the font size according to the smaller of these.

GeometryReader{g in
    ZStack {
        Circle().strokeBorder(Color.red, lineWidth: 30)
        Text("Text")
            .font(.system(size: g.size.height > g.size.width ? g.size.width * 0.4: g.size.height * 0.4))
    }
}

enter image description here enter image description here

like image 34
simibac Avatar answered Oct 17 '22 21:10

simibac


You want to allow your text to:

  • shrink up to a certain limit
  • on 1 (or several) line(s)

You choose this scale factor limit to suit your need. Typically you don't shrink beyond readable or beyond the limit that will make your design look bad

struct ContentView : View {
var body: some View {
    ZStack {
        Circle().strokeBorder(Color.red, lineWidth: 30)
        Text("Text")
            .scaledToFill()
            .minimumScaleFactor(0.5)
            .lineLimit(1)
    }
}

}

like image 50
GrandSteph Avatar answered Oct 17 '22 21:10

GrandSteph


Here's a solution that hides the text resizing code in a custom modifier which can be applied to any View, not just a Circle, and takes a parameter specifying the fraction of the View that the text should occupy.

(I have to agree that while @szemian's solution is still not ideal, her method seems to be the best we can do with the current SwiftUI implementation because of issues inherent in the others. @simibac's answer requires fiddling to find a new magic number to replace 0.4 any time the text or its attributes--font, weight, etc.--are changed, and @giuseppe-sapienza's doesn't allow the size of the circle to be specified, only the font size of the text.)

struct FitToWidth: ViewModifier {
    var fraction: CGFloat = 1.0
    func body(content: Content) -> some View {
        GeometryReader { g in
        content
            .font(.system(size: 1000))
            .minimumScaleFactor(0.005)
            .lineLimit(1)
            .frame(width: g.size.width*self.fraction)
        }
    }
}

Using the modifier, the code becomes just this:

    var body: some View {
        Circle().strokeBorder(Color.red, lineWidth: 30)
            .aspectRatio(contentMode: .fit)
            .overlay(Text("Text")
                .modifier(FitToWidth(fraction: fraction)))
    }

Also, when a future version of Xcode offers SwiftUI improvements that obviate the .minimumScaleFactor hack, you can just update the modifier code to use it. :)

If you want to see how the fraction parameter works, here's code to let you adjust it interactively with a slider:

struct ContentView: View {

    @State var fraction: CGFloat = 0.5

    var body: some View {
        VStack {
            Spacer()
            Circle().strokeBorder(Color.red, lineWidth: 30)
                .aspectRatio(contentMode: .fit)
                .overlay(Text("Text")
                .modifier(FitToWidth(fraction: fraction)))
            Slider(value: $fraction, in:0.1...0.9, step: 0.1).padding()
            Text("Fraction: \(fraction, specifier: "%.1f")")
            Spacer()
        }
    }
}

and here's what it looks like:

Code running on iPhone X

like image 17
Anton Avatar answered Oct 17 '22 20:10

Anton