cjwirth / RichEditorView

RichEditorView is a simple, modular, drop-in UIView subclass for Rich Text Editing.
BSD 3-Clause "New" or "Revised" License
1.89k stars 445 forks source link

Focus issue ios 13 #229

Open devgeektech opened 4 years ago

devgeektech commented 4 years ago

Hello @cjwirth

Keyboard cursor appears only one time and suddenly disappear when I first tap on editor . But when I tap second time again on editor, cursor is showing. For the first time cursor disappear but keyboard exist. And User cannot type as cursor in not there.

mohammadabushalhoob commented 4 years ago

i have the same bug in ios 13 , and ifix it like this

RichEditorDelegate

func richEditorTookFocus(_ editor: RichEditorView) { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5){ editor.focus() } }

griffin-collins commented 4 years ago

That solution refocuses the editor but ignores where the user tapped and always puts the cursor at the end. Is there a way to get it to refocus on the right position?

griffin-collins commented 4 years ago

I think I found a better solution. Create a subclass of RichEditorView and add another target to the gesture recognizer. Save the point when it gets a tap and use that when the delegate refocuses the editor

subclass:

import UIKit
import RichEditorView

class FBRichEditor: RichEditorView {
    public var lastFocus: CGPoint?
    public var tap: UITapGestureRecognizer!

    public override init(frame: CGRect) {
        super.init(frame: frame)
        configure()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        configure()
    }

    public func configure() {
        tap = super.gestureRecognizers![0] as? UITapGestureRecognizer
        tap.addTarget(self, action: #selector(wasTapped))
        tap.delegate = self
        addGestureRecognizer(tap)
    }

    @objc private func wasTapped() {
        lastFocus = tap.location(in: super.webView)
    }
}

change richEditorTookFocus function to this:

func richEditorTookFocus(_ editor: RichEditorView) {
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {
            if self.richTextEditor.lastFocus != nil {
                editor.focus(at: self.richTextEditor.lastFocus!)
            } else {
                editor.focus()
            }
       }
}
vabe1337 commented 4 years ago

For my work this: @objc private func viewWasTapped() { let point = tapRecognizer.location(in: webView) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5){ self.focus(at: point) }

}
pankajjakate commented 4 years ago

i have the same bug in ios 13 , and ifix it like this

RichEditorDelegate

func richEditorTookFocus(_ editor: RichEditorView) { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5){ editor.focus() } }

Thanks this works for me.

Hirobreak commented 4 years ago

I think I found a better solution. Create a subclass of RichEditorView and add another target to the gesture recognizer. Save the point when it gets a tap and use that when the delegate refocuses the editor

subclass:

import UIKit
import RichEditorView

class FBRichEditor: RichEditorView {
    public var lastFocus: CGPoint?
    public var tap: UITapGestureRecognizer!

    public override init(frame: CGRect) {
        super.init(frame: frame)
        configure()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        configure()
    }

    public func configure() {
        tap = super.gestureRecognizers![0] as? UITapGestureRecognizer
        tap.addTarget(self, action: #selector(wasTapped))
        tap.delegate = self
        addGestureRecognizer(tap)
    }

    @objc private func wasTapped() {
        lastFocus = tap.location(in: super.webView)
    }
}

change richEditorTookFocus function to this:

func richEditorTookFocus(_ editor: RichEditorView) {
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {
            if self.richTextEditor.lastFocus != nil {
                editor.focus(at: self.richTextEditor.lastFocus!)
            } else {
                editor.focus()
            }
       }
}

This seems like a good workaround! I ended up adding

func richEditorLostFocus(_ editor: RichEditorView) {
    self.richTextEditor.lastFocus = nil
}

so if you lose focus and then you programmatically need to give it focus again without a touch, the cursor appears at the end of the line as it should be

Still there has to be a reason why this is happening

I tried the referenced answer

additionalSafeAreaInsets.left = 12.0
additionalSafeAreaInsets.right = 12.0

but that did nothing

IshuRocks commented 1 year ago

Step 1 : Add focus

self.editorView.focus(at: .zero)

Step 2 : Add extension in RichEditorView typealias OldClosureType = @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Any?) -> Void typealias NewClosureType = @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void

extension WKWebView{ var keyboardDisplayRequiresUserAction: Bool? { get { return self.keyboardDisplayRequiresUserAction } set { self.setKeyboardRequiresUserInteraction(newValue ?? true) } }

func setKeyboardRequiresUserInteraction( _ value: Bool) {
    guard let WKContentView: AnyClass = NSClassFromString("WKContentView") else {
        print("keyboardDisplayRequiresUserAction extension: Cannot find the WKContentView class")
        return
    }
    // For iOS 10, *
    let sel_10: Selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:")
    // For iOS 11.3, *
    let sel_11_3: Selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:")
    // For iOS 12.2, *
    let sel_12_2: Selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:")
    // For iOS 13.0, *
    let sel_13_0: Selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:")

    if let method = class_getInstanceMethod(WKContentView, sel_10) {
        let originalImp: IMP = method_getImplementation(method)
        let original: OldClosureType = unsafeBitCast(originalImp, to: OldClosureType.self)
        let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3) in
            original(me, sel_10, arg0, !value, arg2, arg3)
        }
        let imp: IMP = imp_implementationWithBlock(block)
        method_setImplementation(method, imp)
    }

    if let method = class_getInstanceMethod(WKContentView, sel_11_3) {
        let originalImp: IMP = method_getImplementation(method)
        let original: NewClosureType = unsafeBitCast(originalImp, to: NewClosureType.self)
        let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3, arg4) in
            original(me, sel_11_3, arg0, !value, arg2, arg3, arg4)
        }
        let imp: IMP = imp_implementationWithBlock(block)
        method_setImplementation(method, imp)
    }

    if let method = class_getInstanceMethod(WKContentView, sel_12_2) {
        let originalImp: IMP = method_getImplementation(method)
        let original: NewClosureType = unsafeBitCast(originalImp, to: NewClosureType.self)
        let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3, arg4) in
            original(me, sel_12_2, arg0, !value, arg2, arg3, arg4)
        }
        let imp: IMP = imp_implementationWithBlock(block)
        method_setImplementation(method, imp)
    }

    if let method = class_getInstanceMethod(WKContentView, sel_13_0) {
        let originalImp: IMP = method_getImplementation(method)
        let original: NewClosureType = unsafeBitCast(originalImp, to: NewClosureType.self)
        let block : @convention(block) (Any, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void = { (me, arg0, arg1, arg2, arg3, arg4) in
            original(me, sel_13_0, arg0, !value, arg2, arg3, arg4)
        }
        let imp: IMP = imp_implementationWithBlock(block)
        method_setImplementation(method, imp)
    }
}

}

Finally set keyboardDisplayRequiresUserAction in private func setup()

webView.keyboardDisplayRequiresUserAction = false