onmyway133 / blog

🍁 What you don't know is what you haven't learned
https://onmyway133.com/
MIT License
679 stars 33 forks source link

How to make attributed TextView for macOS and iOS with SwiftUI #956

Open onmyway133 opened 11 months ago

onmyway133 commented 11 months ago

macOS

import Foundation
import SwiftUI
import AppKit

struct AttributedTextView: NSViewRepresentable {
    @Binding var attributedText: NSAttributedString
    var isEditable: Bool = true

    final class Coordinator: NSObject {
        let parent: AttributedTextView

        init(
            parent: AttributedTextView
        ) {
            self.parent = parent
            super.init()
        }
    }

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

    func makeNSView(context: Context) -> NSScrollView {
        let view = NSTextView.scrollableTextView()

        if let textView = view.documentView as? NSTextView {
            textView.font = NSFont.preferredFont(forTextStyle: .body)
            textView.drawsBackground = false
            textView.isEditable = isEditable
            textView.delegate = context.coordinator
            textView.textContainerInset = NSSize(width: 6, height: 6)
        }

        return view
    }

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

        DispatchQueue.main.async {
            if textView.attributedString() != attributedText {
                textView.textStorage?.setAttributedString(attributedText)
            }
        }
    }
}

extension AttributedTextView.Coordinator: NSTextViewDelegate {
    func textDidChange(_ notification: Notification) {
        guard
            let textView = notification.object as? NSTextView
        else { return }

        parent.attributedText = textView.attributedString()
    }
}

iOS

import Foundation
import SFSafeSymbols
import SwiftUI
import UIKit

struct AttributedTextView: UIViewRepresentable {
    @Binding var attributedText: NSAttributedString
    var isEditable: Bool = true

    final class Coordinator: NSObject {
        let parent: AttributedTextView

        init(
            parent: AttributedTextView
        ) {
            self.parent = parent
            super.init()
        }
    }

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

    func makeUIView(context: Context) -> UITextView {
        let view = UITextView()
        view.attributedText = attributedText
        view.autocorrectionType = .no
        view.autocapitalizationType = .none
        view.spellCheckingType = .no
        view.delegate = context.coordinator
        view.isEditable = isEditable

        return view
    }

    func updateUIView(_ view: UITextView, context: Context) {
        if view.attributedText != attributedText {
            view.attributedText = attributedText
        }
    }
}

extension AttributedTextView.Coordinator: UITextViewDelegate {
    func textViewDidChange(_ textView: UITextView) {
        parent.attributedText = textView.attributedText
    }
}