Как я могу вертикально центрировать NSTextView в представлении SwiftUI с NSViewRepresentable? - PullRequest
1 голос
/ 08 апреля 2020

У меня есть простое NSViewRepresentable, которое оборачивает 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)
  }
}

Я хочу центрировать его по вертикали, поэтому я использую VStack с Spacers выше и ниже. Я хочу оставить левые и правые поля вокруг него пропорциональными размеру окна, поэтому я обернул их в GeometryReader и 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()
      }
    }
  }
}

Горизонтальная рамка работает нормально, но вертикальная интервал вообще не работает:

initial state

(начальное состояние)

И изменение размера окна приводит к появлению махинаций:

resize from bottom

(изменить размер снизу)

resize from bottom to top, then down again

(изменить размер снизу вверх, затем снова вниз)

О, и предварительный просмотр полностью бананов:

Preview

Если я заменю свой пользовательский TextView на SwiftUI native Text, макет работает нормально, что говорит о том, что проблема в TextView. (Обратите также внимание, что размер окна по умолчанию меньше.)

native Text

Вероятно, размер NSTextView установлен неправильно , Если я добавлю nsView.sizeToFit() к updateNSView():

    func updateNSView(_ nsView: NSTextView, context: Context) {
        nsView.textStorage?.setAttributedString(text)
        nsView.sizeToFit()
    }

, это уменьшит размер окна по умолчанию и предотвратит отскок текста от нижней части окна при изменении размера вверх и вниз, но текст по-прежнему закреплен в верхней части окна, предварительный просмотр все еще не работает, а при изменении размера снизу все равно NSTextView временно заполняет большую часть высоты окна.

Другие вещи, которые я пробовал возиться с: isVerticallyResizable, setContentCompressionResistancePriority, autoresizingMask и translatesAutoresizingMaskIntoConstraints, invalidateIntrinsicContentSize. Кажется, что ничего из этого не имеет никакого очевидного значения.

Кажется, что я хочу обновить размер NSTextView, когда изменяются размеры представлений SwiftUI, но, кажется, нет никакого очевидного способа сделать это. это, и я все равно могу ошибаться.

1 Ответ

1 голос
/ 10 апреля 2020

Если вам просто нужно показать NSAttributedString, как я понял, тогда подход, основанный на NSTextField, как показано ниже, более уместен, поскольку NSTextView не имеет внутренней компоновки по умолчанию и требует явного внешнего фрейма.

demo

Здесь представлено измененное представление, ContentView не требует изменений.

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