Programmatically focus on a form in a webview (WKWebView) Programmatically focus on a form in a webview (WKWebView) ios ios

Programmatically focus on a form in a webview (WKWebView)


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


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 Foundationimport WebKittypealias OldClosureType =  @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Any?) -> Voidtypealias NewClosureType =  @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Voidextension 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.


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

import Foundationimport WebKittypealias OlderClosureType =  @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Any?) -> Voidtypealias NewerClosureType =  @convention(c) (Any, Selector, UnsafeRawPointer, Bool, Bool, Bool, Any?) -> Voidextension 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)        }    }}