gpt4 book ai didi

swiftui - 如何在 SwiftUI 列表中以正确的高度渲染多行文本?

转载 作者:行者123 更新时间:2023-12-03 09:36:23 24 4
gpt4 key购买 nike

我想要一个显示多行文本的 SwiftUI View ,具有以下要求:

  • 适用于 macOS 和 iOS。
  • 显示大量字符串(每个字符串由一个单独的模型对象支持)。
  • 我可以对多行​​文本进行任意样式设置。
  • 每个文本字符串可以是任意长度,可能跨越多个行和段落。
  • 每个文本字符串的最大宽度固定为容器的宽度。高度根据文本的实际长度而变化。
  • 没有滚动每个单独的文本,只有列表。
  • 文本中的链接必须是可点击/可点击的。
  • 文本是只读的,不必是可编辑的。

  • 感觉最合适的解决方案是拥有一个 ListView ,包装原生 UITextView/NSTextView。

    这是我到目前为止所拥有的。它实现了大多数要求,除了具有正确的行高度。
    //
    // ListWithNativeTexts.swift
    // SUIToy
    //
    // Created by Jaanus Kase on 03.05.2020.
    // Copyright © 2020 Jaanus Kase. All rights reserved.
    //

    import SwiftUI

    let number = 20

    struct ListWithNativeTexts: View {
    var body: some View {
    List(texts(count: number), id: \.self) { text in
    NativeTextView(string: text)
    }
    }
    }

    struct ListWithNativeTexts_Previews: PreviewProvider {
    static var previews: some View {
    ListWithNativeTexts()
    }
    }

    func texts(count: Int) -> [String] {
    return (1...count).map {
    (1...$0).reduce("Hello https://example.com:", { $0 + " " + String($1) })
    }
    }

    #if os(iOS)
    typealias NativeFont = UIFont
    typealias NativeColor = UIColor

    struct NativeTextView: UIViewRepresentable {

    var string: String

    func makeUIView(context: Context) -> UITextView {
    let textView = UITextView()

    textView.isEditable = false
    textView.isScrollEnabled = false
    textView.dataDetectorTypes = .link
    textView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
    textView.textContainer.lineFragmentPadding = 0

    let attributed = attributedString(for: string)
    textView.attributedText = attributed

    return textView
    }

    func updateUIView(_ textView: UITextView, context: Context) {
    }

    }
    #else
    typealias NativeFont = NSFont
    typealias NativeColor = NSColor

    struct NativeTextView: NSViewRepresentable {

    var string: String

    func makeNSView(context: Context) -> NSTextView {
    let textView = NSTextView()
    textView.isEditable = false
    textView.isAutomaticLinkDetectionEnabled = true
    textView.isAutomaticDataDetectionEnabled = true
    textView.textContainer?.lineFragmentPadding = 0
    textView.backgroundColor = NSColor.clear

    textView.textStorage?.append(attributedString(for: string))
    textView.isEditable = true
    textView.checkTextInDocument(nil) // make links clickable
    textView.isEditable = false

    return textView
    }

    func updateNSView(_ textView: NSTextView, context: Context) {

    }

    }
    #endif

    func attributedString(for string: String) -> NSAttributedString {
    let attributedString = NSMutableAttributedString(string: string)
    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.lineSpacing = 4
    let range = NSMakeRange(0, (string as NSString).length)

    attributedString.addAttribute(.font, value: NativeFont.systemFont(ofSize: 24, weight: .regular), range: range)
    attributedString.addAttribute(.foregroundColor, value: NativeColor.red, range: range)
    attributedString.addAttribute(.backgroundColor, value: NativeColor.yellow, range: range)
    attributedString.addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
    return attributedString
    }

    这是它在 iOS 上的输出。 macOS 输出类似。

    iOS output

    如何获得此解决方案来调整具有正确高度的 TextView 的大小?

    我尝试过但未在此处显示的一种方法是“从外向内”给出高度 - 用框架指定列表行本身的高度。当我知道宽度时,我可以计算 NSAttributedString 的高度,我可以用 geoReader 获得。这几乎可以工作,但有问题,而且感觉不对,所以我没有在这里展示。

    最佳答案

    调整列表行的大小不适用于 SwiftUI。

    但是,我已经研究出如何在堆栈中显示原生 UITextViews 的滚动,其中每个项目都根据其属性文本的高度动态调整大小。

    I have put 2 point spacing between each item and tested with 80 items using your text generator.

    Here are the first three screenshots of scroll, and another screenshot showing the very end of the scroll.











    这是带有属性文本高度和常规字符串大小扩展名的完整类。
    import SwiftUI

    let number = 80

    struct ListWithNativeTexts: View {
    let rows = texts(count:number)
    var body: some View {
    GeometryReader { geometry in
    ScrollView {
    VStack(spacing: 2) {
    ForEach(0..<self.rows.count, id: \.self) { i in
    self.makeView(geometry, text: self.rows[i])
    }
    }
    }
    }
    }
    func makeView(_ geometry: GeometryProxy, text: String) -> some View {
    print(geometry.size.width, geometry.size.height)

    // for a regular string size (not attributed text)
    // let textSize = text.size(width: geometry.size.width, font: UIFont.systemFont(ofSize: 17.0, weight: .regular), padding: UIEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 0))
    // print("textSize: \(textSize)")
    // return NativeTextView(string: text).frame(width: geometry.size.width, height: textSize.height)
    let attributed = attributedString(for: text)
    let height = attributed.height(containerWidth: geometry.size.width)
    print("height: \(height)")
    return NativeTextView(string: text).frame(width: geometry.size.width, height: height)
    }
    }

    struct ListWithNativeTexts_Previews: PreviewProvider {
    static var previews: some View {
    ListWithNativeTexts()
    }
    }

    func texts(count: Int) -> [String] {
    return (1...count).map {
    (1...$0).reduce("Hello https://example.com:", { $0 + " " + String($1) })
    }
    }

    #if os(iOS)
    typealias NativeFont = UIFont
    typealias NativeColor = UIColor

    struct NativeTextView: UIViewRepresentable {

    var string: String

    func makeUIView(context: Context) -> UITextView {
    let textView = UITextView()

    textView.isEditable = false
    textView.isScrollEnabled = false
    textView.dataDetectorTypes = .link
    textView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
    textView.textContainer.lineFragmentPadding = 0

    let attributed = attributedString(for: string)
    textView.attributedText = attributed

    // for a regular string size (not attributed text)
    // textView.font = UIFont.systemFont(ofSize: 17.0, weight: .regular)
    // textView.text = string

    return textView
    }

    func updateUIView(_ textView: UITextView, context: Context) {
    }

    }
    #else
    typealias NativeFont = NSFont
    typealias NativeColor = NSColor

    struct NativeTextView: NSViewRepresentable {

    var string: String

    func makeNSView(context: Context) -> NSTextView {
    let textView = NSTextView()
    textView.isEditable = false
    textView.isAutomaticLinkDetectionEnabled = true
    textView.isAutomaticDataDetectionEnabled = true
    textView.textContainer?.lineFragmentPadding = 0
    textView.backgroundColor = NSColor.clear

    textView.textStorage?.append(attributedString(for: string))
    textView.isEditable = true
    textView.checkTextInDocument(nil) // make links clickable
    textView.isEditable = false

    return textView
    }

    func updateNSView(_ textView: NSTextView, context: Context) {

    }

    }
    #endif

    func attributedString(for string: String) -> NSAttributedString {
    let attributedString = NSMutableAttributedString(string: string)
    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.lineSpacing = 4
    let range = NSMakeRange(0, (string as NSString).length)

    attributedString.addAttribute(.font, value: NativeFont.systemFont(ofSize: 24, weight: .regular), range: range)
    attributedString.addAttribute(.foregroundColor, value: NativeColor.red, range: range)
    attributedString.addAttribute(.backgroundColor, value: NativeColor.yellow, range: range)
    attributedString.addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
    return attributedString
    }

    extension String {
    func size(width:CGFloat = 220.0, font: UIFont = UIFont.systemFont(ofSize: 17.0, weight: .regular), padding: UIEdgeInsets? = nil) -> CGSize {
    let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
    label.numberOfLines = 0
    label.lineBreakMode = NSLineBreakMode.byWordWrapping
    label.font = font
    label.text = self

    label.sizeToFit()

    if let pad = padding{
    // add padding
    return CGSize(width: label.frame.width + pad.left + pad.right, height: label.frame.height + pad.top + pad.bottom)
    } else {
    return CGSize(width: label.frame.width, height: label.frame.height)
    }
    }
    }

    extension NSAttributedString {

    func height(containerWidth: CGFloat) -> CGFloat {

    let rect = self.boundingRect(with: CGSize.init(width: containerWidth, height: CGFloat.greatestFiniteMagnitude),
    options: [.usesLineFragmentOrigin, .usesFontLeading],
    context: nil)
    return ceil(rect.size.height)
    }

    func width(containerHeight: CGFloat) -> CGFloat {

    let rect = self.boundingRect(with: CGSize.init(width: CGFloat.greatestFiniteMagnitude, height: containerHeight),
    options: [.usesLineFragmentOrigin, .usesFontLeading],
    context: nil)
    return ceil(rect.size.width)
    }
    }

    关于swiftui - 如何在 SwiftUI 列表中以正确的高度渲染多行文本?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61580838/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com