简体   繁体   中英

How do I vertically center an NSTextView in a SwiftUI view with NSViewRepresentable?

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

backup

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM