3

I am writing my first MacOS app using Swift. I need to create a simple editable text box which allows multiple paragraphs. In HTML, this would be a textarea.

I gather that iOS 14 will include TextEditor, but that’s not now, and I don’t know whether that will be in MacOS anyway.

Many solutions I have seen presume iOS and UIKit.

How do I do this with SwiftUI and MacOS?

Update

Someone has suggested that the question is similar to this one: How do I create a multiline TextField in SwiftUI?

I have already looked at that question, but:

  • The linked question is specifically for iOS, but mine is for MacOS
  • The answers, as far as I can tell, are specifically for iOS using UIKit. If someone cares to explain how this answers my MacOS question I would be very interested …
Manngo
  • 14,066
  • 10
  • 88
  • 110
  • Does this answer your question? [How do I create a multiline TextField in SwiftUI?](https://stackoverflow.com/questions/56471973/how-do-i-create-a-multiline-textfield-in-swiftui) – New Dev Sep 06 '20 at 03:27
  • @NewDev Thanks, I’ve already looked at that. As far as I can tell, it requires UIKit, which is not available for MacOS applications. – Manngo Sep 06 '20 at 03:53

1 Answers1

8

Here is some initial demo of component like iOS14 TextEditor.

Demo prepared & tested with Xcode 11.7 / macOS 10.15.6

demo

struct TestTextArea: View {
    @State private var text = "Placeholder: Enter some text"

    var body: some View {
        VStack {
            TextArea(text: $text)
                .border(Color.black)
//            Text(text) // uncomment to see mirror of enterred text
        }.padding()
    }
}

struct TextArea: NSViewRepresentable {
    @Binding var text: String

    func makeNSView(context: Context) -> NSScrollView {
        context.coordinator.createTextViewStack()
    }

    func updateNSView(_ nsView: NSScrollView, context: Context) {
        if let textArea = nsView.documentView as? NSTextView, textArea.string != self.text {
            textArea.string = self.text
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(text: $text)
    }

    class Coordinator: NSObject, NSTextViewDelegate {
        var text: Binding<String>

        init(text: Binding<String>) {
            self.text = text
        }

        func textView(_ textView: NSTextView, shouldChangeTextIn range: NSRange, replacementString text: String?) -> Bool {
            defer {
                self.text.wrappedValue = (textView.string as NSString).replacingCharacters(in: range, with: text!)
            }
            return true
        }

        fileprivate lazy var textStorage = NSTextStorage()
        fileprivate lazy var layoutManager = NSLayoutManager()
        fileprivate lazy var textContainer = NSTextContainer()
        fileprivate lazy var textView: NSTextView = NSTextView(frame: CGRect(), textContainer: textContainer)
        fileprivate lazy var scrollview = NSScrollView()

        func createTextViewStack() -> NSScrollView {
            let contentSize = scrollview.contentSize

            textContainer.containerSize = CGSize(width: contentSize.width, height: CGFloat.greatestFiniteMagnitude)
            textContainer.widthTracksTextView = true

            textView.minSize = CGSize(width: 0, height: 0)
            textView.maxSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
            textView.isVerticallyResizable = true
            textView.frame = CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height)
            textView.autoresizingMask = [.width]
            textView.delegate = self

            scrollview.borderType = .noBorder
            scrollview.hasVerticalScroller = true
            scrollview.documentView = textView

            textStorage.addLayoutManager(layoutManager)
            layoutManager.addTextContainer(textContainer)

            return scrollview
        }
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • That is fantastic. Did you really just make this up? Have I permission to use this in a real project? Where can I learn more about what you have done? – Manngo Sep 06 '20 at 07:34
  • This is a demo. You can use or modify it as you wish. If your project is open source and if you want (someone does this) you can add reference to this post in your corresponding source module, but it is up to you. – Asperi Sep 06 '20 at 07:50
  • Just one more thing: is there a way I can add padding inside the text area? – Manngo Sep 08 '20 at 09:10
  • @Asperi - can you update this to do a full rich text version as well, rather than just a plain string. Thanks – Duncan Groenewald Dec 22 '21 at 06:23
  • @Asperi - better still check out my question as I posted a link to an demo SwiftUI app that illustrates the problem when you reuse the SwiftUI view. – Duncan Groenewald Dec 22 '21 at 06:38
  • IDK why but `defer` block doesn't allow me to change any value provided as binding. But removing `defer` solves this problem – fnc12 Feb 26 '23 at 15:52