Issue #587
Use NSTextVIew
From https://github.com/twostraws/ControlRoom/blob/main/ControlRoom/NSViewWrappers/TextView.swift
import SwiftUI
/// A wrapper around NSTextView so we can get multiline text editing in SwiftUI.
struct TextView: NSViewRepresentable {
@Binding private var text: String
private let isEditable: Bool
init(text: Binding<String>, isEditable: Bool = true) {
_text = text
self.isEditable = isEditable
}
init(text: String) {
self.init(text: Binding<String>.constant(text), isEditable: false)
}
func makeNSView(context: Context) -> NSScrollView {
let text = NSTextView()
text.backgroundColor = isEditable ? .textBackgroundColor : .clear
text.delegate = context.coordinator
text.isRichText = false
text.font = NSFont.monospacedSystemFont(ofSize: NSFont.systemFontSize, weight: .regular)
text.autoresizingMask = [.width]
text.translatesAutoresizingMaskIntoConstraints = true
text.isVerticallyResizable = true
text.isHorizontallyResizable = false
text.isEditable = isEditable
let scroll = NSScrollView()
scroll.hasVerticalScroller = true
scroll.documentView = text
scroll.drawsBackground = false
return scroll
}
func updateNSView(_ view: NSScrollView, context: Context) {
let text = view.documentView as? NSTextView
text?.string = self.text
guard context.coordinator.selectedRanges.count > 0 else {
return
}
text?.selectedRanges = context.coordinator.selectedRanges
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, NSTextViewDelegate {
var parent: TextView
var selectedRanges = [NSValue]()
init(_ parent: TextView) {
self.parent = parent
}
func textDidChange(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else { return }
parent.text = textView.string
selectedRanges = textView.selectedRanges
}
}
}
Use xib
Create a xib called ScrollableTextView
, and drag just Scrollable text view
as top object
Connect just the textView
property
import AppKit
class ScrollableTextView: NSScrollView {
@IBOutlet var textView: NSTextView!
}
Conform to NSViewRepresentable
import SwiftUI
struct TextView: NSViewRepresentable {
@Binding var text: String
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeNSView(context: Context) -> ScrollableTextView {
var views: NSArray?
Bundle.main.loadNibNamed("ScrollableTextView", owner: nil, topLevelObjects: &views)
let scrollableTextView = views!.compactMap({ $0 as? ScrollableTextView }).first!
scrollableTextView.textView.delegate = context.coordinator
return scrollableTextView
}
func updateNSView(_ nsView: ScrollableTextView, context: Context) {
guard nsView.textView.string != text else { return }
nsView.textView.string = text
}
class Coordinator: NSObject, NSTextViewDelegate {
let parent: TextView
init(_ textView: TextView) {
self.parent = textView
}
func textDidChange(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else { return }
self.parent.text = textView.string
}
}
}
There seems to be a bug that if we have open and close curly braces, any character typed into NSTextView will move the cursor to the end. This is easily fixed with a check in updateNSView