Using NSTextView in SwiftUI

MarsManaged is built on SwiftUI. At the time of this writing, November 2021, SwiftUI is still quite new and catching up to AppKit/UIKit capabilities.

As many early adopters, I found myself in a situation where a key feature I wanted to build for the app was not feasible using only SwiftUI views. I needed good old AppKit.

Thankfully, there’s a way to bridge AppKit & UIKit to SwiftUI so you can use them in your app. Unfortunately, there’s not many great examples out there.

So, I went back to WWDC, digested available documentation and tried to create the simplest template I could come up with to make it easy to adapt it to my/your app needs. In this case I’m bridging NSTextView. 

You can grab it from GitHub here, pasting it also below for your reference:

//

//  MacEditorv2.swift

//

//  Created by Marc Maset – 2021

//  https://bluelemonbits.com

//  https://twitter.com/MarcMasVi

//

import SwiftUI

 

struct MacEditorv2: NSViewRepresentable {

    func makeCoordinator() -> Coordinator {

        Coordinator(self)

    }

    

    var theTextView = NSTextView.scrollableTextView()

    

    @Binding var text: String

    

    func makeNSView(context: Context) -> NSScrollView {

        let textView = (theTextView.documentView as! NSTextView)

        textView.delegate = context.coordinator

        textView.string = text

        

        

        return theTextView

    }

    

    func updateNSView(_ nsView: NSScrollView, context: Context) {

 

    }

    

    

}

 

extension MacEditorv2{

    

    class Coordinator: NSObject, NSTextViewDelegate{

        

        var parent: MacEditorv2

        var affectedCharRange: NSRange?

        

        init(_ parent: MacEditorv2) {

            self.parent = parent

        }

        

        func textDidChange(_ notification: Notification) {

            guard let textView = notification.object as? NSTextView else {

                return

            }

            

            //Update text

            self.parent.text = textView.string

        }

        

        func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool {

            return true

        }

        

    }

    

    

}

 

Hope it helps others. Given its simplicity it should be easy to adopt it to other AppKit / UIKit classes. 

Until next time, 

Marc

PS. unnamedd posted a great example a while back, unfortunately is not supporting newer SwiftUI features and was a bit complex to tweak. Still a great read if you’re trying to bridge to AppKit/UIKit.