为啥 macOS 中的 SwiftUI 多行换行文本在 Preview 中有效,但在实际应用中无效?
Posted
技术标签:
【中文标题】为啥 macOS 中的 SwiftUI 多行换行文本在 Preview 中有效,但在实际应用中无效?【英文标题】:Why SwiftUI multiline wrapping Text in macOS works in Preview, but not in real app?为什么 macOS 中的 SwiftUI 多行换行文本在 Preview 中有效,但在实际应用中无效? 【发布时间】:2021-03-22 23:48:24 【问题描述】:我有一个显示图像和文本的 SwiftUI 视图。
import SwiftUI
#if canImport(UIKit)
typealias AppColor = UIColor
#else
typealias AppColor = NSColor
#endif
struct ReportErrorUI: View
enum Order
case even, odd
let error: String
let order: Order
init(error: String, order: Order)
self.error = error
self.order = order
var body: some SwiftUI.View
let bgColor = (order == .even ? AppColor.systemGreen : .systemRed).withAlphaComponent(0.5)
return VStack(alignment: .leading, spacing: 0, content:
HStack(alignment: .center, spacing: 8, content:
Image("icon.error")
Text(error).font(.body)
).padding(8)
Color(.magenta).frame(height: 2)
).background(Color(bgColor))
现在我可以预览了:
import SwiftUI
@available(OSX 11.0, *)
struct ReportErrorUI_Previews: PreviewProvider
static let shortText = "Etiam habebis sem dicantur magna mollis euismod."
static let longText = "Tityre, tu patulae recubans sub tegmine fagi dolor. Idque Caesaris facere voluntate liceret: sese habere. Unam incolunt Belgae, aliam Aquitani, tertiam."
static var previews: some SwiftUI.View
Group
VStack(spacing: 0)
ReportErrorUI(error: longText, order: .even)
ReportErrorUI(error: shortText, order: .odd)
.previewLayout(.sizeThatFits)
.background(Color.white).padding().frame(width: 320)
看起来是这样的:
如您所见,Text
视图按预期工作 - 长文本被换行。
现在我通过NSHostingController
或NSHostingView
将这个ReportErrorUI
与老式NSTextField
多行标签一起添加到现有的AppKit 应用程序中。两个 UI 元素都添加到 NSStackView
。
class MainViewController: ReusableViewController
private lazy var stackView = NSStackView()
override func setupUI()
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 8).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8).isActive = true
stackView.widthAnchor.constraint(greaterThanOrEqualToConstant: 120).isActive = true
stackView.heightAnchor.constraint(greaterThanOrEqualToConstant: 80).isActive = true
stackView.orientation = .vertical
stackView.spacing = 8
stackView.distribution = .fill
stackView.alignment = .leading
// Wrappable multiline label. See: https://***.com/a/35920653/1418981
let label = NSTextField()
label.stringValue = "Excepteur sint obcaecat cupiditat non proident culpa. A communi observantia non est recedendum."
label.setContentHuggingPriority(.init(1), for: .horizontal)
label.setContentCompressionResistancePriority(.init(1), for: .horizontal)
label.isEditable = false
label.backgroundColor = NSColor.magenta.withAlphaComponent(0.4)
label.isBezeled = false
label.isSelectable = true
//> Below 4 lines seems not needed.
// label.usesSingleLineMode = false
// label.lineBreakMode = .byWordWrapping
// label.cell?.wraps = true
// label.cell?.isScrollable = false
//<
stackView.addArrangedSubview(label)
do
let ui = ReportErrorUI(error: "Excepteur sint obcaecat cupiditat non proident culpa. A communi observantia non est recedendum.", order: .even)
let view = NSHostingView(rootView: ui)
view.setContentHuggingPriority(.init(1), for: .horizontal)
view.setContentCompressionResistancePriority(.init(1), for: .horizontal)
view.setContentCompressionResistancePriority(.required, for: .vertical)
stackView.addArrangedSubview(view)
do
let ui = ReportErrorUI(error: "Excepteur sint obcaecat cupiditat non proident culpa. A communi observantia non est recedendum.", order: .odd)
let vc = NSHostingController(rootView: ui)
addChild(vc)
vc.view.setContentHuggingPriority(.init(1), for: .horizontal)
vc.view.setContentCompressionResistancePriority(.init(1), for: .horizontal)
vc.view.setContentCompressionResistancePriority(.required, for: .vertical)
stackView.addArrangedSubview(vc.view)
do
let view = ReusableView()
view.backgroundColor = .yellow
view.heightAnchor.constraint(greaterThanOrEqualToConstant: 12).isActive = true
stackView.addArrangedSubview(view)
结果老派NSTextField
工作正常 - 多行文本在应用程序窗口调整大小时自动换行。但是 SwiftUI Text
视图中的文本没有被换行。
来自 UIKit 世界的技巧,例如 lineLimit(nil)
或 .fixedSize(horizontal: false, vertical: true)
,要么不起作用,要么破坏应用程序窗口布局。
例如.fixedSize(horizontal: false, vertical: true)
的工作原理 - 应用程序窗口布局已损坏。应用程序窗口不能垂直调整大小。
setContentCompressionResistancePriority
或 setContentHuggingPriority
的任意组合用于 horizontal
、vertical
轴设置在 NSHostingView
或 NSHostingController
上均无济于事。
如何使多行包装 SwiftUI Text
在 macOS 上正常工作?
【问题讨论】:
【参考方案1】:macOS 的解决方案。
感谢这篇文章:https://zalogatomek.medium.com/swiftui-missing-intrinsic-content-size-how-to-get-it-6eca8178a71f
struct IntrinsicContentSizePreferenceKey: PreferenceKey
static let defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize)
value = nextValue()
extension View
func readIntrinsicContentSize(to size: Binding<CGSize>) -> some View
background(GeometryReader proxy in
Color.clear.preference(
key: IntrinsicContentSizePreferenceKey.self,
value: proxy.size
)
)
.onPreferenceChange(IntrinsicContentSizePreferenceKey.self)
size.wrappedValue = $0
struct MultilineText: View
@State private var textSize: CGSize = .zero
let text: String
init(_ text: String)
self.text = text
var body: some SwiftUI.View
Text(text).readIntrinsicContentSize(to: $textSize).fixedSize(horizontal: false, vertical: true).frame(height: textSize.height)
【讨论】:
以上是关于为啥 macOS 中的 SwiftUI 多行换行文本在 Preview 中有效,但在实际应用中无效?的主要内容,如果未能解决你的问题,请参考以下文章
WPFListBox GridViewColumn Header 文字换行文字多行显示