【发布时间】:2020-08-18 04:58:02
【问题描述】:
我想要一个显示多行文本的 SwiftUI 视图,具有以下要求:
- 适用于 macOS 和 iOS。
- 显示大量字符串(每个字符串由一个单独的模型对象支持)。
- 我可以对多行文本进行任意样式设置。
- 每个文本字符串可以是任意长度,可能跨越多行和多段。
- 每个文本字符串的最大宽度固定为容器的宽度。高度根据文字的实际长度而变化。
- 每个单独的文本都没有滚动,只有列表。
- 文本中的链接必须是可点击/可点击的。
- 文本是只读的,不一定是可编辑的。
感觉最合适的解决方案是拥有一个列表视图,包装原生 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 输出类似。
如何获得此解决方案以调整具有正确高度的文本视图?
我尝试过但未在此处显示的一种方法是给出“从外向内”的高度 - 用框架指定列表行本身的高度。当我知道宽度时,我可以计算 NSAttributedString 的高度,这可以通过 geoReader 获得。这几乎可以工作,但是有问题,而且感觉不对,所以我不在这里展示它。
【问题讨论】:
-
correct heights是什么意思?据我从屏幕截图中看到的行文本像往常一样适合列表。 -
这里有两个问题。 1) 我显示的内容看起来正确只是因为我选择的大小大致对应于列表行的默认高度。如果文本更大或更小,则会被截断或填充太大。 2)更重要的是,文本行没有换行。您会看到每一行都包含一个数字。 1 2 3 适合同一行。 4 5 6 应该换到下一行。
-
我在How do I create a multiline TextField in SwiftUI? 中解决的类似问题。它基于 UITextView,所以应该会有帮助。
-
如果同时向表中添加许多视图,则高度的 dispatch_main 异步会提供可怕的性能。我真的希望有一些更清洁的东西也适用于 macOS。 Apple 论坛上也有相同的主题,我有另一个工作版本,但我不喜欢这样:forums.developer.apple.com/thread/132489
标签: swiftui swiftui-list uiviewrepresentable nsviewrepresentable