I found a somehow "nasty" approach that allows a UILabel to properly wrap when used as a UIViewRepresentable (even when inside a ScrollView), without the need for GeometryReader:
Whenever creating your UILabel:
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) label.setContentHuggingPriority(.defaultHigh, for: .vertical)
This ensures that:
- the label will break line and not have an infinite width
- the label will not add grow unnecessarily in height, which may happen in some circumstances.
Then...
- Add a
width property to your UIViewRepresentable that will be used to set the preferredMaxLayoutWidth - Use your
UIViewRepresentable into a vanilla SwiftUI.View - Add a
GeometryReader as an overlay to prevent expansion - Trigger the measurement after a soft delay, modifying some state to trigger a new pass.
i.e.:
public var body: some View { MyRepresentable(width: $width, separator: separator, content: fragments) .overlay(geometryOverlay) .onAppear { shouldReadGeometry = true } } // MARK: - Private Props @State private var width: CGFloat? @State private var shouldReadGeometry = false @ViewBuilder var geometryOverlay: some View { if shouldReadGeometry { GeometryReader { g in SwiftUI.Color.clear.onAppear { self.width = g.size.width } } } }
OLD ANSWER:
...
In your updateUIView(_:context:):
if let sv = uiView.superview, sv.bounds.width != 0 { let shouldReloadState = uiView.preferredMaxLayoutWidth != sv.bounds.width uiView.preferredMaxLayoutWidth = sv.bounds.width if shouldReloadState { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { self.stateToggle.toggle() // a Bool @State you can add in your struct } } }
Disclaimer: I'm not a huge fan of main.async calls, particularly when they come in combination with some arbitrary delay, but this seems to get the work done in a consistent way.
import SwiftUI.