How to render multiline text in SwiftUI List with correct height?

Sizing List rows doesn't work well with SwiftUI.

However, I have worked out how to display a scroll of native UITextViews in a stack, where each item is dynamically sized based on the height of its attributedText.

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.

Here is the full class with extensions for attributedText height and regular string size, as well.

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