I've got a simple NSViewRepresentable
that wraps an NSTextView
.
struct TextView: NSViewRepresentable {
typealias NSViewType = NSTextView
var text: NSAttributedString
func makeNSView(context: Context) -> NSTextView {
let view = NSTextView()
// set background color to show view bounds
view.backgroundColor = NSColor.systemBlue
view.drawsBackground = true
view.isEditable = false
view.isSelectable = false
return view
}
func updateNSView(_ nsView: NSTextView, context: Context) {
nsView.textStorage?.setAttributedString(text)
}
}
I want to center it vertically, so I'm using a VStack
with Spacers
above and below.
I want to leave left and right margins around it proportional to the window size, so I've wrapped it in a GeometryReader
and frame()
.
struct ContentView: View {
func textView() -> some View {
let text = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \
tempor incididunt ut labore et dolore magna aliqua.
"""
// Note: real application has more complex text than this, *not* using
// NSAttributedString isn't really an option, at least till SwiftUI has
// proper rich text support.
let size: CGFloat = 18
let font = NSFont(name: "LeagueSpartan-Bold", size: size)
let attrString = NSAttributedString(
string: text,
attributes: [ .foregroundColor: NSColor.systemYellow,
.font: font ?? NSFont.systemFont(ofSize: size) ])
return TextView(text: attrString)
}
var body: some View {
let textView: some View = self.textView()
return ZStack {
// use ZStack to provide window background color
Color(NSColor.systemTeal)
VStack {
Spacer()
GeometryReader { m2 in
textView.frame(width: m2.size.width / 1.618)
}
Spacer()
}
}
}
}
The horizontal frame works fine, but the vertical spacing isn't working at all:
(initial state)
And resizing the window produces shenanigans:
(resize from bottom)
(resize from bottom to top, then down again)
Oh, and the Preview is completely bananas:
If I replace my custom TextView
with a SwiftUI
native Text
, the layout works fine, which suggests that the problem is in TextView
. (Note also that the default window size is smaller.)
It seems likely that the size of the NSTextView
isn't getting set properly. If I add nsView.sizeToFit()
to updateNSView()
:
func updateNSView(_ nsView: NSTextView, context: Context) {
nsView.textStorage?.setAttributedString(text)
nsView.sizeToFit()
}
this gets me the smaller default window size, and stops the text from bouncing to the bottom of the window when I resize up and down, but the text is still pinned near the top of the window, the preview is still broken, and resizing from the bottom still gets the NSTextView
temporarily filling most of the height of the window.
Other things I've tried fiddling with: isVerticallyResizable
, setContentCompressionResistancePriority
, autoresizingMask
and translatesAutoresizingMaskIntoConstraints
, invalidateIntrinsicContentSize
. None of these seem to make any obvious difference.
It seems like what I want is to update the NSTextView
size when the containing SwiftUI views resize, but there doesn't seem to be any obvious way to do that, and I might anyway be wrong.
If you just need to show NSAttributedString
, as I understood, then approach based on NSTextField
, as shown below, is more appropriate, because NSTextView
does not have default internal layout and requires explicit external frame.
Here is modified representable, ContentView
does not require changes.
struct TextView: NSViewRepresentable {
typealias NSViewType = NSTextField
var text: NSAttributedString
func makeNSView(context: Context) -> NSTextField {
let view = NSTextField()
// set background color to show view bounds
view.backgroundColor = NSColor.systemBlue
view.drawsBackground = true
view.isEditable = false
view.isSelectable = false
view.lineBreakMode = .byWordWrapping
view.maximumNumberOfLines = 0
view.translatesAutoresizingMaskIntoConstraints = false
view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
view.setContentCompressionResistancePriority(.required, for: .vertical)
return view
}
func updateNSView(_ nsView: NSTextField, context: Context) {
nsView.attributedStringValue = text
}
}