Skip to content

Commit

Permalink
Fix iOS imports on RN0.40+ and replace UIWebview with WkWebview
Browse files Browse the repository at this point in the history
No more deprecated webview on iOS and let people use webviewbridge API with RN 0.40+

Commit messages:

Fix iOS imports on RN 0.40+

Replacing UIWebview with WkWebview

Fix WKWebview Bridge

Remove debug informations
  • Loading branch information
guilhermebruzzi committed Mar 9, 2017
1 parent cab93e4 commit 1f2f3e6
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 74 deletions.
2 changes: 1 addition & 1 deletion ios/RCTWebViewBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import "RCTView.h"
#import <React/RCTView.h>

@class RCTWebViewBridge;

Expand Down
189 changes: 121 additions & 68 deletions ios/RCTWebViewBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@

#import <UIKit/UIKit.h>

#import "RCTAutoInsetsProtocol.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTUtils.h"
#import "RCTView.h"
#import "UIView+React.h"

#import <React/RCTAutoInsetsProtocol.h>
#import <React/RCTConvert.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTLog.h>
#import <React/RCTUtils.h>

#import <React/UIView+React.h>
#import <objc/runtime.h>
#import <WebKit/WebKit.h>

//This is a very elegent way of defining multiline string in objective-c.
//source: http://stackoverflow.com/a/23387659/828487
Expand All @@ -41,7 +43,7 @@ -(id)inputAccessoryView
}
@end

@interface RCTWebViewBridge () <UIWebViewDelegate, RCTAutoInsetsProtocol>
@interface RCTWebViewBridge () <WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, RCTAutoInsetsProtocol>

@property (nonatomic, copy) RCTDirectEventBlock onLoadingStart;
@property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish;
Expand All @@ -53,8 +55,9 @@ @interface RCTWebViewBridge () <UIWebViewDelegate, RCTAutoInsetsProtocol>

@implementation RCTWebViewBridge
{
UIWebView *_webView;
WKWebView *_webView;
NSString *_injectedJavaScript;
bool _shouldTrackLoadingStart;
}

- (instancetype)initWithFrame:(CGRect)frame
Expand All @@ -63,8 +66,8 @@ - (instancetype)initWithFrame:(CGRect)frame
super.backgroundColor = [UIColor clearColor];
_automaticallyAdjustContentInsets = YES;
_contentInset = UIEdgeInsetsZero;
_webView = [[UIWebView alloc] initWithFrame:self.bounds];
_webView.delegate = self;
_shouldTrackLoadingStart = NO;
[self setupWebview];
[self addSubview:_webView];
}
return self;
Expand Down Expand Up @@ -100,12 +103,16 @@ - (void)sendToBridge:(NSString *)message
);

NSString *command = [NSString stringWithFormat: format, message];
[_webView stringByEvaluatingJavaScriptFromString:command];
[_webView evaluateJavaScript:command completionHandler:^(id result, NSError * _Nullable error) {
if (error) {
NSLog(@"WKWebview sendToBridge evaluateJavaScript Error: %@", error);
}
}];
}

- (NSURL *)URL
{
return _webView.request.URL;
return _webView.URL;
}

- (void)setSource:(NSDictionary *)source
Expand All @@ -126,7 +133,7 @@ - (void)setSource:(NSDictionary *)source
// passing the redirect urls back here, so we ignore them if trying to load
// the same url. We'll expose a call to 'reload' to allow a user to load
// the existing page.
if ([request.URL isEqual:_webView.request.URL]) {
if ([request.URL isEqual:_webView.URL]) {
return;
}
if (!request.URL) {
Expand Down Expand Up @@ -167,9 +174,9 @@ - (UIColor *)backgroundColor
- (NSMutableDictionary<NSString *, id> *)baseEvent
{
NSMutableDictionary<NSString *, id> *event = [[NSMutableDictionary alloc] initWithDictionary:@{
@"url": _webView.request.URL.absoluteString ?: @"",
@"url": _webView.URL.absoluteString ?: @"",
@"loading" : @(_webView.loading),
@"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"],
@"title": _webView.title,
@"canGoBack": @(_webView.canGoBack),
@"canGoForward" : @(_webView.canGoForward),
}];
Expand Down Expand Up @@ -215,58 +222,80 @@ -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView
object_setClass(subview, newClass);
}

#pragma mark - UIWebViewDelegate methods
#pragma mark - WebKit WebView Setup and JS Handler

- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType
{
BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme];
-(void)setupWebview {
WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc] init];
WKUserContentController *controller = [[WKUserContentController alloc]init];
[controller addScriptMessageHandler:self name:@"observe"];

[theConfiguration setUserContentController:controller];
theConfiguration.allowsInlineMediaPlayback = NO;

if (!isJSNavigation && [request.URL.scheme isEqualToString:RCTWebViewBridgeSchema]) {
NSString* message = [webView stringByEvaluatingJavaScriptFromString:@"WebViewBridge.__fetch__()"];
_webView = [[WKWebView alloc] initWithFrame:self.bounds configuration:theConfiguration];
_webView.UIDelegate = self;
_webView.navigationDelegate = self;

[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
}

-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
if ([message.body rangeOfString:RCTWebViewBridgeSchema].location == NSNotFound) {
NSMutableDictionary<NSString *, id> *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{
@"messages": [self stringArrayJsonToArray: message]
@"messages": [self stringArrayJsonToArray: message.body]
}];

_onBridgeMessage(onBridgeMessageEvent);

isJSNavigation = YES;
return;
}

// skip this for the JS Navigation handler
if (!isJSNavigation && _onShouldStartLoadWithRequest) {
[_webView evaluateJavaScript:@"WebViewBridge.__fetch__()" completionHandler:^(id result, NSError * _Nullable error) {
if (!error) {
NSMutableDictionary<NSString *, id> *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{
@"messages": [self stringArrayJsonToArray: result]
}];

_onBridgeMessage(onBridgeMessageEvent);
}
}];
}

#pragma mark - WebKit WebView Delegate methods

- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
{
_shouldTrackLoadingStart = YES;
}

-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
if (_onLoadingStart && _shouldTrackLoadingStart) {
_shouldTrackLoadingStart = NO;
NSMutableDictionary<NSString *, id> *event = [self baseEvent];
[event addEntriesFromDictionary: @{
@"url": (request.URL).absoluteString,
@"navigationType": @(navigationType)
@"url": (navigationAction.request.URL).absoluteString,
@"navigationType": @(navigationAction.navigationType)
}];
if (![self.delegate webView:self
shouldStartLoadForRequest:event
withCallback:_onShouldStartLoadWithRequest]) {
return NO;
}
_onLoadingStart(event);
}

if (_onLoadingStart) {
// We have this check to filter out iframe requests and whatnot
BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL];
if (isTopFrame) {
NSMutableDictionary<NSString *, id> *event = [self baseEvent];
[event addEntriesFromDictionary: @{
@"url": (request.URL).absoluteString,
@"navigationType": @(navigationType)
}];
_onLoadingStart(event);
if (_onShouldStartLoadWithRequest) {
NSMutableDictionary<NSString *, id> *event = [self baseEvent];
[event addEntriesFromDictionary: @{
@"url": (navigationAction.request.URL).absoluteString,
@"navigationType": @(navigationAction.navigationType)
}];

if (![self.delegate webView:self shouldStartLoadForRequest:event withCallback:_onShouldStartLoadWithRequest]) {
decisionHandler(WKNavigationActionPolicyCancel);
}else{
decisionHandler(WKNavigationActionPolicyAllow);
}
}

// JS Navigation handler
return !isJSNavigation;
decisionHandler(WKNavigationActionPolicyAllow);
}

- (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)error
{
-(void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error{
if (_onLoadingError) {
if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
// NSURLErrorCancelled is reported when a page has a redirect OR if you load
Expand All @@ -286,27 +315,25 @@ - (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)er
}
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
//injecting WebViewBridge Script
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
NSString *webViewBridgeScriptContent = [self webViewBridgeScript];
[webView stringByEvaluatingJavaScriptFromString:webViewBridgeScriptContent];
//////////////////////////////////////////////////////////////////////////////

if (_injectedJavaScript != nil) {
NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript];
[webView evaluateJavaScript:webViewBridgeScriptContent completionHandler:^(id result, NSError * _Nullable error) {
_onLoadingFinish([self baseEvent]);
}];
}

NSMutableDictionary<NSString *, id> *event = [self baseEvent];
event[@"jsEvaluationValue"] = jsEvaluationValue;
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{

_onLoadingFinish(event);
}
// we only need the final 'finishLoad' call so only fire the event when we're actually done loading.
else if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) {
_onLoadingFinish([self baseEvent]);
if (!navigationAction.targetFrame.isMainFrame) {
[webView loadRequest:navigationAction.request];
}

return nil;
}

#pragma mark - WebviewBridge helpers

- (NSArray*)stringArrayJsonToArray:(NSString *)message
{
return [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding]
Expand All @@ -326,8 +353,6 @@ - (NSString *)webViewBridgeScript {

return NSStringMultiline(
(function (window) {
'use strict';

//Make sure that if WebViewBridge already in scope we don't override it.
if (window.WebViewBridge) {
return;
Expand All @@ -339,14 +364,42 @@ - (NSString *)webViewBridgeScript {
var doc = window.document;
var customEvent = doc.createEvent('Event');

function wkWebViewBridgeAvailable() {
return (
window.webkit &&
window.webkit.messageHandlers &&
window.webkit.messageHandlers.observe &&
window.webkit.messageHandlers.observe.postMessage
);
}

function wkWebViewSend(event) {
if (!wkWebViewBridgeAvailable()) {
return;
}
try {
window.webkit.messageHandlers.observe.postMessage(event);
} catch (e) {
console.error('wkWebViewSend error', e.message);
if (window.WebViewBridge.onError) {
window.WebViewBridge.onError(e);
}
}
}

function callFunc(func, message) {
if ('function' === typeof func) {
func(message);
}
}

function signalNative() {
window.location = RNWBSchema + '://message' + new Date().getTime();
if (wkWebViewBridgeAvailable()) {
var event = window.WebViewBridge.__fetch__();
wkWebViewSend(event);
} else { // iOS UIWebview
window.location = RNWBSchema + '://message' + new Date().getTime();
}
}

//I made the private function ugly signiture so user doesn't called them accidently.
Expand Down Expand Up @@ -396,7 +449,7 @@ function signalNative() {
//dispatch event
customEvent.initEvent('WebViewBridge', true, true);
doc.dispatchEvent(customEvent);
}(window));
})(this);
);
}

Expand Down
2 changes: 1 addition & 1 deletion ios/RCTWebViewBridgeManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import "RCTViewManager.h"
#import <React/RCTViewManager.h>

@interface RCTWebViewBridgeManager : RCTViewManager

Expand Down
9 changes: 5 additions & 4 deletions ios/RCTWebViewBridgeManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
*/

#import "RCTWebViewBridgeManager.h"

#import "RCTBridge.h"
#import "RCTUIManager.h"
#import "RCTWebViewBridge.h"
#import "UIView+React.h"

#import <React/RCTBridge.h>
#import <React/RCTUIManager.h>

#import <React/UIView+React.h>

@interface RCTWebViewBridgeManager () <RCTWebViewBridgeDelegate>

Expand Down

0 comments on commit 1f2f3e6

Please sign in to comment.