From 64f576b79f3e93ad92ede916c00832babbebaa78 Mon Sep 17 00:00:00 2001 From: Jim Speth Date: Tue, 5 Feb 2019 16:12:28 -0500 Subject: [PATCH 1/3] Added support for configuring an input accessory view. --- README.md | 4 ++ WKWebView.ios.js | 8 ++++ ios/RCTWKWebView/CRAWKWebView.h | 4 +- ios/RCTWKWebView/CRAWKWebView.m | 51 ++++++++++++++++++++++++-- ios/RCTWKWebView/CRAWKWebViewManager.m | 2 + 5 files changed, 65 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2c015b10..705e524d 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,10 @@ You can also use the `require` syntax (sendCookies and userAgent will be ignored Set a custom user agent for WKWebView. Note this only works on iOS 9+. Previous version will simply ignore this props. +- **inputAccessoryViewID** + +An optional identifier which links a React Native [InputAccessoryView](https://facebook.github.io/react-native/docs/inputaccessoryview) to this webview. The InputAccessoryView is rendered above the keyboard when this webview is focused. + - **hideKeyboardAccessoryView** This will hide the keyboard accessory view (`<` `>` and `Done`). Default is false. diff --git a/WKWebView.ios.js b/WKWebView.ios.js index a24024a2..66e73681 100644 --- a/WKWebView.ios.js +++ b/WKWebView.ios.js @@ -237,6 +237,13 @@ class WKWebView extends React.Component { * of new window. Default is false to be backward compatible. */ openNewWindowInWebView: PropTypes.bool, + /** + * An optional identifier which links a custom InputAccessoryView to + * this webview. The InputAccessoryView is rendered above the + * keyboard when this webview is focused. + * @platform ios + */ + inputAccessoryViewID: PropTypes.string, /** * Hide the accessory view when the keyboard is open. Default is false to be * backward compatible. @@ -354,6 +361,7 @@ class WKWebView extends React.Component { allowsBackForwardNavigationGestures={this.props.allowsBackForwardNavigationGestures} automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets} openNewWindowInWebView={this.props.openNewWindowInWebView} + inputAccessoryViewID={this.props.inputAccessoryViewID} hideKeyboardAccessoryView={this.props.hideKeyboardAccessoryView} keyboardDisplayRequiresUserAction={this.props.keyboardDisplayRequiresUserAction} allowsLinkPreview={this.props.allowsLinkPreview} diff --git a/ios/RCTWKWebView/CRAWKWebView.h b/ios/RCTWKWebView/CRAWKWebView.h index fe6e2346..acdd2e9d 100644 --- a/ios/RCTWKWebView/CRAWKWebView.h +++ b/ios/RCTWKWebView/CRAWKWebView.h @@ -3,6 +3,7 @@ #import @class CRAWKWebView; +@class RCTBridge; /** * Special scheme used to pass messages to the injectedJavaScript @@ -24,6 +25,7 @@ shouldStartLoadForRequest:(NSMutableDictionary *)request - (instancetype)initWithProcessPool:(WKProcessPool *)processPool; +@property (nonatomic, weak) RCTBridge *bridge; @property (nonatomic, weak) id delegate; @property (nonatomic, copy) NSDictionary *source; @@ -36,10 +38,10 @@ shouldStartLoadForRequest:(NSMutableDictionary *)request @property (nonatomic, assign) BOOL injectedJavaScriptForMainFrameOnly; @property (nonatomic, copy) NSString *injectJavaScript; @property (nonatomic, copy) NSString *injectedJavaScript; +@property (nonatomic, copy) NSString *inputAccessoryViewID; @property (nonatomic, assign) BOOL hideKeyboardAccessoryView; @property (nonatomic, assign) BOOL keyboardDisplayRequiresUserAction; - - (void)goForward; - (void)goBack; - (BOOL)canGoBack; diff --git a/ios/RCTWKWebView/CRAWKWebView.m b/ios/RCTWKWebView/CRAWKWebView.m index fdc27ce3..d94e7bdf 100644 --- a/ios/RCTWKWebView/CRAWKWebView.m +++ b/ios/RCTWKWebView/CRAWKWebView.m @@ -5,22 +5,28 @@ #import #import +#import #import #import #import +#import #import #import #import #import -// runtime trick to remove WKWebView keyboard default toolbar +// runtime trick to remove or replace WKWebView keyboard default toolbar // see: http://stackoverflow.com/questions/19033292/ios-7-uiwebview-keyboard-issue/19042279#19042279 @interface _SwizzleHelperWK : NSObject @end @implementation _SwizzleHelperWK -(id)inputAccessoryView { - return nil; + UIView *webView = (UIView *)self; + while (webView && ![webView isKindOfClass:[CRAWKWebView class]]) { + webView = webView.superview; + } + return webView.inputAccessoryView; } @end @@ -37,6 +43,7 @@ @interface CRAWKWebView () *)changedProps +{ + if ([changedProps containsObject:@"inputAccessoryViewID"] && self.inputAccessoryViewID) { + [self setCustomInputAccessoryViewWithNativeID:self.inputAccessoryViewID]; + } else if (!self.inputAccessoryViewID) { + [self setDefaultInputAccessoryView]; + } +} + - (void)setInjectJavaScript:(NSString *)injectJavaScript { _injectJavaScript = injectJavaScript; self.atStartScript = [[WKUserScript alloc] initWithSource:injectJavaScript @@ -174,12 +190,41 @@ -(void)setAllowsLinkPreview:(BOOL)allowsLinkPreview } } +- (void)setCustomInputAccessoryViewWithNativeID:(NSString *)nativeID +{ +#if !TARGET_OS_TV + __weak CRAWKWebView *weakSelf = self; + [self.bridge.uiManager rootViewForReactTag:self.reactTag withCompletion:^(UIView *rootView) { + CRAWKWebView *strongSelf = weakSelf; + if (rootView) { + UIView *accessoryView = [strongSelf.bridge.uiManager viewForNativeID:nativeID + withRootTag:rootView.reactTag]; + if (accessoryView && [accessoryView respondsToSelector:@selector(inputAccessoryView)]) { + [strongSelf swizzleWebView]; + strongSelf.inputAccessoryView = [accessoryView valueForKey:@"inputAccessoryView"]; + [strongSelf reloadInputViews]; + } + } + }]; +#endif /* !TARGET_OS_TV */ +} + +- (void)setDefaultInputAccessoryView +{ + self.inputAccessoryView = nil; +} + -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView { if (!hideKeyboardAccessoryView) { return; } - + [self swizzleWebView]; + self.inputAccessoryView = nil; +} + +- (void)swizzleWebView +{ UIView* subview; for (UIView* view in _webView.scrollView.subviews) { if([[view.class description] hasPrefix:@"WKContent"]) diff --git a/ios/RCTWKWebView/CRAWKWebViewManager.m b/ios/RCTWKWebView/CRAWKWebViewManager.m index fcd88f3b..76ea21b0 100644 --- a/ios/RCTWKWebView/CRAWKWebViewManager.m +++ b/ios/RCTWKWebView/CRAWKWebViewManager.m @@ -44,6 +44,7 @@ @implementation CRAWKWebViewManager - (UIView *)view { CRAWKWebView *webView = [[CRAWKWebView alloc] initWithProcessPool:[WKProcessPool sharedProcessPool]]; + webView.bridge = self.bridge; webView.delegate = self; return webView; } @@ -69,6 +70,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(onProgress, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(inputAccessoryViewID, NSString) RCT_EXPORT_VIEW_PROPERTY(hideKeyboardAccessoryView, BOOL) RCT_EXPORT_VIEW_PROPERTY(keyboardDisplayRequiresUserAction, BOOL) RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL) From c5179babd57597464f76747a146b232843f9a8d5 Mon Sep 17 00:00:00 2001 From: Jim Speth Date: Tue, 12 Mar 2019 08:59:03 -0400 Subject: [PATCH 2/3] Backwards compatibility with React Native 0.55.4 for input accessory view. --- ios/RCTWKWebView/CRAWKWebView.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ios/RCTWKWebView/CRAWKWebView.m b/ios/RCTWKWebView/CRAWKWebView.m index d94e7bdf..a0c234d8 100644 --- a/ios/RCTWKWebView/CRAWKWebView.m +++ b/ios/RCTWKWebView/CRAWKWebView.m @@ -199,7 +199,11 @@ - (void)setCustomInputAccessoryViewWithNativeID:(NSString *)nativeID if (rootView) { UIView *accessoryView = [strongSelf.bridge.uiManager viewForNativeID:nativeID withRootTag:rootView.reactTag]; - if (accessoryView && [accessoryView respondsToSelector:@selector(inputAccessoryView)]) { + // For backwards compatibility with React Native 0.55.4, use the content view. + if ([accessoryView respondsToSelector:@selector(content)]) { + accessoryView = [accessoryView valueForKey:@"content"]; + } + if ([accessoryView respondsToSelector:@selector(inputAccessoryView)]) { [strongSelf swizzleWebView]; strongSelf.inputAccessoryView = [accessoryView valueForKey:@"inputAccessoryView"]; [strongSelf reloadInputViews]; From 2a6857ba50fe3ab37d922da1b4a378813fa03d7d Mon Sep 17 00:00:00 2001 From: Jim Speth Date: Thu, 14 Mar 2019 11:26:50 -0400 Subject: [PATCH 3/3] Allow hideKeyboardAccessoryView prop to show/hide custom input accessory view. --- ios/RCTWKWebView/CRAWKWebView.m | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/ios/RCTWKWebView/CRAWKWebView.m b/ios/RCTWKWebView/CRAWKWebView.m index a0c234d8..33e99fd9 100644 --- a/ios/RCTWKWebView/CRAWKWebView.m +++ b/ios/RCTWKWebView/CRAWKWebView.m @@ -103,7 +103,7 @@ - (void)didSetProps:(NSArray *)changedProps if ([changedProps containsObject:@"inputAccessoryViewID"] && self.inputAccessoryViewID) { [self setCustomInputAccessoryViewWithNativeID:self.inputAccessoryViewID]; } else if (!self.inputAccessoryViewID) { - [self setDefaultInputAccessoryView]; + self.inputAccessoryView = nil; } } @@ -206,25 +206,16 @@ - (void)setCustomInputAccessoryViewWithNativeID:(NSString *)nativeID if ([accessoryView respondsToSelector:@selector(inputAccessoryView)]) { [strongSelf swizzleWebView]; strongSelf.inputAccessoryView = [accessoryView valueForKey:@"inputAccessoryView"]; - [strongSelf reloadInputViews]; } } }]; #endif /* !TARGET_OS_TV */ } -- (void)setDefaultInputAccessoryView -{ - self.inputAccessoryView = nil; -} - -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView { - if (!hideKeyboardAccessoryView) { - return; - } [self swizzleWebView]; - self.inputAccessoryView = nil; + self.inputAccessoryView.hidden = hideKeyboardAccessoryView; } - (void)swizzleWebView