danielsaidi / RichTextKit

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

Rich text not rendering when deserialised #159

Open alelordelo opened 8 months ago

alelordelo commented 8 months ago

This renders rich text deserialised from Realm:

 @Published var richTextContent = NSAttributedString(string: "")
Text(AttributedString(cellViewModel.richTextContent))

This render text and I can style it fine, but does NOT render rich text .rtf deserialised from Realm:

 @Published var richTextContent = NSAttributedString(string: "")
 RichTextEditor(text: $viewModel.richTextContent, context: viewModel2.context) { _ in
 }

I tried to manually refresh the view, but that didn't work. Really lost here on what could be wrong...

Any inputs are supper appreciated!

danielsaidi commented 8 months ago

Hi @alelordelo

Can you try this with the main branch? There was a font bug that causes styles to be ignored.

alelordelo commented 8 months ago

hi @danielsaidi, just tried now but didn't work also...

RichTextEditor render rich text fine when editing, but not when read from database.

Any idea why this read the rich text attributes, but RichTextEditor not? Text(AttributedString(cellViewModel.richTextContent))

danielsaidi commented 8 months ago

I found a bug in the theming, and disabled applying the theme if the string's not empty...but perhaps something else needs to be fixed as well.

Just to clarify, how do you serialize? If you only serialize the raw string, the format will be thrown away. Have a look at the demo, and see how it persists to file.

alelordelo commented 8 months ago

hey @danielsaidi

So, this is basically my workflow:

Editing works fine

RichTextEditor(text: $viewModel.richTextContent, context: cellViewModel.context) { _ in }

Screenshot 2024-03-15 at 21 05 59

Serialize

        if let richTextData = try? richTextContent.data(from: NSRange(location: 0, length: richTextContent.length), documentAttributes: [.documentType: NSAttributedString.DocumentType.rtf]) {
  newCell.content = richTextData
                            }

Deserialize

  func deserializeRichText(from data: Data) -> NSAttributedString {
        do {
            let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
                .documentType: NSAttributedString.DocumentType.rtf
            ]
            let attributedString = try NSAttributedString(data: data, options: options, documentAttributes: nil)

            // Print success message with some details about the attributed string
            print("Successfully deserialized RTF data. Length: \(attributedString.length). Preview: \(attributedString.string.prefix(50))")

            return attributedString
        } catch {
            print("Error deserializing RTF data: \(error)")
            return NSAttributedString(string: "Failed to load content")
        }
    }

A- Read deserializeRichText into RichTextEditor, renders without complete rich text (underscript works)

 RichTextEditor(text: $viewModel.richTextContent, context: cellViewModel.context) { _ in }
         .onAppear {
                      // Debugging: Log the preview of richTextContent before loading it into the RichTextEditor
                      let previewText = viewModel.richTextContent.string.prefix(100) // Limiting to the first 100 characters for brevity
                      print("Loading rich text into RichTextEditor. Preview: \(previewText)")
                    print("Assigning rich text to cell \(cellViewModel.id). Preview: \(previewText)")
                      // Debugging: Log the attributes at the beginning of the content
                      if viewModel.richTextContent.length > 0 {
                          let range = NSRange(location: 0, length: min(10, cellViewModel.richTextContent.length)) // Inspect the first 10 characters
                          let attributes = cellViewModel.richTextContent.attributes(at: 0, effectiveRange: nil)
                          print("Attributes at start of rich text: \(attributes)")
                      }
                  }

Print this

Loading rich text into RichTextEditor. Preview: hello
Assigning rich text to cell 2DE583D2-2B09-4F2E-8AA6-7380C3E7A708. Preview: hello
Attributes at start of rich text: [:]

updateNSView called
Updated attributed string: hello
Attributes in range {0, 5}: [:]
makeNSView called
Initial attributed string: Academy typewriter, size 46, colour blue  
updateNSView called
Updated attributed string: Academy typewriter, size 46, colour blue  
Attributes in range {0, 42}: [__C.NSAttributedStringKey(_rawValue: NSStrikethroughColor): sRGB IEC61966-2.1 colorspace 0 0.47843 1 1, __C.NSAttributedStringKey(_rawValue: NSColor): Catalog color: System textColor, __C.NSAttributedStringKey(_rawValue: NSStrikethrough): 1, __C.NSAttributedStringKey(_rawValue: NSFont): "HelveticaNeue 16.00 pt. P [] (0x12e791e20) fobj=0x12e745790, spc=4.45", __C.NSAttributedStringKey(_rawValue: NSParagraphStyle): Alignment Right, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode WordWrapping, Tabs (
Screenshot 2024-03-15 at 20 59 20

B- Read deserializeRichText into Text(AttributedString( )) for debug purposes. Render correctly

Text(AttributedString(viewModel.richTextContent))

Screenshot 2024-03-15 at 21 02 15

Print this

Successfully deserialized RTF data. Length: 42. Preview: Academy typewriter, size 46, colour blue  
Successfully deserialized RTF data. Length: 42. Preview: Academy typewriter, size 46, colour blue  
DesignView rendered with namespace: ID(id: 626)
DesignView rendered with layout.id): 73A3271C-FC19-456F-AE02-935F72DD27B4
Loading rich text into RichTextEditor. Preview: hello
Assigning rich text to cell F7B9B307-177E-45B9-8DB5-E6F2F9B4E3CC. Preview: hello
Attributes at start of rich text: [:]
Loading rich text into RichTextEditor. Preview: Academy typewriter, size 46, colour blue  
Assigning rich text to cell 6621503E-929A-4758-B076-EA3A406661C2. Preview: Academy typewriter, size 46, colour blue  
Attributes at start of rich text: [__C.NSAttributedStringKey(_rawValue: NSStrikethrough): 1, __C.NSAttributedStringKey(_rawValue: NSStrikethroughColor): sRGB IEC61966-2.1 colorspace 0 0.47843 1 1, __C.NSAttributedStringKey(_rawValue: NSFont): "AcademyEngravedLetPlain 48.00 pt. P [] (0x142e57960) fobj=0x142f11f40, spc=15.89", __C.NSAttributedStringKey(_rawValue: NSColor): sRGB IEC61966-2.1 colorspace 0 0.47843 1 1, __C.NSAttributedStringKey(_rawValue: NSParagraphStyle): Alignment Right, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode WordWrapping, Tabs (
danielsaidi commented 8 months ago

@alelordelo Thank you for the detailed bug report! 🙏

In that case, I think it may be the font bug, since underline works.

@DominikBucher12 We should look into this.

alelordelo commented 8 months ago

@danielsaidi and @DominikBucher12 ,

Updates on the debug...

I added some prints to RichTextEditor ViewRepresentable:


    public init(
        text: Binding<NSAttributedString>,
        context: RichTextContext,
        config: RichTextView.Configuration = .standard,
        format: RichTextDataFormat = .archivedData,
        viewConfiguration: @escaping ViewConfiguration = { _ in }
    ) {
        self.text = text
        self.config = config
        self._context = ObservedObject(wrappedValue: context)
        self.format = format
        self.viewConfiguration = viewConfiguration
    }

    public typealias ViewConfiguration = (RichTextViewComponent) -> Void

    @ObservedObject
    private var context: RichTextContext

    private var text: Binding<NSAttributedString>
    private let config: RichTextView.Configuration
    private var format: RichTextDataFormat
    private var viewConfiguration: ViewConfiguration

    #if iOS || os(tvOS) || os(visionOS)
    public let textView = RichTextView()
    #endif

    #if macOS
    public let scrollView = RichTextView.scrollableTextView()

    public var textView: RichTextView {
        scrollView.documentView as? RichTextView ?? RichTextView()
    }
    #endif

    public func makeCoordinator() -> RichTextCoordinator {
        RichTextCoordinator(
            text: text,
            textView: textView,
            richTextContext: context
        )
    }

    #if iOS || os(tvOS) || os(visionOS)
    public func makeUIView(context: Context) -> some UIView {
        textView.setup(with: text.wrappedValue, format: format)
        textView.configuration = config
        viewConfiguration(textView)
        return textView
    }

    public func updateUIView(_ view: UIViewType, context: Context) {}

    #else

    public func makeNSView(context: Context) -> some NSView {
        print("makeNSView called")
        print("Initial attributed string inside makeNSView : \(text.wrappedValue.string.prefix(100))") // Print the first 100 characters for brevity
        textView.setup(with: text.wrappedValue, format: format)
        textView.configuration = config
        viewConfiguration(textView)
        return scrollView
    }

    public func updateNSView(_ view: NSViewType, context: Context) {
        print("updateNSView called")
            let attributedString = text.wrappedValue
            print("Updated attributed string inside updateNSView: \(attributedString.string.prefix(100))") // Print the first 100 characters for brevity

            // Print attributes of the entire attributed string
            attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), options: []) { attributes, range, _ in
                print("Attributes inside updateNSView in range \(range): \(attributes)")
            }
    }
    #endif
}

// MARK: RichTextPresenter

public extension RichTextEditor {

    /// Get the currently selected range.
    var selectedRange: NSRange {
        textView.selectedRange
    }
}

// MARK: RichTextReader

public extension RichTextEditor {

    /// Get the string that is managed by the editor.
    var attributedString: NSAttributedString {
        text.wrappedValue
    }
}

// MARK: RichTextWriter

public extension RichTextEditor {

    /// Get the mutable string that is managed by the editor.
    var mutableAttributedString: NSMutableAttributedString? {
        textView.mutableAttributedString
    }
}

#endif

And found that:

Initially the font is read correctly: Attributes inside updateNSView in range {0, 14}: [__C.NSAttributedStringKey(_rawValue: NSStrikethrough): 1, __C.NSAttributedStringKey(_rawValue: NSFont): "AcademyEngravedLetPlain 72.00 pt.

But when updated, it defaults to AppleSystemUIFont: Attributes inside updateNSView in range {0, 14}: [__C.NSAttributedStringKey(_rawValue: NSStrikethroughColor): sRGB IEC61966-2.1 colorspace 0 0.47843 1 1, __C.NSAttributedStringKey(_rawValue: NSFont): ".AppleSystemUIFont 16.00 pt.

Probably something is causing the view to be refreshed, and it defaults to apple font?

danielsaidi commented 7 months ago

I tested the demo app in the main branch, and opening a saved text document still works. It restores fonts, colors, styles, etc.