Issue #956

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
    }
}