From 455c30e00078cb19bdae4c7a649f86d4cbb0b9e0 Mon Sep 17 00:00:00 2001 From: Eric Lewis Date: Mon, 29 Apr 2019 11:46:07 -0400 Subject: [PATCH] feat(WKWebView): Allow focus without user interaction (#540) * [iOS] Allow focus without user interaction * Add documentation for keyboardDisplayRequiresUserAction * set keyboardDisplayRequiresUserAction default to true --- docs/Reference.md | 11 +++++++ ios/RNCWKWebView.h | 1 + ios/RNCWKWebView.m | 61 +++++++++++++++++++++++++++++++++++++++ ios/RNCWKWebViewManager.m | 4 +++ src/WebViewTypes.ts | 12 ++++++++ 5 files changed, 89 insertions(+) diff --git a/docs/Reference.md b/docs/Reference.md index e3ece1b..b5f0ef3 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -42,6 +42,7 @@ This document lays out the current public properties and methods for the React N - [`useWebKit`](Reference.md#usewebkit) - [`url`](Reference.md#url) - [`html`](Reference.md#html) +- [`keyboardDisplayRequiresUserAction`](Reference.md#keyboardDisplayRequiresUserAction) - [`hideKeyboardAccessoryView`](Reference.md#hidekeyboardaccessoryview) - [`allowsBackForwardNavigationGestures`](Reference.md#allowsbackforwardnavigationgestures) - [`incognito`](Reference.md#incognito) @@ -756,6 +757,16 @@ If true, use WKWebView instead of UIWebView. --- +### `keyboardDisplayRequiresUserAction` + +If false, web content can programmatically display the keyboard when using the WKWebView. The default value is `true`. + +| Type | Required | Platform | +| ------- | -------- | -------- | +| boolean | No | iOS | + +--- + ### `hideKeyboardAccessoryView` If true, this will hide the keyboard accessory view (< > and Done) when using the WKWebView. diff --git a/ios/RNCWKWebView.h b/ios/RNCWKWebView.h index 7cea86b..1b61c28 100644 --- a/ios/RNCWKWebView.h +++ b/ios/RNCWKWebView.h @@ -37,6 +37,7 @@ #endif @property (nonatomic, assign) UIEdgeInsets contentInset; @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; +@property (nonatomic, assign) BOOL keyboardDisplayRequiresUserAction; @property (nonatomic, assign) BOOL hideKeyboardAccessoryView; @property (nonatomic, assign) BOOL allowsBackForwardNavigationGestures; @property (nonatomic, assign) BOOL incognito; diff --git a/ios/RNCWKWebView.m b/ios/RNCWKWebView.m index a1fa7d8..c152d5b 100644 --- a/ios/RNCWKWebView.m +++ b/ios/RNCWKWebView.m @@ -41,6 +41,7 @@ static NSURLCredential* clientAuthenticationCredential; { UIColor * _savedBackgroundColor; BOOL _savedHideKeyboardAccessoryView; + BOOL _savedKeyboardDisplayRequiresUserAction; } - (instancetype)initWithFrame:(CGRect)frame @@ -54,6 +55,7 @@ static NSURLCredential* clientAuthenticationCredential; _directionalLockEnabled = YES; _automaticallyAdjustContentInsets = YES; _contentInset = UIEdgeInsetsZero; + _savedKeyboardDisplayRequiresUserAction = YES; } // Workaround for a keyboard dismissal bug present in iOS 12 @@ -214,6 +216,7 @@ static NSURLCredential* clientAuthenticationCredential; [self addSubview:_webView]; [self setHideKeyboardAccessoryView: _savedHideKeyboardAccessoryView]; + [self setKeyboardDisplayRequiresUserAction: _savedKeyboardDisplayRequiresUserAction]; [self visitSource]; } } @@ -364,6 +367,64 @@ static NSURLCredential* clientAuthenticationCredential; } } +-(void)setKeyboardDisplayRequiresUserAction:(BOOL)keyboardDisplayRequiresUserAction +{ + if (_webView == nil) { + _savedKeyboardDisplayRequiresUserAction = keyboardDisplayRequiresUserAction; + return; + } + + if (_savedKeyboardDisplayRequiresUserAction == true) { + return; + } + + UIView* subview; + + for (UIView* view in _webView.scrollView.subviews) { + if([[view.class description] hasPrefix:@"WK"]) + subview = view; + } + + if(subview == nil) return; + + Class class = subview.class; + + NSOperatingSystemVersion iOS_11_3_0 = (NSOperatingSystemVersion){11, 3, 0}; + NSOperatingSystemVersion iOS_12_2_0 = (NSOperatingSystemVersion){12, 2, 0}; + + Method method; + IMP override; + + if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_12_2_0]) { + // iOS 12.2.0 - Future + SEL selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:"); + method = class_getInstanceMethod(class, selector); + IMP original = method_getImplementation(method); + 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); + }); + } + else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_11_3_0]) { + // iOS 11.3.0 - 12.2.0 + SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:"); + method = class_getInstanceMethod(class, selector); + IMP original = method_getImplementation(method); + 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); + }); + } else { + // iOS 9.0 - 11.3.0 + SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:"); + method = class_getInstanceMethod(class, selector); + IMP original = method_getImplementation(method); + 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); +} + -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView { if (_webView == nil) { diff --git a/ios/RNCWKWebViewManager.m b/ios/RNCWKWebViewManager.m index 79c4f00..8de7f93 100644 --- a/ios/RNCWKWebViewManager.m +++ b/ios/RNCWKWebViewManager.m @@ -101,6 +101,10 @@ RCT_CUSTOM_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL, RNCWKWebView) { view.showsVerticalScrollIndicator = json == nil ? true : [RCTConvert BOOL: json]; } +RCT_CUSTOM_VIEW_PROPERTY(keyboardDisplayRequiresUserAction, BOOL, RNCWKWebView) { + view.keyboardDisplayRequiresUserAction = json == nil ? true : [RCTConvert BOOL: json]; +} + RCT_EXPORT_METHOD(injectJavaScript:(nonnull NSNumber *)reactTag script:(NSString *)script) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { diff --git a/src/WebViewTypes.ts b/src/WebViewTypes.ts index 3ae5f9d..5a572a3 100644 --- a/src/WebViewTypes.ts +++ b/src/WebViewTypes.ts @@ -403,6 +403,18 @@ export interface IOSWebViewProps extends WebViewSharedProps { */ directionalLockEnabled?: boolean; + /** + * A Boolean value indicating whether web content can programmatically display the keyboard. + * + * When this property is set to true, the user must explicitly tap the elements in the + * web view to display the keyboard (or other relevant input view) for that element. + * When set to false, a focus event on an element causes the input view to be displayed + * and associated with that element automatically. + * + * The default value is `true`. + * @platform ios + */ + keyboardDisplayRequiresUserAction?: boolean; } export interface AndroidWebViewProps extends WebViewSharedProps {