Skip to content

Instantly share code, notes, and snippets.

@antingle
Forked from unnamedd/MacEditorTextView.swift
Last active April 14, 2025 01:02

Revisions

  1. antingle revised this gist May 4, 2022. No changes.
  2. antingle revised this gist May 4, 2022. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions CustomMacTextView.swift
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    //
    // CustomMacTextView.swift
    //
    // Foundation by Marc Maset - 2021
    // MacEditorv2 Created by Marc Maset - 2021
    // Changes inspired from MacEditorTextView by Thiago Holanda
    //
    // Modified by Anthony Ingle - 2022
    @@ -115,7 +115,7 @@ fileprivate class PlaceholderNSTextView: NSTextView {
    didSet {
    var attributes = [NSAttributedString.Key: AnyObject]()
    attributes[.font] = font
    attributes[.foregroundColor] = NSColor.darkGray
    attributes[.foregroundColor] = NSColor.gray
    let captionAttributedString = NSAttributedString(string: placeholderText ?? "", attributes: attributes)
    placeholderAttributedString = captionAttributedString
    }
  3. antingle revised this gist May 4, 2022. 1 changed file with 20 additions and 16 deletions.
    36 changes: 20 additions & 16 deletions CustomMacTextView.swift
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    //
    // CustomMacTextView.swift
    //
    // MacEditorv2 Created by Marc Maset - 2021
    // Foundation by Marc Maset - 2021
    // Changes inspired from MacEditorTextView by Thiago Holanda
    //
    // Modified by Anthony Ingle - 2022
    @@ -13,6 +13,7 @@ struct CustomMacTextView: NSViewRepresentable {

    var placeholderText: String?
    @Binding var text: String
    @Binding var shouldMoveCursorToEnd: Bool
    var font: NSFont = .systemFont(ofSize: 14, weight: .regular)

    var onSubmit : () -> Void = {}
    @@ -24,24 +25,34 @@ struct CustomMacTextView: NSViewRepresentable {
    }

    func makeNSView(context: Context) -> NSScrollView {
    let theTextView = PlaceholderNSTextView.scrollableTextView()
    let textView = (theTextView.documentView as! PlaceholderNSTextView)
    let scrollView = PlaceholderNSTextView.scrollableTextView()

    guard let textView = scrollView.documentView as? PlaceholderNSTextView else {
    return scrollView
    }

    textView.delegate = context.coordinator
    textView.string = text
    textView.drawsBackground = false
    textView.font = font
    textView.allowsUndo = true
    textView.placeholderText = placeholderText
    theTextView.hasVerticalScroller = false

    return theTextView
    scrollView.hasVerticalScroller = false

    return scrollView
    }

    func updateNSView(_ view: NSScrollView, context: Context) {
    guard let textView = view.documentView as? NSTextView else {
    return
    }

    // the range is reset when updating the string of the textView
    // so this will set it back to where it was previously
    let currentRange = textView.selectedRange()
    textView.string = text
    textView.setSelectedRange(currentRange)
    }

    }
    @@ -51,11 +62,11 @@ 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
    @@ -70,9 +81,8 @@ extension CustomMacTextView {
    return
    }

    // Update text
    self.parent.text = textView.string
    self.parent.onTextChange(textView.string)
    self.parent.text = textView.string
    }

    func textDidEndEditing(_ notification: Notification) {
    @@ -84,23 +94,17 @@ extension CustomMacTextView {
    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
    // Do something when ENTER key pressed
    self.parent.onSubmit()
    return true
    }

    // return true if the action was handled; otherwise false
    return false
    }

    }
    }

    @@ -111,7 +115,7 @@ fileprivate class PlaceholderNSTextView: NSTextView {
    didSet {
    var attributes = [NSAttributedString.Key: AnyObject]()
    attributes[.font] = font
    attributes[.foregroundColor] = NSColor.gray
    attributes[.foregroundColor] = NSColor.darkGray
    let captionAttributedString = NSAttributedString(string: placeholderText ?? "", attributes: attributes)
    placeholderAttributedString = captionAttributedString
    }
  4. antingle revised this gist Apr 30, 2022. 2 changed files with 119 additions and 257 deletions.
    119 changes: 119 additions & 0 deletions CustomMacTextView.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,119 @@
    //
    // 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
    }
    }
    }
    257 changes: 0 additions & 257 deletions MacEditorTextView.swift
    Original file line number Diff line number Diff line change
    @@ -1,257 +0,0 @@

    /**
    * MacEditorTextView
    * Copyright (c) Thiago Holanda 2020-2021
    * https://twitter.com/tholanda
    *
    * MIT license
    *
    * Edited by Anthony Ingle
    * - Added placeholder text
    * - onSubmit is called when enter/return key is pressed
    * - Background is not drawn (current behavior of TextField in SwiftUI)
    */

    import Combine
    import SwiftUI

    struct MacEditorTextView: NSViewRepresentable {
    @Binding var text: String
    var placeholderText: String?
    var isEditable: Bool = true
    var font: NSFont? = .systemFont(ofSize: 14, weight: .regular)

    var onEditingChanged: () -> Void = {}
    var onSubmit : () -> Void = {}
    var onTextChange : (String) -> Void = { _ in }

    func makeCoordinator() -> Coordinator {
    Coordinator(self)
    }

    func makeNSView(context: Context) -> CustomTextView {
    let textView = CustomTextView(
    text: text,
    isEditable: isEditable,
    font: font,
    placeholderText: placeholderText
    )
    textView.delegate = context.coordinator

    return textView
    }

    func updateNSView(_ view: CustomTextView, context: Context) {
    view.text = text
    view.selectedRanges = context.coordinator.selectedRanges
    }
    }

    // MARK: - Preview

    #if DEBUG

    struct MacEditorTextView_Previews: PreviewProvider {
    static var previews: some View {
    Group {
    MacEditorTextView(
    text: .constant("{ \n planets { \n name \n }\n}"),
    isEditable: true,
    font: .userFixedPitchFont(ofSize: 14)
    )
    .environment(\.colorScheme, .dark)
    .previewDisplayName("Dark Mode")

    MacEditorTextView(
    text: .constant("{ \n planets { \n name \n }\n}"),
    isEditable: false
    )
    .environment(\.colorScheme, .light)
    .previewDisplayName("Light Mode")
    }
    }
    }

    #endif

    // MARK: - Coordinator

    extension MacEditorTextView {

    class Coordinator: NSObject, NSTextViewDelegate {
    var parent: MacEditorTextView
    var selectedRanges: [NSValue] = []

    init(_ parent: MacEditorTextView) {
    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
    }

    self.parent.text = textView.string
    self.selectedRanges = textView.selectedRanges
    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()
    }

    // handles commands
    func textView(_ textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
    if (commandSelector == #selector(NSResponder.insertNewline(_:))) {
    // Do something when ENTER key pressed
    self.parent.onSubmit()
    return true
    }

    // return true if the action was handled; otherwise false
    return false
    }

    }
    }

    // MARK: - CustomTextView

    final class CustomTextView: NSView {
    private var isEditable: Bool
    private var font: NSFont?
    private var placeholderText: String?

    weak var delegate: NSTextViewDelegate?

    var text: String {
    didSet {
    textView.string = text
    }
    }

    var selectedRanges: [NSValue] = [] {
    didSet {
    guard selectedRanges.count > 0 else {
    return
    }

    textView.selectedRanges = selectedRanges
    }
    }

    private lazy var scrollView: NSScrollView = {
    let scrollView = NSScrollView()
    scrollView.drawsBackground = false // set this to true to enable background colors/materials
    scrollView.borderType = .noBorder
    scrollView.hasVerticalScroller = false
    scrollView.hasHorizontalRuler = false
    scrollView.autoresizingMask = [.width, .height]
    scrollView.translatesAutoresizingMaskIntoConstraints = false

    return scrollView
    }()

    private lazy var textView: NSTextView = {
    let contentSize = scrollView.contentSize
    let textStorage = NSTextStorage()


    let layoutManager = NSLayoutManager()
    textStorage.addLayoutManager(layoutManager)


    let textContainer = NSTextContainer(containerSize: scrollView.frame.size)
    textContainer.widthTracksTextView = true
    textContainer.containerSize = NSSize(
    width: contentSize.width,
    height: CGFloat.greatestFiniteMagnitude
    )

    layoutManager.addTextContainer(textContainer)

    let textView = PlaceholderNSTextView(frame: .zero, textContainer: textContainer)
    textView.autoresizingMask = .width
    textView.delegate = self.delegate
    textView.drawsBackground = false
    textView.font = self.font
    textView.isEditable = self.isEditable
    textView.isHorizontallyResizable = false
    textView.isVerticallyResizable = true
    textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
    textView.minSize = NSSize(width: 0, height: contentSize.height)
    textView.textColor = NSColor.labelColor
    textView.allowsUndo = true
    textView.placeholderText = self.placeholderText

    return textView
    }()

    // MARK: - Init
    init(text: String, isEditable: Bool, font: NSFont?, placeholderText: String?) {
    self.font = font
    self.isEditable = isEditable
    self.text = text
    self.placeholderText = placeholderText

    super.init(frame: .zero)
    }

    required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
    }

    // MARK: - Life cycle

    override func viewWillDraw() {
    super.viewWillDraw()

    setupScrollViewConstraints()
    setupTextView()
    }

    func setupScrollViewConstraints() {
    scrollView.translatesAutoresizingMaskIntoConstraints = false

    addSubview(scrollView)

    NSLayoutConstraint.activate([
    scrollView.topAnchor.constraint(equalTo: topAnchor),
    scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
    scrollView.bottomAnchor.constraint(equalTo: bottomAnchor),
    scrollView.leadingAnchor.constraint(equalTo: leadingAnchor)
    ])
    }

    func setupTextView() {
    scrollView.documentView = textView
    }
    }

    // for setting a proper placeholder text on an NSTextView
    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
    }
    }
    }
  5. antingle revised this gist Apr 30, 2022. 2 changed files with 51 additions and 57 deletions.
    65 changes: 51 additions & 14 deletions MacEditorTextView.swift
    Original file line number Diff line number Diff line change
    @@ -1,21 +1,28 @@

    /**
    * MacEditorTextView
    * Copyright (c) Thiago Holanda 2020-2021
    * https://twitter.com/tholanda
    *
    * MIT license
    *
    * Edited by Anthony Ingle
    * - Added placeholder text
    * - onSubmit is called when enter/return key is pressed
    * - Background is not drawn (current behavior of TextField in SwiftUI)
    */

    import Combine
    import SwiftUI

    struct MacEditorTextView: NSViewRepresentable {
    @Binding var text: String
    var placeholderText: String?
    var isEditable: Bool = true
    var font: NSFont? = .systemFont(ofSize: 14, weight: .regular)

    var onEditingChanged: () -> Void = {}
    var onCommit : () -> Void = {}
    var onSubmit : () -> Void = {}
    var onTextChange : (String) -> Void = { _ in }

    func makeCoordinator() -> Coordinator {
    @@ -26,7 +33,8 @@ struct MacEditorTextView: NSViewRepresentable {
    let textView = CustomTextView(
    text: text,
    isEditable: isEditable,
    font: font
    font: font,
    placeholderText: placeholderText
    )
    textView.delegate = context.coordinator

    @@ -94,6 +102,7 @@ extension MacEditorTextView {

    self.parent.text = textView.string
    self.selectedRanges = textView.selectedRanges
    self.parent.onTextChange(textView.string)
    }

    func textDidEndEditing(_ notification: Notification) {
    @@ -102,8 +111,21 @@ extension MacEditorTextView {
    }

    self.parent.text = textView.string
    self.parent.onCommit()
    self.parent.onSubmit()
    }

    // handles commands
    func textView(_ textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
    if (commandSelector == #selector(NSResponder.insertNewline(_:))) {
    // Do something when ENTER key pressed
    self.parent.onSubmit()
    return true
    }

    // return true if the action was handled; otherwise false
    return false
    }

    }
    }

    @@ -112,6 +134,7 @@ extension MacEditorTextView {
    final class CustomTextView: NSView {
    private var isEditable: Bool
    private var font: NSFont?
    private var placeholderText: String?

    weak var delegate: NSTextViewDelegate?

    @@ -133,9 +156,9 @@ final class CustomTextView: NSView {

    private lazy var scrollView: NSScrollView = {
    let scrollView = NSScrollView()
    scrollView.drawsBackground = true
    scrollView.drawsBackground = false // set this to true to enable background colors/materials
    scrollView.borderType = .noBorder
    scrollView.hasVerticalScroller = true
    scrollView.hasVerticalScroller = false
    scrollView.hasHorizontalRuler = false
    scrollView.autoresizingMask = [.width, .height]
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    @@ -161,12 +184,10 @@ final class CustomTextView: NSView {

    layoutManager.addTextContainer(textContainer)


    let textView = NSTextView(frame: .zero, textContainer: textContainer)
    let textView = PlaceholderNSTextView(frame: .zero, textContainer: textContainer)
    textView.autoresizingMask = .width
    textView.backgroundColor = NSColor.textBackgroundColor
    textView.delegate = self.delegate
    textView.drawsBackground = true
    textView.drawsBackground = false
    textView.font = self.font
    textView.isEditable = self.isEditable
    textView.isHorizontallyResizable = false
    @@ -175,15 +196,17 @@ final class CustomTextView: NSView {
    textView.minSize = NSSize(width: 0, height: contentSize.height)
    textView.textColor = NSColor.labelColor
    textView.allowsUndo = true
    textView.placeholderText = self.placeholderText

    return textView
    }()

    // MARK: - Init
    init(text: String, isEditable: Bool, font: NSFont?) {
    self.font = font
    self.isEditable = isEditable
    self.text = text
    init(text: String, isEditable: Bool, font: NSFont?, placeholderText: String?) {
    self.font = font
    self.isEditable = isEditable
    self.text = text
    self.placeholderText = placeholderText

    super.init(frame: .zero)
    }
    @@ -217,4 +240,18 @@ final class CustomTextView: NSView {
    func setupTextView() {
    scrollView.documentView = textView
    }
    }
    }

    // for setting a proper placeholder text on an NSTextView
    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
    }
    }
    }
    43 changes: 0 additions & 43 deletions Using ContentQueryView.swift
    Original file line number Diff line number Diff line change
    @@ -1,43 +0,0 @@
    /**
    * MacEditorTextView
    * Copyright (c) Thiago Holanda 2020-2021
    * https://twitter.com/tholanda
    *
    * MIT license
    */

    import SwiftUI
    import Combine

    struct ContentQueryView: View {

    @State private var queryText = "{ \n planets { \n name \n }\n}"
    @State private var responseJSONText = "{ \"name\": \"Earth\"}"

    var body: some View {
    let queryTextView = MacEditorTextView(
    text: $queryText,
    isEditable: false,
    font: .systemFont(ofSize: 14, weight: .regular)
    )
    .frame(minWidth: 300,
    maxWidth: .infinity,
    minHeight: 300,
    maxHeight: .infinity)

    let responseTextView = MacEditorTextView(
    text: $responseJSONText,
    isEditable: false,
    font: .userFixedPitchFont(ofSize: 14)
    )
    .frame(minWidth: 300,
    maxWidth: .infinity,
    minHeight: 300,
    maxHeight: .infinity)

    return HSplitView {
    queryTextView
    responseTextView
    }
    }
    }
  6. @unnamedd unnamedd revised this gist Feb 10, 2021. 2 changed files with 2 additions and 2 deletions.
    2 changes: 1 addition & 1 deletion MacEditorTextView.swift
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    /**
    * MacEditorTextView
    * Copyright (c) Thiago Holanda 2020
    * Copyright (c) Thiago Holanda 2020-2021
    * https://twitter.com/tholanda
    *
    * MIT license
    2 changes: 1 addition & 1 deletion Using ContentQueryView.swift
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    /**
    * MacEditorTextView
    * Copyright (c) Thiago Holanda 2020
    * Copyright (c) Thiago Holanda 2020-2021
    * https://twitter.com/tholanda
    *
    * MIT license
  7. @unnamedd unnamedd revised this gist Dec 18, 2020. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions MacEditorTextView.swift
    Original file line number Diff line number Diff line change
    @@ -174,6 +174,7 @@ final class CustomTextView: NSView {
    textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
    textView.minSize = NSSize(width: 0, height: contentSize.height)
    textView.textColor = NSColor.labelColor
    textView.allowsUndo = true

    return textView
    }()
  8. @unnamedd unnamedd revised this gist May 26, 2020. 2 changed files with 60 additions and 30 deletions.
    62 changes: 42 additions & 20 deletions MacEditorTextView.swift
    Original file line number Diff line number Diff line change
    @@ -1,27 +1,33 @@
    /**
    * MacEditorTextView
    * Copyright (c) Thiago Holanda 2020
    * https://twitter.com/tholanda
    *
    * MIT license
    */
    * MacEditorTextView
    * Copyright (c) Thiago Holanda 2020
    * https://twitter.com/tholanda
    *
    * MIT license
    */

    import Combine
    import SwiftUI

    struct MacEditorTextView: NSViewRepresentable {
    @Binding var text: String
    var isEditable: Bool = true
    var font: NSFont? = .systemFont(ofSize: 14, weight: .regular)

    var onEditingChanged : () -> Void = {}
    var onCommit : () -> Void = {}
    var onTextChange : (String) -> Void = { _ in }
    var onEditingChanged: () -> Void = {}
    var onCommit : () -> Void = {}
    var onTextChange : (String) -> Void = { _ in }

    func makeCoordinator() -> Coordinator {
    Coordinator(self)
    }

    func makeNSView(context: Context) -> CustomTextView {
    let textView = CustomTextView(text: self.text)
    let textView = CustomTextView(
    text: text,
    isEditable: isEditable,
    font: font
    )
    textView.delegate = context.coordinator

    return textView
    @@ -33,23 +39,37 @@ struct MacEditorTextView: NSViewRepresentable {
    }
    }

    // MARK: - Preview

    #if DEBUG

    struct MacEditorTextView_Previews: PreviewProvider {
    static var previews: some View {
    Group {
    MacEditorTextView(text: .constant("{ \n planets { \n name \n }\n}"))
    .environment(\.colorScheme, .dark)
    .previewDisplayName("Dark Mode")
    MacEditorTextView(
    text: .constant("{ \n planets { \n name \n }\n}"),
    isEditable: true,
    font: .userFixedPitchFont(ofSize: 14)
    )
    .environment(\.colorScheme, .dark)
    .previewDisplayName("Dark Mode")

    MacEditorTextView(text: .constant("{ \n planets { \n name \n }\n}"))
    .environment(\.colorScheme, .light)
    .previewDisplayName("Light Mode")
    MacEditorTextView(
    text: .constant("{ \n planets { \n name \n }\n}"),
    isEditable: false
    )
    .environment(\.colorScheme, .light)
    .previewDisplayName("Light Mode")
    }
    }
    }

    #endif

    // MARK: - Coordinator

    extension MacEditorTextView {

    class Coordinator: NSObject, NSTextViewDelegate {
    var parent: MacEditorTextView
    var selectedRanges: [NSValue] = []
    @@ -87,9 +107,11 @@ extension MacEditorTextView {
    }
    }

    // MARK: - CustomTextView

    final class CustomTextView: NSView {
    private var isEditable: Bool
    private var font: NSFont
    private var font: NSFont?

    weak var delegate: NSTextViewDelegate?

    @@ -152,16 +174,16 @@ final class CustomTextView: NSView {
    textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
    textView.minSize = NSSize(width: 0, height: contentSize.height)
    textView.textColor = NSColor.labelColor

    return textView
    }()

    // MARK: - Init
    init(text: String, isEditable: Bool = true, font: NSFont = NSFont.systemFont(ofSize: 32, weight: .ultraLight)) {
    init(text: String, isEditable: Bool, font: NSFont?) {
    self.font = font
    self.isEditable = isEditable
    self.text = text

    super.init(frame: .zero)
    }

    28 changes: 18 additions & 10 deletions Using ContentQueryView.swift
    Original file line number Diff line number Diff line change
    @@ -15,17 +15,25 @@ struct ContentQueryView: View {
    @State private var responseJSONText = "{ \"name\": \"Earth\"}"

    var body: some View {
    let queryTextView = MacEditorTextView(text: $queryText)
    .frame(minWidth: 300,
    maxWidth: .infinity,
    minHeight: 300,
    maxHeight: .infinity)
    let queryTextView = MacEditorTextView(
    text: $queryText,
    isEditable: false,
    font: .systemFont(ofSize: 14, weight: .regular)
    )
    .frame(minWidth: 300,
    maxWidth: .infinity,
    minHeight: 300,
    maxHeight: .infinity)

    let responseTextView = MacEditorTextView(text: $responseJSONText)
    .frame(minWidth: 300,
    maxWidth: .infinity,
    minHeight: 300,
    maxHeight: .infinity)
    let responseTextView = MacEditorTextView(
    text: $responseJSONText,
    isEditable: false,
    font: .userFixedPitchFont(ofSize: 14)
    )
    .frame(minWidth: 300,
    maxWidth: .infinity,
    minHeight: 300,
    maxHeight: .infinity)

    return HSplitView {
    queryTextView
  9. @unnamedd unnamedd revised this gist Jan 30, 2020. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions MacEditorTextView.swift
    Original file line number Diff line number Diff line change
    @@ -39,11 +39,11 @@ struct MacEditorTextView_Previews: PreviewProvider {
    Group {
    MacEditorTextView(text: .constant("{ \n planets { \n name \n }\n}"))
    .environment(\.colorScheme, .dark)
    .previewDisplayName("Light Mode")
    .previewDisplayName("Dark Mode")

    MacEditorTextView(text: .constant("{ \n planets { \n name \n }\n}"))
    .environment(\.colorScheme, .light)
    .previewDisplayName("Dark Mode")
    .previewDisplayName("Light Mode")
    }
    }
    }
  10. @unnamedd unnamedd revised this gist Jan 21, 2020. 2 changed files with 8 additions and 8 deletions.
    12 changes: 6 additions & 6 deletions TextView.swift → MacEditorTextView.swift
    Original file line number Diff line number Diff line change
    @@ -34,27 +34,27 @@ struct MacEditorTextView: NSViewRepresentable {
    }

    #if DEBUG
    struct EditorTextView_Previews: PreviewProvider {
    struct MacEditorTextView_Previews: PreviewProvider {
    static var previews: some View {
    Group {
    EditorTextView(text: .constant("{ \n planets { \n name \n }\n}"))
    MacEditorTextView(text: .constant("{ \n planets { \n name \n }\n}"))
    .environment(\.colorScheme, .dark)
    .previewDisplayName("Light Mode")

    EditorTextView(text: .constant("{ \n planets { \n name \n }\n}"))
    MacEditorTextView(text: .constant("{ \n planets { \n name \n }\n}"))
    .environment(\.colorScheme, .light)
    .previewDisplayName("Dark Mode")
    }
    }
    }
    #endif

    extension EditorTextView {
    extension MacEditorTextView {
    class Coordinator: NSObject, NSTextViewDelegate {
    var parent: EditorTextView
    var parent: MacEditorTextView
    var selectedRanges: [NSValue] = []

    init(_ parent: EditorTextView) {
    init(_ parent: MacEditorTextView) {
    self.parent = parent
    }

    4 changes: 2 additions & 2 deletions ContentQueryView.swift → Using ContentQueryView.swift
    Original file line number Diff line number Diff line change
    @@ -15,13 +15,13 @@ struct ContentQueryView: View {
    @State private var responseJSONText = "{ \"name\": \"Earth\"}"

    var body: some View {
    let queryTextView = EditorTextView(text: $queryText)
    let queryTextView = MacEditorTextView(text: $queryText)
    .frame(minWidth: 300,
    maxWidth: .infinity,
    minHeight: 300,
    maxHeight: .infinity)

    let responseTextView = EditorTextView(text: $responseJSONText)
    let responseTextView = MacEditorTextView(text: $responseJSONText)
    .frame(minWidth: 300,
    maxWidth: .infinity,
    minHeight: 300,
  11. @unnamedd unnamedd revised this gist Jan 21, 2020. 2 changed files with 43 additions and 5 deletions.
    35 changes: 35 additions & 0 deletions ContentQueryView.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,35 @@
    /**
    * MacEditorTextView
    * Copyright (c) Thiago Holanda 2020
    * https://twitter.com/tholanda
    *
    * MIT license
    */

    import SwiftUI
    import Combine

    struct ContentQueryView: View {

    @State private var queryText = "{ \n planets { \n name \n }\n}"
    @State private var responseJSONText = "{ \"name\": \"Earth\"}"

    var body: some View {
    let queryTextView = EditorTextView(text: $queryText)
    .frame(minWidth: 300,
    maxWidth: .infinity,
    minHeight: 300,
    maxHeight: .infinity)

    let responseTextView = EditorTextView(text: $responseJSONText)
    .frame(minWidth: 300,
    maxWidth: .infinity,
    minHeight: 300,
    maxHeight: .infinity)

    return HSplitView {
    queryTextView
    responseTextView
    }
    }
    }
    13 changes: 8 additions & 5 deletions TextView.swift
    Original file line number Diff line number Diff line change
    @@ -1,12 +1,15 @@
    //
    // Created by Thiago Holanda on 22.07.19.
    // Copyright © 2019 unnamedd codes. All rights reserved.
    //
    /**
    * MacEditorTextView
    * Copyright (c) Thiago Holanda 2020
    * https://twitter.com/tholanda
    *
    * MIT license
    */

    import Combine
    import SwiftUI

    struct EditorTextView: NSViewRepresentable {
    struct MacEditorTextView: NSViewRepresentable {
    @Binding var text: String

    var onEditingChanged : () -> Void = {}
  12. @unnamedd unnamedd revised this gist Jan 21, 2020. 1 changed file with 3 additions and 2 deletions.
    5 changes: 3 additions & 2 deletions TextView.swift
    Original file line number Diff line number Diff line change
    @@ -9,8 +9,9 @@ import SwiftUI
    struct EditorTextView: NSViewRepresentable {
    @Binding var text: String

    var onEditingChanged: () -> Void = {}
    var onCommit: () -> Void = {}
    var onEditingChanged : () -> Void = {}
    var onCommit : () -> Void = {}
    var onTextChange : (String) -> Void = { _ in }

    func makeCoordinator() -> Coordinator {
    Coordinator(self)
  13. @unnamedd unnamedd revised this gist Jul 23, 2019. 1 changed file with 22 additions and 10 deletions.
    32 changes: 22 additions & 10 deletions TextView.swift
    Original file line number Diff line number Diff line change
    @@ -11,7 +11,7 @@ struct EditorTextView: NSViewRepresentable {

    var onEditingChanged: () -> Void = {}
    var onCommit: () -> Void = {}

    func makeCoordinator() -> Coordinator {
    Coordinator(self)
    }
    @@ -25,6 +25,7 @@ struct EditorTextView: NSViewRepresentable {

    func updateNSView(_ view: CustomTextView, context: Context) {
    view.text = text
    view.selectedRanges = context.coordinator.selectedRanges
    }
    }

    @@ -47,6 +48,7 @@ struct EditorTextView_Previews: PreviewProvider {
    extension EditorTextView {
    class Coordinator: NSObject, NSTextViewDelegate {
    var parent: EditorTextView
    var selectedRanges: [NSValue] = []

    init(_ parent: EditorTextView) {
    self.parent = parent
    @@ -67,6 +69,7 @@ extension EditorTextView {
    }

    self.parent.text = textView.string
    self.selectedRanges = textView.selectedRanges
    }

    func textDidEndEditing(_ notification: Notification) {
    @@ -92,6 +95,16 @@ final class CustomTextView: NSView {
    }
    }

    var selectedRanges: [NSValue] = [] {
    didSet {
    guard selectedRanges.count > 0 else {
    return
    }

    textView.selectedRanges = selectedRanges
    }
    }

    private lazy var scrollView: NSScrollView = {
    let scrollView = NSScrollView()
    scrollView.drawsBackground = true
    @@ -124,26 +137,25 @@ final class CustomTextView: NSView {


    let textView = NSTextView(frame: .zero, textContainer: textContainer)
    textView.minSize = NSSize(width: 0, height: contentSize.height)
    textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
    textView.isVerticallyResizable = true
    textView.isHorizontallyResizable = false
    textView.autoresizingMask = .width

    textView.drawsBackground = true
    textView.backgroundColor = NSColor.textBackgroundColor
    textView.textColor = NSColor.labelColor
    textView.delegate = self.delegate
    textView.drawsBackground = true
    textView.font = self.font
    textView.isEditable = self.isEditable
    textView.delegate = self.delegate
    textView.isHorizontallyResizable = false
    textView.isVerticallyResizable = true
    textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
    textView.minSize = NSSize(width: 0, height: contentSize.height)
    textView.textColor = NSColor.labelColor

    return textView
    }()

    // MARK: - Init
    init(text: String, isEditable: Bool = true, font: NSFont = NSFont.systemFont(ofSize: 32, weight: .ultraLight)) {
    self.isEditable = isEditable
    self.font = font
    self.isEditable = isEditable
    self.text = text

    super.init(frame: .zero)
  14. @unnamedd unnamedd created this gist Jul 23, 2019.
    181 changes: 181 additions & 0 deletions TextView.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,181 @@
    //
    // Created by Thiago Holanda on 22.07.19.
    // Copyright © 2019 unnamedd codes. All rights reserved.
    //

    import Combine
    import SwiftUI

    struct EditorTextView: NSViewRepresentable {
    @Binding var text: String

    var onEditingChanged: () -> Void = {}
    var onCommit: () -> Void = {}

    func makeCoordinator() -> Coordinator {
    Coordinator(self)
    }

    func makeNSView(context: Context) -> CustomTextView {
    let textView = CustomTextView(text: self.text)
    textView.delegate = context.coordinator

    return textView
    }

    func updateNSView(_ view: CustomTextView, context: Context) {
    view.text = text
    }
    }

    #if DEBUG
    struct EditorTextView_Previews: PreviewProvider {
    static var previews: some View {
    Group {
    EditorTextView(text: .constant("{ \n planets { \n name \n }\n}"))
    .environment(\.colorScheme, .dark)
    .previewDisplayName("Light Mode")

    EditorTextView(text: .constant("{ \n planets { \n name \n }\n}"))
    .environment(\.colorScheme, .light)
    .previewDisplayName("Dark Mode")
    }
    }
    }
    #endif

    extension EditorTextView {
    class Coordinator: NSObject, NSTextViewDelegate {
    var parent: EditorTextView

    init(_ parent: EditorTextView) {
    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
    }

    self.parent.text = textView.string
    }

    func textDidEndEditing(_ notification: Notification) {
    guard let textView = notification.object as? NSTextView else {
    return
    }

    self.parent.text = textView.string
    self.parent.onCommit()
    }
    }
    }

    final class CustomTextView: NSView {
    private var isEditable: Bool
    private var font: NSFont

    weak var delegate: NSTextViewDelegate?

    var text: String {
    didSet {
    textView.string = text
    }
    }

    private lazy var scrollView: NSScrollView = {
    let scrollView = NSScrollView()
    scrollView.drawsBackground = true
    scrollView.borderType = .noBorder
    scrollView.hasVerticalScroller = true
    scrollView.hasHorizontalRuler = false
    scrollView.autoresizingMask = [.width, .height]
    scrollView.translatesAutoresizingMaskIntoConstraints = false

    return scrollView
    }()

    private lazy var textView: NSTextView = {
    let contentSize = scrollView.contentSize
    let textStorage = NSTextStorage()


    let layoutManager = NSLayoutManager()
    textStorage.addLayoutManager(layoutManager)


    let textContainer = NSTextContainer(containerSize: scrollView.frame.size)
    textContainer.widthTracksTextView = true
    textContainer.containerSize = NSSize(
    width: contentSize.width,
    height: CGFloat.greatestFiniteMagnitude
    )

    layoutManager.addTextContainer(textContainer)


    let textView = NSTextView(frame: .zero, textContainer: textContainer)
    textView.minSize = NSSize(width: 0, height: contentSize.height)
    textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
    textView.isVerticallyResizable = true
    textView.isHorizontallyResizable = false
    textView.autoresizingMask = .width

    textView.drawsBackground = true
    textView.backgroundColor = NSColor.textBackgroundColor
    textView.textColor = NSColor.labelColor
    textView.font = self.font
    textView.isEditable = self.isEditable
    textView.delegate = self.delegate

    return textView
    }()

    // MARK: - Init
    init(text: String, isEditable: Bool = true, font: NSFont = NSFont.systemFont(ofSize: 32, weight: .ultraLight)) {
    self.isEditable = isEditable
    self.font = font
    self.text = text

    super.init(frame: .zero)
    }

    required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
    }

    // MARK: - Life cycle

    override func viewWillDraw() {
    super.viewWillDraw()

    setupScrollViewConstraints()
    setupTextView()
    }

    func setupScrollViewConstraints() {
    scrollView.translatesAutoresizingMaskIntoConstraints = false

    addSubview(scrollView)

    NSLayoutConstraint.activate([
    scrollView.topAnchor.constraint(equalTo: topAnchor),
    scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
    scrollView.bottomAnchor.constraint(equalTo: bottomAnchor),
    scrollView.leadingAnchor.constraint(equalTo: leadingAnchor)
    ])
    }

    func setupTextView() {
    scrollView.documentView = textView
    }
    }