Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to set caret at specific location #28

Open
mixtly87 opened this issue Aug 9, 2023 · 4 comments
Open

How to set caret at specific location #28

mixtly87 opened this issue Aug 9, 2023 · 4 comments
Labels
question Further information is requested

Comments

@mixtly87
Copy link

mixtly87 commented Aug 9, 2023

I want to support email-compose kind of view so that when you forward or reply on existing email, it shows previous content bellow, setting the cursor at the top of the RichEditorView.

However, I'm not sure how to achieve that. Is it possible to set a caret cursor at specified location in RichEditorView?

@Andrew-Chen-Wang Andrew-Chen-Wang added the question Further information is requested label Aug 9, 2023
@Andrew-Chen-Wang
Copy link
Owner

Yes I believe it is possible. In Resources/editor/rich_editor.js, you can create a new RE.fn function that your Swift code in RichEditorView can call via runJs() (runJs can also be called elsewhere, so long as you have access to the class). The internals of the Javascript function can be modeled after this stackoverflow answer: https://stackoverflow.com/a/30938898

Assuming we already know the position of the text, you can 1) focus in on the window (this might be run via another Swift command that calls runJs("focus") that 2) next selects the range as minimally as possible that the user can type on.

In other words, if you can figure out how to do it via JavaScript like on any normal HTML website, you can do it here as well. Hope that helps

@mixtly87
Copy link
Author

Thanks @Andrew-Chen-Wang for pointing me to the right direction. I was able to focus the web view and set the cursor at the beginning of the document by adding the code in viewDidAppear of relevant view controller:

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
    textView.webView.becomeFirstResponder()
    textView.setCursorAtBegining()
}

and adding to RichEditorView.swift:

public func setCursorAtBegining() {
    runJS("RE.setCursorAtBegining()")
}

and also adding to rich_editor.js:

RE.setCursorAtBegining = function() {
    var el = document.getElementById("editor");
    var range = document.createRange();
    var sel = window.getSelection();
    range.setStart(el.childNodes[0], 0);
    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
    el.focus();
}

however the problem I'm facing is that whole tableView is scrolled to bottom. So the RichEditorView is embedded into a UITableViewCell (it's Mail Compose view controller), and the cell has a proper height to fit entire RichTextView with its entire content.

But this problem with scrolling... do you have a suggestion on how to prevent the UITableView from scrolling to bottom when RichTextView is focused?

@Andrew-Chen-Wang
Copy link
Owner

I'm not sure which thing is scrolling to the bottom, but regardless, there is a JS event listener for focus that allows you to set clientX back to the top. If the table view is scrolling to the bottom, you can add a callback to Swift. I believe there is a callback that you can create using RE.callback that'll let you send back any data type. Then in Swift, you can adjust the scroll.

@mixtly87
Copy link
Author

Btw focusing for me worked on simulator but not on device (iPhone Mini 12, iOS 16.3.1). What fixed it was this thread.

In viewDidAppear:

textView.webView.setKeyboardRequiresUserInteraction(false)
textView.focus(at: .zero)

and WKWebView extension:

extension WKWebView {

    func setKeyboardRequiresUserInteraction( _ value: Bool) {
        
        guard let WKContentViewClass = NSClassFromString("WKContentView") else { return }
        
        typealias OlderClosureType =  @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Any?) -> Void
        typealias NewerClosureType =  @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void
        
        let olderSelector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:")
        let iOS12_2Selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:")
        let iOS13Selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:")
        if let method = class_getInstanceMethod(WKContentViewClass, iOS13Selector) {
            
            let originalImp: IMP = method_getImplementation(method)
            let original: NewerClosureType = unsafeBitCast(originalImp, to: NewerClosureType.self)
            let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3, arg4) in
                original(me, iOS13Selector, arg0, !value, arg2, arg3, arg4)
            }
            let imp: IMP = imp_implementationWithBlock(block)
            method_setImplementation(method, imp)
        } else if let method = class_getInstanceMethod(WKContentViewClass, iOS12_2Selector) {
            
            let originalImp: IMP = method_getImplementation(method)
            let original: NewerClosureType = unsafeBitCast(originalImp, to: NewerClosureType.self)
            let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3, arg4) in
                original(me, iOS12_2Selector, arg0, !value, arg2, arg3, arg4)
            }
            let imp: IMP = imp_implementationWithBlock(block)
            method_setImplementation(method, imp)
        } else  if let method = class_getInstanceMethod(WKContentViewClass, olderSelector) {
            let originalImp: IMP = method_getImplementation(method)
            let original: OlderClosureType = unsafeBitCast(originalImp, to: OlderClosureType.self)
            let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3) in
                original(me, olderSelector, arg0, !value, arg2, arg3)
            }
            let imp: IMP = imp_implementationWithBlock(block)
            method_setImplementation(method, imp)
        }        
    }    
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants