Browse Source

feat(WKWebView): Allow focus without user interaction (#540)

* [iOS] Allow focus without user interaction

* Add documentation for keyboardDisplayRequiresUserAction

* set keyboardDisplayRequiresUserAction default to true
Eric Lewis 5 years ago
parent
commit
455c30e000
5 changed files with 89 additions and 0 deletions
  1. 11
    0
      docs/Reference.md
  2. 1
    0
      ios/RNCWKWebView.h
  3. 61
    0
      ios/RNCWKWebView.m
  4. 4
    0
      ios/RNCWKWebViewManager.m
  5. 12
    0
      src/WebViewTypes.ts

+ 11
- 0
docs/Reference.md View File

@@ -42,6 +42,7 @@ This document lays out the current public properties and methods for the React N
42 42
 - [`useWebKit`](Reference.md#usewebkit)
43 43
 - [`url`](Reference.md#url)
44 44
 - [`html`](Reference.md#html)
45
+- [`keyboardDisplayRequiresUserAction`](Reference.md#keyboardDisplayRequiresUserAction)
45 46
 - [`hideKeyboardAccessoryView`](Reference.md#hidekeyboardaccessoryview)
46 47
 - [`allowsBackForwardNavigationGestures`](Reference.md#allowsbackforwardnavigationgestures)
47 48
 - [`incognito`](Reference.md#incognito)
@@ -756,6 +757,16 @@ If true, use WKWebView instead of UIWebView.
756 757
 
757 758
 ---
758 759
 
760
+### `keyboardDisplayRequiresUserAction`
761
+
762
+If false, web content can programmatically display the keyboard when using the WKWebView. The default value is `true`.
763
+
764
+| Type    | Required | Platform |
765
+| ------- | -------- | -------- |
766
+| boolean | No       | iOS      |
767
+
768
+---
769
+
759 770
 ### `hideKeyboardAccessoryView`
760 771
 
761 772
 If true, this will hide the keyboard accessory view (< > and Done) when using the WKWebView.

+ 1
- 0
ios/RNCWKWebView.h View File

@@ -37,6 +37,7 @@
37 37
 #endif
38 38
 @property (nonatomic, assign) UIEdgeInsets contentInset;
39 39
 @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
40
+@property (nonatomic, assign) BOOL keyboardDisplayRequiresUserAction;
40 41
 @property (nonatomic, assign) BOOL hideKeyboardAccessoryView;
41 42
 @property (nonatomic, assign) BOOL allowsBackForwardNavigationGestures;
42 43
 @property (nonatomic, assign) BOOL incognito;

+ 61
- 0
ios/RNCWKWebView.m View File

@@ -41,6 +41,7 @@ static NSURLCredential* clientAuthenticationCredential;
41 41
 {
42 42
   UIColor * _savedBackgroundColor;
43 43
   BOOL _savedHideKeyboardAccessoryView;
44
+  BOOL _savedKeyboardDisplayRequiresUserAction;
44 45
 }
45 46
 
46 47
 - (instancetype)initWithFrame:(CGRect)frame
@@ -54,6 +55,7 @@ static NSURLCredential* clientAuthenticationCredential;
54 55
     _directionalLockEnabled = YES;
55 56
     _automaticallyAdjustContentInsets = YES;
56 57
     _contentInset = UIEdgeInsetsZero;
58
+    _savedKeyboardDisplayRequiresUserAction = YES;
57 59
   }
58 60
 
59 61
   // Workaround for a keyboard dismissal bug present in iOS 12
@@ -214,6 +216,7 @@ static NSURLCredential* clientAuthenticationCredential;
214 216
 
215 217
     [self addSubview:_webView];
216 218
     [self setHideKeyboardAccessoryView: _savedHideKeyboardAccessoryView];
219
+    [self setKeyboardDisplayRequiresUserAction: _savedKeyboardDisplayRequiresUserAction];
217 220
     [self visitSource];
218 221
   }
219 222
 }
@@ -364,6 +367,64 @@ static NSURLCredential* clientAuthenticationCredential;
364 367
     }
365 368
 }
366 369
 
370
+-(void)setKeyboardDisplayRequiresUserAction:(BOOL)keyboardDisplayRequiresUserAction
371
+{
372
+    if (_webView == nil) {
373
+        _savedKeyboardDisplayRequiresUserAction = keyboardDisplayRequiresUserAction;
374
+        return;
375
+    }
376
+  
377
+    if (_savedKeyboardDisplayRequiresUserAction == true) {
378
+        return;
379
+    }
380
+  
381
+    UIView* subview;
382
+  
383
+    for (UIView* view in _webView.scrollView.subviews) {
384
+        if([[view.class description] hasPrefix:@"WK"])
385
+            subview = view;
386
+    }
387
+  
388
+    if(subview == nil) return;
389
+  
390
+    Class class = subview.class;
391
+  
392
+    NSOperatingSystemVersion iOS_11_3_0 = (NSOperatingSystemVersion){11, 3, 0};
393
+    NSOperatingSystemVersion iOS_12_2_0 = (NSOperatingSystemVersion){12, 2, 0};
394
+
395
+    Method method;
396
+    IMP override;
397
+  
398
+    if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_12_2_0]) {
399
+        // iOS 12.2.0 - Future
400
+        SEL selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:");
401
+        method = class_getInstanceMethod(class, selector);
402
+        IMP original = method_getImplementation(method);
403
+        override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
404
+            ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
405
+        });
406
+    }
407
+    else if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_11_3_0]) {
408
+        // iOS 11.3.0 - 12.2.0
409
+        SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:");
410
+        method = class_getInstanceMethod(class, selector);
411
+        IMP original = method_getImplementation(method);
412
+        override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
413
+            ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
414
+        });
415
+    } else {
416
+        // iOS 9.0 - 11.3.0
417
+        SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:");
418
+        method = class_getInstanceMethod(class, selector);
419
+        IMP original = method_getImplementation(method);
420
+        override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, id arg3) {
421
+            ((void (*)(id, SEL, void*, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3);
422
+        });
423
+    }
424
+  
425
+    method_setImplementation(method, override);
426
+}
427
+
367 428
 -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView
368 429
 {
369 430
     if (_webView == nil) {

+ 4
- 0
ios/RNCWKWebViewManager.m View File

@@ -101,6 +101,10 @@ RCT_CUSTOM_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL, RNCWKWebView) {
101 101
   view.showsVerticalScrollIndicator = json == nil ? true : [RCTConvert BOOL: json];
102 102
 }
103 103
 
104
+RCT_CUSTOM_VIEW_PROPERTY(keyboardDisplayRequiresUserAction, BOOL, RNCWKWebView) {
105
+  view.keyboardDisplayRequiresUserAction = json == nil ? true : [RCTConvert BOOL: json];
106
+}
107
+
104 108
 RCT_EXPORT_METHOD(injectJavaScript:(nonnull NSNumber *)reactTag script:(NSString *)script)
105 109
 {
106 110
   [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RNCWKWebView *> *viewRegistry) {

+ 12
- 0
src/WebViewTypes.ts View File

@@ -403,6 +403,18 @@ export interface IOSWebViewProps extends WebViewSharedProps {
403 403
    */
404 404
   directionalLockEnabled?: boolean;
405 405
 
406
+  /**
407
+   * A Boolean value indicating whether web content can programmatically display the keyboard.
408
+   *
409
+   * When this property is set to true, the user must explicitly tap the elements in the
410
+   * web view to display the keyboard (or other relevant input view) for that element.
411
+   * When set to false, a focus event on an element causes the input view to be displayed
412
+   * and associated with that element automatically.
413
+   *
414
+   * The default value is `true`.
415
+   * @platform ios
416
+   */
417
+  keyboardDisplayRequiresUserAction?: boolean;
406 418
 }
407 419
 
408 420
 export interface AndroidWebViewProps extends WebViewSharedProps {