The goal is to get a UILabel integrated via UIViewRepresentable to size the same way Text does - use the available width and wrap to multiple lines to fit all the text thus increasing the height of the HStack it's in, instead of expanding in width infinitely. This is very similar to this question, though the accepted answer does not work for the layout I'm using involving a ScrollView, VStack, and HStack.
struct ContentView: View {
var body: some View {
ScrollView {
VStack {
HStack {
Text("Hello, World")
Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla tempor justo quam, quis suscipit leo sollicitudin vel.")
//LabelView(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla tempor justo quam, quis suscipit leo sollicitudin vel.")
}
HStack {
Text("Hello, World")
Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla tempor justo quam, quis suscipit leo sollicitudin vel.")
}
Spacer()
}
}
}
}
struct LabelView: UIViewRepresentable {
var text: String
func makeUIView(context: UIViewRepresentableContext<LabelView>) -> UILabel {
let label = UILabel()
label.text = text
label.numberOfLines = 0
return label
}
func updateUIView(_ uiView: UILabel, context: UIViewRepresentableContext<LabelView>) {
uiView.text = text
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Using two Texts in the HStack results in this desired layout:

Using the Text and LabelView results in this undesired layout:

If you wrap the LabelView in GeometryReader and pass a width into LabelView to set the preferredMaxLayoutWidth, it's 0.0 for some reason. You can get a width if you move the GeometryReader outside the ScrollView, but then it's the scroll view width, not the width SwiftUI is proposing for the LabelView in the HStack.
If instead I specify label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) it works better, but still doesn't display all the text, it strangely truncates the LabelView at 3 lines and the second Text at 2 lines.
The problem here is in ScrollView which requires definite height, but representable does not provide it. The possible solution is to dynamically calculate wrapped text height and specify it explicitly.
Note: as height is calculated dynamically it is available only in run-time, so cannot be tested with Preview.
Tested with Xcode 12 / iOS 14

struct LabelView: View {
var text: String
@State private var height: CGFloat = .zero
var body: some View {
InternalLabelView(text: text, dynamicHeight: $height)
.frame(minHeight: height)
}
struct InternalLabelView: UIViewRepresentable {
var text: String
@Binding var dynamicHeight: CGFloat
func makeUIView(context: Context) -> UILabel {
let label = UILabel()
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return label
}
func updateUIView(_ uiView: UILabel, context: Context) {
uiView.text = text
DispatchQueue.main.async {
dynamicHeight = uiView.sizeThatFits(CGSize(width: uiView.bounds.width, height: CGFloat.greatestFiniteMagnitude)).height
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With