danielsaidi / richtextkit Goto Github PK
View Code? Open in Web Editor NEWRichTextKit is a Swift SDK that helps you use rich text in Swift and SwiftUI.
License: MIT License
RichTextKit is a Swift SDK that helps you use rich text in Swift and SwiftUI.
License: MIT License
The library should have support for creating document-based apps, or at least add DocumentGroup
features to the demo apps.
We can currently use the context to select a range, but not replace the currently selected text.
This should be possible when replacing text.
Replace the pasteText
function with an insertText(at: position)
(non-replacing) and insertText(at: range)
(replacing).
Having multiple RichTextEditor instances with a shared context causes the xcode editor to overload with "Publishing changes from within view updates is not allowed, this will cause undefined behavior." ultimately leading to a crash.
Commenting out func textViewDidBeginEditing and func textViewDidEndEditing in the RichTextCoordinator swift file prevents this error but currently unsure of unintended consequences of this.
To properly add support for macOS and iPadOS menu commands, we need focus values.
Make the rich text editor bind its context to a focus value, then use this context in the menu commands.
On macOS, the background and foreground colors aren't reset when moving the text input cursor from colored text to a plain text.
In the image above, all text was plain. I then made the leftmost text green. When I then moved the cursor to the end of the text and continued typing, the green color was still applied.
Some rich text writers should support easily affecting the entire text range.
The functions will be modified so that the range
parameter is optional and uses the richTextRange
if no range is provided.
This will initially be added to:
RichTextAttributeWriter
RichTextColorWriter
RichTextFontWriter
RichTextStyleWriter
The various buttons should have opt-out keyboard shortcuts applies, for instance:
I am reading attributed text from rtf file.
try! NSAttributedString(data: data!, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.rtf], documentAttributes: nil)
and set these text to EditorScreen
RichTextEditor(text: $text, context: context,format: .rtf) { $0.textContentInset = CGSize(width: 10, height: 20) }
but colors not appearing.
If I set same text to TextView. Colors appearing
Something manipulating reader attributed text but I couldn't find
If this can be use for UIKit where is the example or do I have create the SwitfUI to UIKit bridging file?
It should be possible to add a link to the current selection, and remote the link if there is already one.
We can currently use the context to select a range, but can not delete text at a certain range.
This should be possible when pasting text and images.
After creating a rich text editor, it's not possible to completely replace the current text. Doing so currently doesn't do anything. The reason why I tag this as a feature and not a bug is that it's a known limitation in the current implementation.
This should be possible, though, so a future update should fix this.
Is there a way to remove the font family picker from RichTextKeyboardToolbar
?
How to add RichTextKeyboardToolbar when using in a UIKit project.
Hello,
One final one for today. Is it possible to get the current position of the cursor from the editor? My use-case would be to add a button that can insert text that my app will generate (a link). I am working now on finding a workaround.
Thanks,
David Alvarez
[email protected]
This is related to #5
While investigating, I added a random color to parts of the demo app. It turns out that the entire app redraws every time the text position is moved:
This is because the RichTextCoordinator
syncs the text field's selectedRange
to the RichTextContext
when the text position changes, which happens when you move the cursor or type in the text field.
This is horrible for performance, but I don't know how to fix it. The rich text context is synced in full with the text field, which means that views that take the context as observed object will redraw if the range changes, even if they don't use the range.
Any input to help me fix this problem would be greatly appreciated.
Is it currently possible to make the RichTextEditor
frame dynamic with the following behavior?
Since RichTextKit wraps UIKit and AppKit, the SwiftUI toolbar
view modifier with keyboard placement doesn't work.
In other words, this will not work:
RichTextEditor(text: $text, context: textEditorContext)
.frame(minHeight: 200)
.toolbar {
ToolbarItem(placement: .keyboard) {
Button {
richTextContext.isBold.toggle()
} label: {
Image(systemName: "bold")
}
}
}
In my own app, I developed a custom toolbar that was pinned above the keyboard. The library should offer this custom toolbar as a feature.
It would be nice if the text editor could automatically detect when a new sentence should be added to a new paragraph, for instance after a certain number of sentences.
For instance, if I ask Chat GPT to Can you divide this text into paragraphs?
for this text:
Once upon a time in a small village nestled in the mountains, there lived a young girl named Lily. She possessed a curious spirit and an insatiable love for adventure. One day, while exploring the woods near her home, she stumbled upon a hidden cave. Intrigued, she entered cautiously, discovering a dusty, ancient book with a worn leather cover. As Lily opened the book, a magical portal appeared, transporting her to a mystical realm called Evermore. In Evermore, she encountered fantastical creatures, enchanted forests, and breathtaking landscapes. Guided by an old, wise owl named Orion, Lily embarked on a quest to restore the balance between light and darkness. Along her journey, she encountered a mischievous sprite named Ember, who became her loyal companion. Together, they faced numerous challenges, including treacherous riddles, fearsome monsters, and tests of courage. Lily's determination and Ember's cleverness proved to be a formidable combination. With each obstacle overcome, Lily grew wiser and stronger, unlocking hidden powers within herself. As they delved deeper into Evermore's secrets, they discovered an ancient prophecy foretelling a cataclysmic event that would engulf the realm in eternal darkness. Lily realized that she was the chosen one, destined to fulfill the prophecy and save Evermore. The fate of an entire world now rested on her young shoulders. With unwavering bravery, Lily confronted the malevolent sorcerer responsible for the impending darkness. In a climactic battle, she tapped into her newfound powers, weaving spells of light and hope. The sorcerer, vanquished by her courage, was defeated, and the realm of Evermore was saved. As a token of gratitude, the creatures of Evermore bid Lily a fond farewell, and she returned to her village. Though she had changed, her heart remained filled with wonder and the knowledge that magic existed not only in distant realms but within herself. Lily became an inspiration to her fellow villagers, reminding them to embrace adventure and the boundless possibilities of the unknown. And so, the story of Lily, the young girl who journeyed through the realm of Evermore, lived on as a testament to the extraordinary power of belief, courage, and the timeless allure of a world where anything is possible.
we get:
Once upon a time in a small village nestled in the mountains, there lived a young girl named Lily. She possessed a curious spirit and an insatiable love for adventure. One day, while exploring the woods near her home, she stumbled upon a hidden cave. Intrigued, she entered cautiously, discovering a dusty, ancient book with a worn leather cover.
As Lily opened the book, a magical portal appeared, transporting her to a mystical realm called Evermore. In Evermore, she encountered fantastical creatures, enchanted forests, and breathtaking landscapes. Guided by an old, wise owl named Orion, Lily embarked on a quest to restore the balance between light and darkness.
Along her journey, she encountered a mischievous sprite named Ember, who became her loyal companion. Together, they faced numerous challenges, including treacherous riddles, fearsome monsters, and tests of courage. Lily's determination and Ember's cleverness proved to be a formidable combination. With each obstacle overcome, Lily grew wiser and stronger, unlocking hidden powers within herself.
As they delved deeper into Evermore's secrets, they discovered an ancient prophecy foretelling a cataclysmic event that would engulf the realm in eternal darkness. Lily realized that she was the chosen one, destined to fulfill the prophecy and save Evermore. The fate of an entire world now rested on her young shoulders.
With unwavering bravery, Lily confronted the malevolent sorcerer responsible for the impending darkness. In a climactic battle, she tapped into her newfound powers, weaving spells of light and hope. The sorcerer, vanquished by her courage, was defeated, and the realm of Evermore was saved.
As a token of gratitude, the creatures of Evermore bid Lily a fond farewell, and she returned to her village. Though she had changed, her heart remained filled with wonder and the knowledge that magic existed not only in distant realms but within herself. Lily became an inspiration to her fellow villagers, reminding them to embrace adventure and the boundless possibilities of the unknown.
And so, the story of Lily, the young girl who journeyed through the realm of Evermore, lived on as a testament to the extraordinary power of belief, courage, and the timeless allure of a world where anything is possible.
If you set a text color with the current implementation, the text view is properly updated. You can try it out in the demo app.
However, if you save the rich text to file or write its data to NSUserDefaults
and then restores the rich text, everything that is currently supported by the library (font, font size, background color, style etc.) is restored, except the text color.
The text color is handled exactly like the background color, except that it uses the foregroundColor
key instead of the backgroundColor
, so I just can't understand why this single thing doesn't work.
On macOS Is there a simple way to allow "enter" key to submit on RichTextEditor ? I actually couldn't find this feature on the SwiftUI TextEditor either.
I want standard text input for a chat view. Enter submits text, shift+enter adds new line.
Any insight, much appreciated!
When the RichTextDataReader
uses NSKeyedArchiver
to generate archived data, it doesn't use secure coding. This can lead to errors when the file is unarchived.
For instance, this error was suddenly thrown after editing a file:
UserInfo={NSDebugDescription=value for key 'NS.objects' was of unexpected class 'AFAnalyticsTurnBasedInstrumentationContext' (0x210af22a8) [/System/Library/PrivateFrameworks/AssistantServices.framework].
This seems to be caused by some private frameworks, but we still have to handle it somehow.
We can try to change this RichTextDataReader
function:
func richTextArchivedData() throws -> Data {
try NSKeyedArchiver.archivedData(
withRootObject: richText,
requiringSecureCoding: false
)
}
to use secure coding, but we then have to make sure that the invalid content doesn't cause the archive to fail.
When adding an image to a RichTextEditor
in a document-based iOS app, the image is correctly resized.
However, when later opening the document with the image, the image is not initially resized to fix the screen. As you then start to type, the image is resized as the text changes.
The demo app is currently functional, but not designed at all.
We should adjust it to look like e.g. Notes, Pages, Word or Google Docs.
i tried to add a button to clear the content of the text editor but it is not working. The content is not being cleared
Button("Clear") {
clearContent()
}
private func clearContent() {
text = NSAttributedString()
}
It should be possible to apply a strikethrough style.
In RichTextCoordinator the subscribeToContextChanges is not calling the functions subscribeToShouldPasteImage and subscribeToShouldPasteImages.
Because of that, images are not showing on the text editor.
Hi @danielsaidi,
Just found several warning as below
Warning
Publishing changes from within view updates is not allowed, this will cause undefined behavior.
in function func syncContextWithTextView()
in RichTextCoordinator
Hey there! This is a phenomenal library. Best that I've seen by far for SwiftUI rich text w/out forcing UIKit paradigms.
One major enhancement that would make this library even more powerful -- allowing for arbitrary NSTextAttachments
. The functionality to support inserting NSTextAttachments
has already been implemented internally for the RichTextImageAttachment
capability.
An idea on implementation:
pasteImages
or pasteText
that instead of [ImageRepresentable]
or String
accepts a NSAttributedString
(that could include an NSTextAttachment). func pasteImages(
_ images: [ImageRepresentable],
at index: Int,
moveCursorToPastedContent: Bool = false
) {
#if os(iOS) || os(tvOS) || os(macOS)
guard validateImageInsertion(for: imagePasteConfiguration) else { return }
let content = NSMutableAttributedString(attributedString: richText)
let insertRange = NSRange(location: index, length: 0)
let safeInsertRange = safeRange(for: insertRange)
let insertedItems = images.count * 2 // The number of inserted "items" is the images and a newline for each
let safeMoveIndex = safeInsertRange.location + insertedItems
let attributes = content.richTextAttributes(at: safeInsertRange)
let attributeRange = NSRange(location: index, length: insertedItems)
let safeAttributeRange = safeRange(for: attributeRange)
images.reversed().forEach {
performPasteImage($0, at: index)
}
mutableRichText?.setRichTextAttributes(attributes, at: safeAttributeRange)
if moveCursorToPastedContent {
moveInputCursor(to: safeMoveIndex)
}
#else
assertionFailure("Image pasting is not supported on this platform")
#endif
}
This would just be the first step in allowing for arbitrary subviews to be included in the RichTextEditor, but would enable a future update to support custom sub-classed NSTextAttachments like here:
Some libraries that implement this type of functionality:
Hey I'm trying to ad a toolbar to the RichText Editor in SwiftUI and it doesn't seem to display. Here is what my code looks like:
` @StateObject var context: RichTextContext = RichTextContext()
@State var text: NSAttributedString = NSAttributedString(string: "type here....")
var body: some View {
RichTextEditor(text: $text, context: context)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
RichTextKeyboardToolbar(context: context) {
} trailingButtons: {
}
}
}
}`
I'm trying to get an action to occur whenever the text of the editor changes however when I add an onChange(of:perform)
callback to the editor view it never gets called. The only time it is actually called is if the @State
field is mutated directly from within the view that defines it.
Hello,
Thanks for your great work, I just started a project this Friday, and you package has beet a huge help!
One problem that I have not been able to solve it to see the cursor indent after hitting enter on the keyboard (new line). The indent works fine if there is no next in the text editor field. It this seems like a bug to me, because the user would have to start typing on that new line before he sees the cursor indent.
I apologize if it is error on my part, but I can't find a workaround.
Thanks in advance,
David Alvarez
[email protected]
The macOS app should have support for menu commands, for instance:
Hello,
The following code in RichTextEditor
causes swift warning Publishing changes from within view updates is not allowed, this will cause undefined behavior.
Also because we update UI from within updateUI function, it causes infinite loop and does not allow to get past the point.
public func updateUIView(_ view: UIViewType, context: Context) {
textView.attributedString = text.wrappedValue
richTextContext.highlightedRange = nil // This causes cyclic update to UIView
}
Delete the assignment of highlightedRange
Xcode Version: 14.3 (14E222b)
iOS 14 Simulator (or any other)
A hint for those who need to store the text to core data: set the field to "Transformable".
The vars are declared like:
@State var information = NSAttributedString(string: "")
init() {
_information = State(initialValue: Entity.Attribute as! NSAttributedString)
}
When adding a 2.6MB HEIC file to a document, the resulting file becomes 6MB+, which about corresponds to the file size you get when you convert the HEIC file to JPEG on disc.
The image in question is 4032 pixels wide, which I think could be scaled down. We should at least provide an image setting that lets us set the max file size or dimensions, and scale down any images accordingly.
I'm wondering if it's possible to add image to the editor from photo album or directly from camera and resize it by gesture.
If you don't support it now, what is your suggestion to add this feature? Thanks.
How do I set the standardRichTextFontSize
for my editor?
The package file itself is not editable.
RichTextEditor(text: $textBodyAttrib, context: context) {
$0.setCurrentFontSize(to: 11)
}
is not working properly and is overwritten.
It should be possible to adjust the letter spacing.
When implementing, do this:
Hello, I was wondering if you'd consider adding Catalyst support. Right now because of several AppKit code blocks, it's not possible to compile the project for Catalyst. I believe the issue is that the blocks in #if canImport(AppKit) will try to run in Catalyst, but some macOS classes are not available in Catalyst. An extra check for #if targetEnvironment(macCatalyst) should do the trick.
For my project I was able to use the library by adding the source code locally and removing the incompatible appkit code.
As this cool RTE is OpenSource, I'd like to share a small peace of code for a nice additional feature.
Background: I have to write many letters and very often I have to add the same piece of text, that I call "snippet".
Therefor add an extra Menu (e.g. to the TopToolBar)
Menu {
ForEach(snippets, id: \.self) { snippet in
Button {
context.pasteText(snippet.text!, at: context.selectedRange.location, moveCursorToPastedContent: true)
} label: {
Text(snippet.title!)
}
}
} label: {
Text("insertSnippet")
}
.help("help_selectMarker")
Snippets here: a FetchedResults from Core Date
Have fun.
In my project I'm using AttributedString .
The OribiRichTextKit unit tests have not yet been ported.
We should do this before releasing 0.2.
It should be possible to adjust the line height.
First off, please accept my gratitude for this lovely package! A smart, modern component to act as a rich text editor is exactly what my AppKit-based application needs right now. While I initially attempted to integrate just the view, I ultimately decided to go all-in with the SwiftUI-based RichTextEditor
component, as a hedge to future-proof at least this part of my app. My SwiftUI-fu is a tad weak, particularly around data issues, so I'll apologize if this is a dumb question. But I note that your sample app doesn't provide a Save feature, and that's exactly what I want to be able to do at this point โย I can get an NSAttributedString out of a file on disk and into the editor. I can use the editor component to edit text. But I cannot figure out how to save that text. While I have an ObservableObject
providing the content, it doesn't appear that the attributed string property is being updated as the user works the editor; so when I save, it's the same content as was originally read off disk. Here's some code:
I'm using an EditorContent
object from my app to contain the relevant file data:
class EditorContent: NSObject, ObservableObject {
@Published var item: FilesystemItem
@Published var fileURL: String
let isReadOnly: Bool
The FilesystemItem
is another ObservableObject
that has an text
property, which is the NSAttributedString
content from the file on disk. This gets fed to the RichTextEditor...
editorVC = NSHostingController(rootView: EditorView(editorContent: content))
<add to view>
which is set out pretty much as your sample app has it:
struct EditorView: View {
@ObservedObject var editorContent: EditorContent
@StateObject var context = RichTextContext()
var body: some View {
VStack {
RichTextEditor(text: $editorContent.item.text, context: context) {
// You can customize the native text view here
$0.textContentInset = CGSize(width: 40, height: 40)
}
.focusedValue(\.richTextContext, context)
}
}
}
I note that your sample app's actions execute from the RichEditorContext
, but that doesn't seem to have access to the textview, or the RichTextCoordinator
, which does. I'm kind of scratching my head trying to sort out the hierarchy, and hoping that you can set me in the right direction?
Thanks again!
Make the iOS demo use more standard menus and stuff for working with styles, actions etc.
Hello again,
I noticed that the "buttonStyle" is not being applied (color now showing active color) when using the RichTextStyleToggleGroup. It's possibly due to the fact that the RichTextStyleToggleGroup uses a ControlGroup. My workaround was just to create the RichTextStyleButton myself.
Thanks in advanced,
David Alvarez
[email protected]
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.