Skip to content

Instantly share code, notes, and snippets.

@antingle
Forked from unnamedd/MacEditorTextView.swift
Last active April 14, 2025 01:02
Show Gist options
  • Save antingle/fbca6d99f3207d1cc3a50b0a1e60bfbd to your computer and use it in GitHub Desktop.
Save antingle/fbca6d99f3207d1cc3a50b0a1e60bfbd to your computer and use it in GitHub Desktop.
CustomMacTextView - A simple NSScrollView wrapped by SwiftUI with placeholder text
//
// CustomMacTextView.swift
//
// MacEditorv2 Created by Marc Maset - 2021
// Changes inspired from MacEditorTextView by Thiago Holanda
//
// Modified by Anthony Ingle - 2022
//
import SwiftUI
struct CustomMacTextView: NSViewRepresentable {
var placeholderText: String?
@Binding var text: String
var font: NSFont = .systemFont(ofSize: 14, weight: .regular)
var onSubmit : () -> Void = {}
var onTextChange : (String) -> Void = { _ in }
var onEditingChanged: () -> Void = {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeNSView(context: Context) -> NSScrollView {
let theTextView = PlaceholderNSTextView.scrollableTextView()
let textView = (theTextView.documentView as! PlaceholderNSTextView)
textView.delegate = context.coordinator
textView.string = text
textView.drawsBackground = false
textView.font = font
textView.placeholderText = placeholderText
theTextView.hasVerticalScroller = false
return theTextView
}
func updateNSView(_ view: NSScrollView, context: Context) {
guard let textView = view.documentView as? NSTextView else {
return
}
textView.string = text
}
}
extension CustomMacTextView {
class Coordinator: NSObject, NSTextViewDelegate {
var parent: CustomMacTextView
var affectedCharRange: NSRange?
init(_ parent: CustomMacTextView) {
self.parent = parent
}
func textDidBeginEditing(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else {
return
}
self.parent.text = textView.string
self.parent.onEditingChanged()
}
func textDidChange(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else {
return
}
// Update text
self.parent.text = textView.string
self.parent.onTextChange(textView.string)
}
func textDidEndEditing(_ notification: Notification) {
guard let textView = notification.object as? NSTextView else {
return
}
self.parent.text = textView.string
self.parent.onSubmit()
}
func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool {
return true
}
// handles commands
func textView(_ textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
if (commandSelector == #selector(NSResponder.insertNewline(_:))) {
// Do something against ENTER key
self.parent.onSubmit()
return true
}
// return true if the action was handled; otherwise false
return false
}
}
}
// for setting a proper placeholder text on an NSTextView
fileprivate class PlaceholderNSTextView: NSTextView {
@objc private var placeholderAttributedString: NSAttributedString?
var placeholderText: String? {
didSet {
var attributes = [NSAttributedString.Key: AnyObject]()
attributes[.font] = font
attributes[.foregroundColor] = NSColor.gray
let captionAttributedString = NSAttributedString(string: placeholderText ?? "", attributes: attributes)
placeholderAttributedString = captionAttributedString
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment