danielsaidi / RichTextKit

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

Feature request: Get cursors current position in the string #63

Closed DavidAlvarezDev closed 8 months ago

DavidAlvarezDev commented 1 year ago

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 info@davidalvarezdev.com

DavidAlvarezDev commented 1 year ago

UPDATE: I just found this post! Seems to work fine.

I guess what I'm missing now is a way to paste a link. I don't mind making the attribute myself, but there does note seem to be an constructor on the context that takes any attribute? Not sure if that is even possible.

I'll look into using context.setAttributedString(to: "") for my sure case.

Thanks again!

DavidAlvarezDev commented 1 year ago

UPDATE: This workaround seems to work for now, but I wonder how demanding it is if the string is large, because I have to replace the entire string.

Steps: 1) I get the current string 2) create the link I want to insert 3) combine them 4) then use context.setAttributedString( to: ) to update the editor. 5) move the cursor back to where it was

At would be awesome if a method on the context would exist to mimic this behavior..

Button {

            // Get the current position of the cursor for restoration as the last step
            cursorPosition = context.selectedRange.location

            // Store the old string
            let oldNSAS = noteBlock.attributedNote

            // Create the new string to insert
            let attributes : [ NSAttributedString.Key : Any ] = [ .link : "https:\\www.google.com" ]
            let newNSMAS = NSAttributedString(string: "someLink", attributes: attributes)

            // Add strings together
            let combinedNSMAS = NSMutableAttributedString(attributedString: oldNSAS)
            combinedNSMAS.insert( newNSMAS, at: context.selectedRange.location )

            // Set the current context to new combined string
            context.setAttributedString( to: combinedNSMAS )

            // Restore the cursor position, (TODO: offset to end of inserted string)
            context.pasteText(" ", at: cursorPosition + 8, moveCursorToPastedContent: true) // NOTE: The first argument needs a blank space so that the cursor will end up after "at" position, note before it. Replace the 8 with the length of string being inserted so that the cursor will appear after the newly inserted string.

        } label: {
            Text("Add link")
        }

Thank you in advanced for any suggestions,

David Alvarez info@davidalvarezdev.com

danielsaidi commented 1 year ago

Hey, that would be great! I'll look at this once I have time to work on the project again.

DavidAlvarezDev commented 1 year ago

Thanks so much! I have never worked on an Open Source project before, but if you need help, I am available. I'v looked through most of the package, and understand for the most part your setup.

Let me know!

Thanks, David Alvarez info@davidalvarezdev.com

DavidAlvarezDev commented 1 year ago

Hello, small update.

I think context.selectedRange.location works fine for this purpose (even tho the property could possibly be called something like context.cursorPosition for easy discovery). The only issue I am having is that it just seems to be not consistent in giving the index, or the count after the character. For example, when the string is empty, the location is 0, but after inserting 11 characters, location is 12. This causes me some confusion when trying use this value to insert an NSAttribute at a specific index.

As a suggestion, it would be nice if it would concisely show the index for the last character before the cursor. So the logic for the possible 'context.cursorPosition' would just to return 'location' -1 if the string is empty, or 0 if the string is empty.

A related feature request is to be able to simply move the cursor to a given index. My current solution for this is to call context.pasteText(" ", at: , moveCursorToPastedContent: true) to move cursor.

These two features could possibly be merged into one. But if you would like to open a new issue for the latter.

Thanks! Enjoy your Sunday.

DavidAlvarezDev commented 1 year ago

Inserting a link seems to work fine now with this code:

    func insertLinkToBibleVerse() {

        let bibleBookFormatted = "\(currentBibleBookName()) \(bibleBook.chapter.sU):\(bibleBook.verse.sU)" // The name of the bible book to insert
        cursorPosition = context.selectedRange.location // Get the current position of the cursor for restoration as the last step
        let fontSize : [ NSAttributedString.Key : Any ] = [ .font : UIFont.systemFont(ofSize: 16.0 )  ]
        let oldNSAS = noteBlock.attributedNote  // Store the old string
        var linkAttribute : [ NSAttributedString.Key : Any ] = [ .link : NSURL(string: makeLinkToBibleVerse() )! ] //Create the new string to insert // Changed type to Any so that it could be merged to with other dictionary
        linkAttribute.merge( dict:  fontSize )

        let blankSpacesToInsert = NSAttributedString( string: " ", attributes: fontSize ) // Inserts a default context so that link does not expand after typing 
        let NSMSLink = NSMutableAttributedString( string: bibleBookFormatted, attributes: linkAttribute ) // Book string with link to insert into note..
        NSMSLink.append( blankSpacesToInsert )

        let combinedNSMAS = NSMutableAttributedString(attributedString: oldNSAS) // Add strings together
        combinedNSMAS.insert( NSMSLink, at: context.selectedRange.location )

        let spaceAfterScriptureIndex = cursorPosition + NSMSLink.string.count
        context.shouldSetAttributedString = combinedNSMAS // cannot use context.setAttributedString( to: ), will set font to context size..
        context.isEditingText = true

        if combinedNSMAS.length > spaceAfterScriptureIndex {
            context.pasteText("", at: spaceAfterScriptureIndex, moveCursorToPastedContent: true)
        }

    }
danielsaidi commented 1 year ago

Hi @DavidAlvarezDev

Do you still need me to take a look at this?