通过 UIViewRepresentable 在 SwiftUI 中调整 UILabel 的大小,如 Text 以包裹多行

Jor*_*n H 3 uilabel ios swiftui uiviewrepresentable

目标是获得一个UILabel集成通孔UIViewRepresentable以相同的方式调整大小Text- 使用可用宽度并换行到多行以适应所有文本,从而增加HStack其所在的高度,而不是无限扩展宽度。这是非常相似的这个问题,但接受的答案不适合我使用涉及的布局工作ScrollViewVStackHStack

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()
    }
}
Run Code Online (Sandbox Code Playgroud)

在 HStack 中使用两个文本会产生这种所需的布局: 文字和文字

使用 Text 和 LabelView 会导致这种不需要的布局: 文本和标签视图

如果您将LabelViewin包装起来GeometryReader并传递一个widthintoLabelView来设置preferredMaxLayoutWidth,那是0.0出于某种原因。你可以得到的宽度,如果你移动GeometryReader之外ScrollView,但那么它的滚动视图宽度,而不是宽度SwiftUI提议为LabelViewHStack

相反,如果我指定label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)它效果更好,但仍然没有显示所有文本,它会奇怪地截断第LabelView3 行和第Text2 行的第二行。

Asp*_*eri 8

这里的问题是ScrollView其中需要确定的高度,但可表示不提供它。可能的解决方案是动态计算换行文本高度并明确指定。

注意:由于高度是动态计算的,因此仅在运行时可用,因此无法使用预览进行测试。

使用 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
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 在“makeUIView”中包含“label.setContentHuggingPriority(.defaultHigh, for: .horizo​​ntal)”也可能很有用。这将尝试拥抱视图,这与文本的行为相同。 (3认同)
  • 您介意检查一下我的答案吗,因为我对 UIKit 不太熟悉,尤其是程序化创建视图。谢谢! (2认同)

zrf*_*ank 7

这不是一个具体的答案。

我意外地发现固定大小修饰符可以帮助自动将 UILabel 调整为其内容大小。

struct ContentView: View {
    let attributedString: NSAttributedString
    
    var body: some View {
        ScrollView {
            LazyVStack(spacing: 10) {
                RepresentedUILabelView(attributedText: attributedString)
                    .frame(maxHeight: 300)
                    .fixedSize(horizontal: false, vertical: true)
                    .background(Color.orange.opacity(0.5))
            }
            .padding()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
struct RepresentedUILabelView: UIViewRepresentable {
    typealias UIViewType = UILabel
    
    var attributedText: NSAttributedString
    
    func makeUIView(context: Context) -> UILabel {
        let label = UILabel()
        
        label.numberOfLines = 0
     
        label.lineBreakMode = .byTruncatingTail

        label.textAlignment = .justified
        
        label.allowsDefaultTighteningForTruncation = true
        
        // Compression resistance is important to enable auto resizing of this view,
        // that base on the SwiftUI layout.
        // Especially when the SwiftUI frame modifier applied to this view.
        label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        label.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
        
        // Maybe this is not necessary.
        label.clipsToBounds = true
        
        return label
    }
    
    func updateUIView(_ uiView: UILabel, context: Context) {
        print(#fileID, #function)
        
        uiView.attributedText = attributedText
    }
    
}
Run Code Online (Sandbox Code Playgroud)

演示:

一个段落

几句话


此外,如果您不想提供最大高度。您可以将其设置preferredMaxLayoutWidth为您的屏幕宽度。如果你把它放在 updateUIView 方法中,每当屏幕方向改变时,这个方法也会被调用。

    func updateUIView(_ uiView: UILabel, context: Context) {
        
        uiView.attributedText = attributedText
        
        uiView.preferredMaxLayoutWidth = 0.9 * UIScreen.main.bounds.width
    }
Run Code Online (Sandbox Code Playgroud)

例如,没有最大框架高度。


为了完整起见,这是我用来测试视图的属性文本设置。但是,它不应该影响视图大小调整的工作方式。

func makeAttributedString(fromString s: String) -> NSAttributedString {
    let content = NSMutableAttributedString(string: s)
    
    let paraStyle = NSMutableParagraphStyle()
    paraStyle.alignment = .justified
    paraStyle.lineHeightMultiple = 1.25
    paraStyle.lineBreakMode = .byTruncatingTail
    paraStyle.hyphenationFactor = 1.0
    
    content.addAttribute(.paragraphStyle,
                         value: paraStyle,
                         range: NSMakeRange(0, s.count))
    
    // First letter/word
    content.addAttributes([
        .font      : UIFont.systemFont(ofSize: 40, weight: .bold),
        .expansion : 0,
        .kern      : -0.2
    ], range: NSMakeRange(0, 1))
    
    return content
}

let coupleWords = "Hello, world!"

let multilineEN = """
    Went to the woods because I wished to live deliberately, to front only the essential facts of life, and see if I could not learn what it had to teach, and not, when I came to die, discover that I had not lived. I did not wish to live what was not life, living is so dear! Nor did I wish to practise resignation, unless it was quite necessary?"

    I went to the woods because I wished to live deliberately, to front only the essential facts of life, and see if I could.

    I went to the woods because I wished to live deliberately, to front only the essential facts of life, and see if I could not learn what it had to teach, and not.
    """
Run Code Online (Sandbox Code Playgroud)