簡體   English   中英

通過 UIViewRepresentable 調整 SwiftUI 中的 UILabel 大小,如 Text 以換行多行

[英]Size a UILabel in SwiftUI via UIViewRepresentable like Text to wrap multiple lines

目標是讓通過UIViewRepresentable集成的UILabel的大小與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()
    }
}

在 HStack 中使用兩個文本會產生所需的布局: 文字與文字

使用 Text 和 LabelView 會導致這種不需要的布局: 文本和標簽視圖

如果您將LabelView包裝在GeometryReader中並將width傳遞給LabelView以設置preferredMaxLayoutWidth ,由於某種原因它是0.0 如果將GeometryReader移到ScrollView之外,您可以獲得一個寬度,但它是滾動視圖寬度,而不是 SwiftUI 為HStack中的LabelView建議的寬度。

相反,如果我指定label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)它工作得更好,但仍然不顯示所有文本,它奇怪地截斷了 3 行的LabelView和 2 行的第二個Text

這里的問題在於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
            }
        }
    }
}

這不是一個具體的答案。

我偶然發現固定大小修飾符可以幫助將 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()
        }
    }
}
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
    }
    
}

演示:

一個段落

幾句話


此外,如果您不想提供最大高度。 您可以將preferredMaxLayoutWidth設置為您的屏幕寬度。 如果你把它放在 updateUIView 方法中,每當屏幕方向改變時,這個方法也會被調用。

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

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


為了完整起見,這是我用來測試視圖的屬性文本設置。 但是,它不應該影響視圖調整大小的工作方式。

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.
    """

問題在於 label 的寬度限制。無需修改內容抗壓縮性或其他 label 設置。 只需設置preferredMaxLayoutWidth值:

public struct AttributedText: UIViewRepresentable {

    public func makeUIView(context: Context) -> UIViewType {
        let label = UIViewType()
        label.numberOfLines = 0
        label.lineBreakMode = .byWordWrapping
        label.attributedText = attributedText
        label.preferredMaxLayoutWidth = width
        label.textAlignment = .left
        return label
    }

    public func updateUIView(_ uiView: UIViewType, context: Context) {

    }

    public typealias UIViewType = UILabel
    public let attributedText: String
    public let width: CGFloat

    public init(attributedText: String, width: CGFloat) {
        self.attributedText = attributedText
        self.width = width
    }
}

用法:

AttributedLabel(
    attributedText: someAttributedText,
    width: UIScreen.main.bounds.width - 80
).padding(.bottom, -20)

您可以使用GeometryReader或使用UIScreen.main.bounds.width提供。 但是底部有一些額外的填充。 這就是為什么我使用了一些負底部填充:

SwiftUI 中的屬性文本

當前答案中沒有一個解決方案與 SwiftUI 的本質足夠接近。 另一種思路,既然 SwiftUI 對 Text 來說已經足夠好了,為什么不做這樣的事情呢?

Text(yourstr)
 .foregroundColor(.clear) <<<<<<<--------👈
 .multilineTextAlignment(.leading)
 .frame(maxWidth:.infinity,maxHeight: .infinity, alignment: .leading)

 .overlay(
         Your ActiveLabelStack() <<<<<<<--------👈
                ,alignment: .center
            )

簡單地說,讓文字告訴你高度,是Label的底。 畢竟Text是SwiftUI的兒子。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM