Строки списка размеров не работают с SwiftUI.
Однако я разработал способ отображения прокрутки собственных UITextViews в стеке, где каждый элемент имеет динамический размер в зависимости от высоты его attribuText.
Я поместил 2 точки между каждым элементом и протестировал 80 элементов с помощью вашего текстового генератора.
Вот первые три скриншота прокрутки и еще один скриншот, показывающий самый конец скролла.
Здесь представлен полный класс с расширениями для атрибута attributeText и обычного размера строки.
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)
}
}