Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Programmatically focus on a form in a webview (WKWebView)

Tags:

It's possible with UIWebView with following:

[webView setKeyboardDisplayRequiresUserAction:NO] Call some JS function 

How can you do the same when the webview is WKWebView instead?

related: How can I get a UIWebView to focus on a form input and bring up the keyboard?

like image 931
Maximus S Avatar asked Sep 08 '15 05:09

Maximus S


2 Answers

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 
like image 173
Alex Staravoitau Avatar answered Sep 23 '22 18:09

Alex Staravoitau


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.

like image 40
Pranit Harekar Avatar answered Sep 25 '22 18:09

Pranit Harekar