如何在没有固定宽度的情况下将文本包装在 UILabel 中(通过 UIViewRepresentable)?

How can I get text to wrap in a UILabel (via UIViewRepresentable) without having a fixed width?

将 lineBreakMode 设置为 byWordWrapping 并将 numberOfLines 设置为 0 似乎不够:

struct MyTextView: UIViewRepresentable {
    func makeUIView(context: Context) -> UILabel {
        let label = UILabel()
        label.lineBreakMode = .byWordWrapping
        label.numberOfLines = 0
        label.text = "Here's a lot of text for you to display. It won't fit on the screen."
        return label
    }

    func updateUIView(_ view: UILabel, context: Context) {
    }
}

struct MyTextView_Previews: PreviewProvider {
    static var previews: some View {
        MyTextView()
            .previewLayout(.fixed(width: 300, height: 200))
    }
}

无论我对 lineBreakMode 使用哪种设置,文本都不会换行。 canvas 预览和实时预览都是这样的:

我得到的最接近的设置是设置 preferredMaxLayoutWidth,它确实会导致文本换行,但似乎没有表示 "whatever size the View is".

的值

可能的解决方案是在 MyTextView 上将宽度声明为变量:

struct MyTextView: UIViewRepresentable {

    var width: CGFloat

    func makeUIView(context: Context) -> UILabel {
        let label = UILabel()
        label.lineBreakMode = .byWordWrapping
        label.numberOfLines = 0
        label.preferredMaxLayoutWidth = width
        label.text = "Here's a lot of text for you to display. It won't fit on the screen."
        return label
    }

    func updateUIView(_ view: UILabel, context: Context) {
    }
}

然后使用 GeometryReader 找出有多少 space 可用并将其传递给初始化器:

struct ExampleView: View {

    var body: some View {
        GeometryReader { geometry in
            MyTextView(width: geometry.size.width)
        }
    }
}

尝试在 makeUIView() func 中使用这条魔法线

label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)

我发现了一种“令人讨厌”的方法,它允许 UILabel 在用作 UIViewRepresentable 时正确换行(,即使在 ScrollView), 不需要 GeometryReader:

每当创建您的 UILabel:

label.setContentCompressionResistancePriority(.defaultLow,
                                              for: .horizontal)
label.setContentHuggingPriority(.defaultHigh,
                                for: .vertical)

这确保:

  • 标签会断线并且没有无限宽度
  • 标签不会增加不必要的高度,这在某些情况下可能会发生。

然后...

  • width 属性 添加到您的 UIViewRepresentable 将用于设置 preferredMaxLayoutWidth
  • 用你的UIViewRepresentable变成香草SwiftUI.View
  • 添加 GeometryReader 作为覆盖以防止扩展
  • 软延迟后触发测量,修改某些状态以触发新的传递。

即:

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

旧答案:

...

在你的 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.