In swiftUI, minimumScaleFactor is used for all child elements in the HStack. How do I get the font sizes of all child elements closer to each other
This is my current code and the result of running it:
import SwiftUI
struct Test: Hashable, Decodable {
var label: String
var value: String
var key: String
}
struct TestView: View {
@State private var fields: [Test] = [
Test(label: "label1", value: "value1value1", key: "KEY1"),
Test(label: "label2", value: "value2value2value2value2value2value2value2value2value2value2value2value2", key: "KEY2"),
Test(label: "label3", value: "value3value3value3", key: "KEY3")
]
var body: some View {
HStack {
ForEach(fields.indices, id: \.self) { index in
let field = fields[index]
if index > 0 {
Spacer(minLength: 10)
}
VStack(alignment: .leading) {
Text(field.label)
.lineLimit(1)
.font(.system(size: 16, weight: .bold))
Text(field.value)
.lineLimit(1)
.minimumScaleFactor(0.4)
}
}
}
}
}
This is the result of the run:
In this case, the font size of the label is fixed and vlaue is the value entered by the user in the future. According to the code running result, we can see that the font size of value1 and value3 will be much larger than the font size of value2 (which is correct). What I hope to achieve is to reduce the font size of value1 and value3 to make them closer to the font of value2. Instead of keeping them all the same font size, this will make the entire HStack display look more beautiful.
That's what I expected:
The root cause of this problem seems to be that HStack assigns width to each element, even if the text is less, it can be allocated to a large width, so the text font size difference is large; But I don't know how to change this behavior of HStack, or is there another label that can replace HStack?
If anyone can help it would be greatly appreciated! Thank you!
The answer to How to apply the same font size to multiple Texts within an HStack and size the HStack to fit its parent? shows a technique for scaling texts with different sizes by the same amount (it was my answer). The basic idea is that you apply the modifier .scaledToFit()
to the container.
For your example, you need to apply the modifier to the outer HStack
, not the inner VStack
:
// TestView
HStack {
ForEach(fields.indices, id: \.self) { index in
// ...
}
}
.scaledToFit() // 👈 added
Since you are allowing a minimum scaling factor of 0.4, this makes all of the scaled text very small:
You might like to consider increasing the minimum scaling factor to, say, 0.7:
Text(field.value)
.lineLimit(1)
.minimumScaleFactor(0.7) // 👈 modified
EDIT Following up on your comment - if I understand correctly, you want all of the scalable text to scale a bit (= get smaller by a bit), but long texts should be scaled even more (= get even smaller).
To achieve this, try making the minimum scale factor depend on the width of the text. For example:
This algorithm can be implemented with a function:
private func minimumScaleFactorForText(text: String) -> CGFloat {
let excessLength = max(0, text.count - 30)
let extraScaling = (CGFloat(min(excessLength, 30)) / 30) * 0.2
return 0.6 - extraScaling
}
Since the texts may now be scaled by different amounts, you may want to add alignment: .top
to the HStack
, so that the labels are aligned.
The updated example:
HStack(alignment: .top) { // 👈 alignment added
ForEach(fields.indices, id: \.self) { index in
// ...
Text(field.value)
.lineLimit(1)
.minimumScaleFactor(minimumScaleFactorForText(text: field.value))
// ...
}
}
.scaledToFit()
EDIT2 You said in another comment that you need to set the height of the view. I am assuming, this is to make sure that the view always has a consistent height.
Instead of setting the height with a fixed value, you could use a hidden placeholder to establish the height needed for when the text is not scaled:
VStack(alignment: .leading) {
Text(field.label)
.lineLimit(1)
.font(.system(size: 16, weight: .bold))
ZStack(alignment: .leadingFirstTextBaseline) {
Text("X")
.hidden()
Text(field.value)
.lineLimit(1)
.minimumScaleFactor(minimumScaleFactorForText(text: field.value))
}
}
.border(.red)
If space is limited and you need the maximum height to be smaller, I would suggest the following additional changes:
VStack
spacing by supplying a (small) spacing
parameter to the VStack
VStack(alignment: .leading, spacing: 2) { // 👈 spacing added
Text(field.label)
.lineLimit(1)
.fontWeight(.bold) // 👈 changed
ZStack(alignment: .leadingFirstTextBaseline) {
Text("X")
.hidden()
Text(field.value)
.lineLimit(1)
.minimumScaleFactor(minimumScaleFactorForText(text: field.value))
}
}
.font(.subheadline) // 👈 added
.border(.red)