ios 在SwiftUI中通过UIViewRepresentable调整UILabel的大小,就像文本一样,以便换行

ylamdve6  于 2022-12-01  发布在  iOS
关注(0)|答案(4)|浏览(196)

我们的目标是通过UIViewRepresentable集成一个UILabel,以与Text相同的方式调整大小--使用可用的宽度并换行以适应所有文本,从而增加HStack的高度,而不是无限地扩展宽度。这与this question非常相似,尽管这个公认的答案不适用于我使用的包含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 Package 到GeometryReader中,并将width传递到LabelView以设置preferredMaxLayoutWidth,由于某种原因,它将是0.0。如果将GeometryReader移到ScrollView之外,可以获得宽度,但这样它将是滚动视图宽度。而不是SwiftUI为HStack中的LabelView建议的宽度。
如果我指定label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal),效果会更好,但仍然不能显示所有文本,奇怪的是,它会在3行处截断LabelView,在2行处截断第二个Text

gg0vcinb

gg0vcinb1#

这里的问题出在ScrollView中,它需要明确的高度,但representable没有提供。可能的解决方案是动态计算换行文本高度并显式指定它。

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

使用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
            }
        }
    }
}
cigdeys3

cigdeys32#

这不是一个具体的答案。
我意外地发现固定大小修饰符可以帮助自动调整UILabel的大小以适应其内容大小。
第一个
演示:

另外,如果你不想提供最大高度,你可以将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.
    """
gj3fmq9x

gj3fmq9x3#

问题出在标签的宽度限制上。不需要修改内容压缩阻力或其他标签设置。只需设置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)

您可以使用GeometryReaderUIScreen.main.bounds.width来提供。但是底部有一些额外的填充。这就是为什么我使用了一些负的底部填充:

sdnqo3pr

sdnqo3pr4#

目前的答案中没有一个解决方案足够接近SwiftUI的本质,换一种思路,既然SwiftUI对Text足够好,为什么不做这样的事情呢?

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

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

简单地说,让Text告诉你标签的高度并作为标签的基础,毕竟Text是SwiftUI的儿子。

相关问题