3

I'm looking to create an editable multi-line text box in Swift UI for macOS. I'd like to create a syntax highlighting text editor, so it'd be multi-line and change styles throughout the lines. Is this possible with the framework in its current state? I can find barely any documentation about it online.

deeBo
  • 836
  • 11
  • 24
  • It looks like SwiftUI now has a TextEditor component here: https://developer.apple.com/documentation/swiftui/texteditor But it doesn't support changing styles like colours throughout the line – deeBo Jul 30 '20 at 12:56

2 Answers2

1

it can be useful, this is my first solution to get an NSTextView with SwiftUI:

import SwiftUI
import os

let uiLog = OSLog(subsystem: "com.visual-science.CryptiK", category: "UI")

class  EditorCoordinator : NSObject, NSTextViewDelegate {
  let textView: NSTextView;
  let scrollView : NSScrollView
  let text : Binding<NSAttributedString>

  init(binding: Binding<NSAttributedString>) {
    text = binding

    textView = NSTextView(frame: .zero)
    textView.autoresizingMask = [.height, .width]
    textView.textStorage?.setAttributedString(text.wrappedValue)
    textView.textColor = NSColor.textColor

    scrollView = NSScrollView(frame: .zero)
    scrollView.hasVerticalScroller = true
    scrollView.autohidesScrollers = false
    scrollView.autoresizingMask = [.height, .width]
    scrollView.documentView = textView

    super.init()
    textView.delegate = self
  }

  func textDidChange(_ notification: Notification) {
    switch  notification.name {
    case NSText.didChangeNotification :
      text.wrappedValue = (notification.object as? NSTextView)?.textStorage ?? NSAttributedString(string: "")
    default:
      os_log(.error, log: uiLog, "Coordinator received unwanted notification")
    }
  }

}

struct DataTextEditorView: View, NSViewRepresentable {
  typealias Coordinator = EditorCoordinator
  typealias NSViewType = NSScrollView

  let text : Binding<NSAttributedString>

  func makeNSView(context: NSViewRepresentableContext<DataTextEditorView>) -> DataTextEditorView.NSViewType {
    os_log(.info, log: uiLog, "%@", context.coordinator.scrollView)
    return context.coordinator.scrollView
  }

  func updateNSView(_ nsView: NSScrollView, context: NSViewRepresentableContext<DataTextEditorView>) {
    os_log(.debug, log: uiLog, "%@", context.coordinator.self)
    os_log(.debug, log: uiLog, "%@", text.wrappedValue)
  }

  func makeCoordinator() -> EditorCoordinator {
    os_log(.info, log: uiLog, "makeCoordinator")
    let coordinator =  EditorCoordinator(binding: text)
    return coordinator
  }

}

If like me, you just need to edit some text without attributes, you can replace NSAttributedString with just String and adapt the code for this simpler case.

igerard
  • 66
  • 5
  • Something important here, the SwiftUI struct is created a lot of time, but the coordinator is cached somewhat... then everything is truly handled by the coordinator to not create a scrollview+textview every time the text change :) – igerard Dec 27 '19 at 00:13
-4

You can have a multi-line TextField in SwiftUI (you just need to call .lineLimit(N) on it to become multi-line capable), but text with multiple separate styles isn't currently supported. A TextField just has a single font & style.

You can roll it yourself, though: create an NSViewRepresentable implementation that vends an NSTextView and bind it to an NSMutableAttributedText property. You'll need to handle all the text view model synchronization and bindings updates yourself, but it is certainly do-able.

marcprux
  • 9,845
  • 3
  • 55
  • 72