Programmatically focus on a form in a webview (WKWebView)

前端 未结 6 1289
说谎
说谎 2020-12-09 03:50

It\'s possible with UIWebView with following:

[webView setKeyboardDisplayRequiresUserAction:NO]
Call some JS function

How can you do the sa

相关标签:
6条回答
  • 2020-12-09 04:18

    Update for iOS 13 as the method changed again:

    Objective-C

    #import <objc/runtime.h>
    
    @implementation WebViewInjection
    
    + (void)allowDisplayingKeyboardWithoutUserAction {
        Class class = NSClassFromString(@"WKContentView");
        NSOperatingSystemVersion iOS_11_3_0 = (NSOperatingSystemVersion){11, 3, 0};
        NSOperatingSystemVersion iOS_12_2_0 = (NSOperatingSystemVersion){12, 2, 0};
        NSOperatingSystemVersion iOS_13_0_0 = (NSOperatingSystemVersion){13, 0, 0};
        char * methodSignature = "_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:";
    
        if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_13_0_0]) {
            methodSignature = "_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:";
        } else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_12_2_0]) {
            methodSignature = "_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:";
        }
    
        if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_11_3_0]) {
            SEL selector = sel_getUid(methodSignature);
            Method method = class_getInstanceMethod(class, selector);
            IMP original = method_getImplementation(method);
            IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
                ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
            });
            method_setImplementation(method, override);
        } else {
            SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:");
            Method method = class_getInstanceMethod(class, selector);
            IMP original = method_getImplementation(method);
            IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, id arg3) {
                ((void (*)(id, SEL, void*, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3);
            });
            method_setImplementation(method, override);
        }
    }
    
    @end
    

    Swift:

        func setKeyboardRequiresUserInteraction( _ value: Bool) {
    
            guard
                let WKContentViewClass: AnyClass = NSClassFromString("WKContentView") else {
                    print("Cannot find the WKContentView class")
                    return
            }
    
            let olderSelector: Selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:")
            let newSelector: Selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:")
            let newerSelector: Selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:")
            let ios13Selector: Selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:")
    
            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)
            }
    
            if let method = class_getInstanceMethod(wkc, newSelector) {
                self.swizzleAutofocusMethod(method, newSelector, value)
            }
    
            if let method = class_getInstanceMethod(wkc, newerSelector) {
                self.swizzleAutofocusMethod(method, newerSelector, value)
            }
    
            if let method = class_getInstanceMethod(wkc, ios13Selector) {
                self.swizzleAutofocusMethod(method, ios13Selector, value)
            }
        }
    
        func swizzleAutofocusMethod(_ method: Method, _ selector: Selector, _ value: Bool) {
            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, selector, arg0, !value, arg2, arg3, arg4)
            }
            let imp: IMP = imp_implementationWithBlock(block)
            method_setImplementation(method, imp)
       }
    
    0 讨论(0)
  • 2020-12-09 04:27

    This Swift extension does the job and is compatible with 11.3 as well as earlier point releases.

    import Foundation
    import WebKit
    
    typealias OlderClosureType =  @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Any?) -> Void
    typealias NewerClosureType =  @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 WKContentViewClass: AnyClass = NSClassFromString("WKContentView") else {
                    print("Cannot find the WKContentView class")
                    return
            }
    
            let olderSelector: Selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:")
            let newerSelector: Selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:")
    
            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)
            }
    
            if let method = class_getInstanceMethod(WKContentViewClass, newerSelector) {
    
                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, newerSelector, arg0, !value, arg2, arg3, arg4)
                }
                let imp: IMP = imp_implementationWithBlock(block)
                method_setImplementation(method, imp)
            }
    
        }
    
    }
    
    0 讨论(0)
  • 2020-12-09 04:28

    After digging into the Webkit sources for a couple of weeks, I've managed to get this working on iOS 9 by swizzling _startAssistingNode:userIsInteracting:blurPreviousNode:userObject on WKContentView and overriding the userIsInteracting value:

    Pseudo code:

    swizzle_intercept("WKContentView", "_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:", &hackAssist);
    
    void hackAssist (id self, SEL _cmd, void* arg0, BOOL arg1, BOOL arg2, id arg3) {
        ((void (*)(id,SEL,void*,BOOL,BOOL,id))swizzle_interceptee(hackAssist))(self, _cmd, arg0, TRUE, arg2, arg3);
    }
    

    Cheers!

    0 讨论(0)
  • 2020-12-09 04:29

    I had to change @Mark's answer from an extension to a subclass as Swift 4.2 gave me an "All paths through this function will call itself" warning on the keyboardDisplayRequiresUserAction getter.

    import Foundation
    import WebKit
    
    typealias OldClosureType = @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Any?) -> Void
    typealias NewClosureType = @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Void
    
    class WebView: WKWebView {
    
        private var _keyboardDisplayRequiresUseraction = true
    
        var keyboardDisplayRequiresUserAction: Bool? {
            get {
                return _keyboardDisplayRequiresUseraction
            }
            set {
                _keyboardDisplayRequiresUseraction = newValue ?? true
                setKeyboardRequiresUserInteraction(_keyboardDisplayRequiresUseraction)
            }
        }
    
        private func setKeyboardRequiresUserInteraciton(_ value: Bool) {
            guard let WKContentViewClass: AnyClass = NSClassFromString("WKContentView") else {
                return print("Cannot find WKContentView class")
            }
    
            let oldSelector: Selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:")
            let newSelector: Selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:")
    
            if let method = class_getInstanceMethod(WKContentViewClass, oldSelector) {
                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, oldSelector, arg0, !value, arg2, arg3)
                }
                let imp: IMP = imp_implementationWithBlock(block)
                method_setImplementation(method, imp)
            }
            if let method = class_getInstanceMethod(WKContentViewClass, newSelector) {
                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, newSelector, arg0, !value, arg2, arg3, arg4)
                }
                let imp: IMP = imp_implementationWithBlock(block)
                method_setImplementation(method, imp)
            }
        }
    }
    

    Tested on iOS 11.2 and 12.0

    0 讨论(0)
  • 2020-12-09 04:34

    Update: This solution works for iOS 13.0, 12.2, 11.* and 10.* Also, works on iPadOS 13.1

    I wrote an extension (in Swift 4 for WKWebView class that adds keyboardDisplayRequiresUserAction as a computed property, just like in UIWebView.

    After referring to the Apple’s official open source documents for WebKit, I came up with the following runtime swizzling:

    import Foundation
    import WebKit
    
    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)
            }
        }
    }
    

    Make sure you call property on your WKWebView like this,

    let webView = WKWebView()
    webView.keyboardDisplayRequiresUserAction = false
    

    Also, make sure your HTML TextArea element has AutoFocus set to true otherwise this won't work.

    0 讨论(0)
  • 2020-12-09 04:36

    The accepted answer no longer works in iOS 11.3, since WebKit method signature has changed. Here is a workaround (in Obj-C):

    (UPDATE: Method signature has changed a few more times in iOS 12.2 and iOS 13, code below has been updated to reflect these changes)

    #import <objc/runtime.h>
    
    @implementation WebViewInjection
    
    + (void)allowDisplayingKeyboardWithoutUserAction {
        Class class = NSClassFromString(@"WKContentView");
        NSOperatingSystemVersion iOS_11_3_0 = (NSOperatingSystemVersion){11, 3, 0};
        NSOperatingSystemVersion iOS_12_2_0 = (NSOperatingSystemVersion){12, 2, 0};
        NSOperatingSystemVersion iOS_13_0_0 = (NSOperatingSystemVersion){13, 0, 0};
        if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_13_0_0]) {
            SEL selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:");
            Method method = class_getInstanceMethod(class, selector);
            IMP original = method_getImplementation(method);
            IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
            ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
            });
            method_setImplementation(method, override);
        }
       else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_12_2_0]) {
            SEL selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:");
            Method method = class_getInstanceMethod(class, selector);
            IMP original = method_getImplementation(method);
            IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
            ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
            });
            method_setImplementation(method, override);
        }
        else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_11_3_0]) {
            SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:");
            Method method = class_getInstanceMethod(class, selector);
            IMP original = method_getImplementation(method);
            IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
                ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
            });
            method_setImplementation(method, override);
        } else {
            SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:");
            Method method = class_getInstanceMethod(class, selector);
            IMP original = method_getImplementation(method);
            IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, id arg3) {
                ((void (*)(id, SEL, void*, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3);
            });
            method_setImplementation(method, override);
        }
    }
    
    @end
    
    0 讨论(0)
提交回复
热议问题