Scaling Down a Text's Font Size to Fit Its Length to Another Text in Swiftui

Scaling down a text's font size to fit its length to another text in SwiftUI

You need to get the size of the reference view

struct ScaledTextView: View {
@State var textSize: CGSize = CGSize.zero
var body: some View {
VStack{
Text("A reference text")
.viewSize(viewSize: $textSize) //Get initial size and any changes
Text("(Adjust the length of this line to match the one above)")
//If you want it to be a single line set the limit
//.lineLimit(1)

//Set the dependent's view frame, you can just set the width if you want, height is optional
.frame(width: textSize.width, height: textSize.height)
.scaledToFit()
.minimumScaleFactor(0.01)
}
}
}
//Make it reusable and keep the view clean
extension View{
func viewSize(viewSize: Binding<CGSize>) -> some View{
return self.background(
//Get the size of the View
GeometryReader{geo in
Text("")
//detects changes such as landscape, etc
.onChange(of: geo.size, perform: {value in
//set the size
viewSize.wrappedValue = value
})
//Initial size
.onAppear(perform: {
//set the size
viewSize.wrappedValue = geo.size
})
}
)
}
}

How to scale text to fit parent view with SwiftUI?

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))
}
}

Sample Image
Sample Image

SwiftUI scale text to fit the width and height

on UIKit minimumScaleFactor and minimumFontSize are ineffective unless you set adjustsFontSizeToFitWidth.

Instead of creating a VStack, you may want to place all the figures, separated by newlines, in a single text, and fit the text to the bounding rectangle.

With SwiftUI the solution looks like:

struct aView: View {
let array = [4, 5, 6]

var body: some View {
HStack(spacing: 100) {
Text(array.map {String($0)}.joined(separator: "\n"))
.font(.system(size: 1000))
.minimumScaleFactor(0.01)
.frame(width: 60, height: 300)
.border(Color.primary)
Text(array.map {String($0)}.joined(separator: "\n"))
.font(.system(size: 1000))
.minimumScaleFactor(0.01)
.frame(width: 30, height: 150)
.border(Color.primary)
}
}
}

SwiftUI: Dynamically change font size based on a string length

There is a simple answer, however, I have found that it only works sometimes.

Text("Hello World!")
.minimumScaleFactor(0.4)

This should do what you want, only scale down the text if it will not fit. It will only scale down the text the amount required to fit in the text's frame.

I have found though that on some devices, swiftUI will scale down the text to the minimum factor no matter what. This seems to depend on the app. In some of my apps, it only works in the simulator while in others it just works fine on any device.

Hope this helps.

Automatic adjust font size in Text() of Swiftui?

Use minimumScaleFactor(_:) https://developer.apple.com/documentation/swiftui/text/minimumscalefactor(_:)

struct ContentView: View {
var body: some View {
VStack{
Text("Test string")
.minimumScaleFactor(0.1) //<--Here
.frame(width: 15, height: 15)
}
}
}

SwiftUI - Text minimumScaleFactor not scaling only when needed

Adding a GeometryReader around the VStack that my two Text views were in solved this. As requested by @gohnjanotis:

GeometryReader { proxy in
VStack(alignment: .center, spacing: 0) {
HStack {
Image("medal \(self.place)").resizable()
.foregroundColor(self.color)
.frame(width: 40, height: 40)

Spacer()
Text(self.username)
.minimumScaleFactor(0.1)
.font(.bold(16))
.lineLimit(1)
.frame(alignment: .trailing)
.foregroundColor(Color("mediumTextColor"))
.layoutPriority(1)

}
.padding(.top, 10)
.padding(.horizontal, 10)
Spacer()
Text(self.score)
.font(.extraBold(60))
.foregroundColor(self.color)
.lineLimit(1)
.minimumScaleFactor(0.1)
.layoutPriority(1)
.padding(.horizontal, 10)
.padding(.bottom, 10)
.offset(y: -10)
}
.frame(width: proxy.size.width, height: proxy.size.height, alignment: .top)
}
.frame(maxHeight: 130)

SwiftUi how to prevent text to automatically truncate in order to fit

That is a bit more involved. It takes a lot of interplay with PreferenceKeys. This code could probably be shortened, but it works...

struct InlineTextView: View {

@State var userName = "longtext username test"
@State var userNamePref: CGFloat = 0
@State var additionalTextPref: CGFloat = 0
@State var hStackPref: CGFloat = 0
@State var totalWidth: CGFloat = 0
@State var isTooWide: Bool = true

var body: some View {
GeometryReader {outerGeometry in
VStack(alignment: .leading, spacing: 20) {
Group {
if isTooWide {
Text(userName)
.background(GeometryReader { userGeometry in
Color.clear.preference(
key: UsernameWidthPreferenceKey.self,
value: userGeometry.size.width)
})
additionalInlineText
.onTapGesture {
print("tapped")
}
.background(GeometryReader { addGeometry in
Color.clear.preference(
key: AdditionalWidthPreferenceKey.self,
value: addGeometry.size.width)
})
} else {
HStack {
Text(userName)
.background(GeometryReader { geometry in
Color.clear.preference(
key: UsernameWidthPreferenceKey.self,
value: geometry.size.width)
})
additionalInlineTextView
.background(GeometryReader { geometry in
Color.clear.preference(
key: AdditionalWidthPreferenceKey.self,
value: geometry.size.width)
})
}
}
}
.onPreferenceChange(UsernameWidthPreferenceKey.self) {
userNamePref = max($0, userNamePref)
totalWidth = userNamePref + additionalTextPref
isTooWide = totalWidth > outerGeometry.size.width
print("userNamePref = \(userNamePref)")
print("additionalTextPref = \(additionalTextPref)")
print("hStackPref = \(hStackPref)")
print("totalWidth = \(totalWidth)")
print("outerGeo = \(outerGeometry.size.width)")
}
.onPreferenceChange(AdditionalWidthPreferenceKey.self) {
additionalTextPref = max($0, additionalTextPref)
totalWidth = userNamePref + additionalTextPref
isTooWide = totalWidth > outerGeometry.size.width
print("userNamePref = \(userNamePref)")
print("additionalTextPref = \(additionalTextPref)")
print("hStackPref = \(hStackPref)")
print("totalWidth = \(totalWidth)")
print("outerGeo = \(outerGeometry.size.width)")
}

Button {
if userName == "longtext username test" {
userNamePref = 0
userName = "userName"
} else {
userNamePref = 0
userName = "longtext username test"
}
} label: {
Text("Change name.")
}

}
}
}

var additionalInlineText: Text {
// Block 1
Text(Image(systemName: "network")) +
Text("12345") +

// Block 2
Text(Image(systemName: "network")) +
Text("2.0K") +

// Block 3
Text(Image(systemName: "network")) +
Text("2H") +

// Block 4
Text(Image(systemName: "network")) +
Text(Image(systemName: "network")) +
Text(Image(systemName: "network")) +
Text("1234")


}
}

private extension InlineTextView {
struct UsernameWidthPreferenceKey: PreferenceKey {
static let defaultValue: CGFloat = 0

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

struct AdditionalWidthPreferenceKey: PreferenceKey {
static let defaultValue: CGFloat = 0

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

Edit: made additionalInlineText tappable and changed the name to make it clear that it was a text.

And yes, the GeometryReader determines the width of the screen, and the preference keys determine the width of the views. I start them in the VStack so they read their max width. If you notice when userName is changed, I reset the userNamePref to get a new reading. This is a bit fragile, but it is the only way to do what you want.



Related Topics



Leave a reply



Submit