danielsaidi / RichTextKit

RichTextKit is a Swift SDK that helps you use rich text in Swift and SwiftUI.
MIT License
885 stars 114 forks source link

Toggle RichTextEditor to editable/non editable? #151

Closed alelordelo closed 7 months ago

alelordelo commented 7 months ago

The code bellow implements editable/non editable in AppKit.

I it possible to do something similar with RichTextKit?

textView.isEditable = isEditable


import SwiftUI
import AppKit

struct RichTextEditorNative: NSViewRepresentable {
    @Binding var text: NSAttributedString
    var isEditable: Bool

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

    func makeNSView(context: Context) -> NSTextView {
        let scrollView = NSScrollView()
        let textView = NSTextView()
        scrollView.documentView = textView
        textView.delegate = context.coordinator
        textView.isEditable = isEditable
        textView.drawsBackground = true
        textView.backgroundColor = .white // Ensuring background is visible
        textView.textStorage?.setAttributedString(text)
        return textView
    }

    func updateNSView(_ nsView: NSTextView, context: Context) {
        nsView.isEditable = isEditable

        // Only update the content if it's different to avoid losing selection and cursor position
        if nsView.textStorage?.string != text.string {
            nsView.textStorage?.setAttributedString(text)
        }
    }

    class Coordinator: NSObject, NSTextViewDelegate {
        var parent: RichTextEditorNative

        init(_ parent: RichTextEditorNative) {
            self.parent = parent
        }

        func textDidChange(_ notification: Notification) {
            guard let textView = notification.object as? NSTextView else { return }
            self.parent.text = textView.attributedString()
        }
    }
}

struct ContentView: View {
    @State private var attributedText = NSAttributedString(string: "Hello, SwiftUI!", attributes: [.font: NSFont.systemFont(ofSize: 18)])
    @State private var isEditingEnabled: Bool = true

    var body: some View {
        ZStack {
            RichTextEditorNative(text: $attributedText, isEditable: isEditingEnabled)

            Button(action: {
                isEditingEnabled.toggle()
            }) {
                Text(isEditingEnabled ? "Disable Editing" : "Enable Editing")
            }

        }
    }
}
DominikBucher12 commented 7 months ago

RichTextView is subclass of UI/NSTextView depending on platform. you can do the same things with RichTextView in a complete same way as you did with NSTextView implementation in your example.

danielsaidi commented 7 months ago

As @DominikBucher12 says, this is already possible when you use RichTextView in UIKit or AppKit, since they just inherit the base UITextView/NSTextView. For RichTextEditor, you can use the configuration block to customize the underlying view.

I however think the RichTextContext should have this as a property, which should sync to the underlying view.

@DominikBucher12 What do you think about renaming RichTextView to explicitly UIRichTextView and NSRichTextView and add a SwiftUI RichTextView that just is an editor with a new isEditing configuration disabled?

DominikBucher12 commented 7 months ago

@danielsaidi I wouldnt personally do it 😅 I have kinda strong opinion that Apple framework prefixes should be kept only for apple framework stuff 😅I think it is expressive enough without the prefixes.

I agree about the context though:)

alelordelo commented 7 months ago

For RichTextEditor, you can use the configuration block to customize the underlying view.

@danielsaidi @DominikBucher12 , mind sharing an example on how I could set this in the configuration block? textView.isEditable = isEditable


struct MyView: View {

    @State
    private var text = NSAttributedString(string: "Type here...")

    @StateObject
    var context = RichTextContext()

    var body: some View {
        RichTextEditor(text: $text, context: context) {
            // how can I set here? `textView.isEditable = isEditable`

        }
    }
}
alelordelo commented 7 months ago

Example, when I hardcode this, it works fine:

//added this new parameters textView.isEditable = false textView.isSelectable = false

open class RichTextCoordinator: NSObject {

    // MARK: - Initialization

    /**
     Create a rich text coordinator.

     - Parameters:
       - text: The rich text to edit.
       - textView: The rich text view to keep in sync.
       - richTextContext: The context to keep in sync.
     */
    public init(
        text: Binding<NSAttributedString>,
        textView: RichTextView,
        richTextContext: RichTextContext
    ) {
        textView.attributedString = text.wrappedValue
        self.text = text
        self.textView = textView

        //added this new parameters
        textView.isEditable = false
        textView.isSelectable = false

        self.context = richTextContext
        super.init()
        self.textView.delegate = self

        subscribeToUserActions()
    }

But when I try to change from ContentView, it doesn't change:

open class RichTextCoordinator: NSObject {

    // MARK: - Initialization

    /**
     Create a rich text coordinator.

     - Parameters:
       - text: The rich text to edit.
       - textView: The rich text view to keep in sync.
       - richTextContext: The context to keep in sync.
     */
    public init(
        text: Binding<NSAttributedString>,
        textView: RichTextView,
        richTextContext: RichTextContext
    ) {
        textView.attributedString = text.wrappedValue
        self.text = text
        self.textView = textView

        //added this new parameters
        textView.isEditable = richTextContext.isEditable
        textView.isSelectable = richTextContext.isSelectable

        self.context = richTextContext
        super.init()
        self.textView.delegate = self

        subscribeToUserActions()
    }
public class RichTextContext: ObservableObject {

    /// Create a new rich text context instance.
    public init() {}

     Until then, use `setAttributedString(to:)` to change it.
     */
    public internal(set) var attributedString = NSAttributedString()

    /// The currently selected range, if any.
    public internal(set) var selectedRange = NSRange()

    // MARK: - Bindable Properies

    /// Whether or not the text is currently being edited.
    @Published
    public var isEditingText = false

    @Published public var isEditable: Bool = true
    @Published public var isSelectable: Bool = true
struct ContentView: View {

    @StateObject var context = RichTextContext()
    @State private var text = NSAttributedString(string: "Type here...")

    var body: some View {
              HStack {
                RichTextEditor(text: $text, context: context) {_ in
                     }
                Toggle("Is Editable", isOn: $context.isEditable)
                Toggle("Is Selectable", isOn: $context.isSelectable)
          }
          }

          }
danielsaidi commented 7 months ago

@alelordelo I have added an isEditable property to the RichTextContext. You can set it to false to disable editing in the RichTextEditor.

You can test it in the main branch.

alelordelo commented 7 months ago

awesome! thanks @danielsaidi!