Skip to content

Commit 9cad02b

Browse files
Fix iOS imports on RN0.40+ and replace UIWebview with WkWebview
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
1 parent 0a18985 commit 9cad02b

File tree

4 files changed

+128
-74
lines changed

4 files changed

+128
-74
lines changed

ios/RCTWebViewBridge.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* of patent rights can be found in the PATENTS file in the same directory.
1111
*/
1212

13-
#import "RCTView.h"
13+
#import <React/RCTView.h>
1414

1515
@class RCTWebViewBridge;
1616

ios/RCTWebViewBridge.m

Lines changed: 121 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@
1414

1515
#import <UIKit/UIKit.h>
1616

17-
#import "RCTAutoInsetsProtocol.h"
18-
#import "RCTConvert.h"
19-
#import "RCTEventDispatcher.h"
20-
#import "RCTLog.h"
21-
#import "RCTUtils.h"
22-
#import "RCTView.h"
23-
#import "UIView+React.h"
17+
18+
#import <React/RCTAutoInsetsProtocol.h>
19+
#import <React/RCTConvert.h>
20+
#import <React/RCTEventDispatcher.h>
21+
#import <React/RCTLog.h>
22+
#import <React/RCTUtils.h>
23+
24+
#import <React/UIView+React.h>
2425
#import <objc/runtime.h>
26+
#import <WebKit/WebKit.h>
2527

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

44-
@interface RCTWebViewBridge () <UIWebViewDelegate, RCTAutoInsetsProtocol>
46+
@interface RCTWebViewBridge () <WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, RCTAutoInsetsProtocol>
4547

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

5456
@implementation RCTWebViewBridge
5557
{
56-
UIWebView *_webView;
58+
WKWebView *_webView;
5759
NSString *_injectedJavaScript;
60+
bool _shouldTrackLoadingStart;
5861
}
5962

6063
- (instancetype)initWithFrame:(CGRect)frame
@@ -63,8 +66,8 @@ - (instancetype)initWithFrame:(CGRect)frame
6366
super.backgroundColor = [UIColor clearColor];
6467
_automaticallyAdjustContentInsets = YES;
6568
_contentInset = UIEdgeInsetsZero;
66-
_webView = [[UIWebView alloc] initWithFrame:self.bounds];
67-
_webView.delegate = self;
69+
_shouldTrackLoadingStart = NO;
70+
[self setupWebview];
6871
[self addSubview:_webView];
6972
}
7073
return self;
@@ -100,12 +103,16 @@ - (void)sendToBridge:(NSString *)message
100103
);
101104

102105
NSString *command = [NSString stringWithFormat: format, message];
103-
[_webView stringByEvaluatingJavaScriptFromString:command];
106+
[_webView evaluateJavaScript:command completionHandler:^(id result, NSError * _Nullable error) {
107+
if (error) {
108+
NSLog(@"WKWebview sendToBridge evaluateJavaScript Error: %@", error);
109+
}
110+
}];
104111
}
105112

106113
- (NSURL *)URL
107114
{
108-
return _webView.request.URL;
115+
return _webView.URL;
109116
}
110117

111118
- (void)setSource:(NSDictionary *)source
@@ -126,7 +133,7 @@ - (void)setSource:(NSDictionary *)source
126133
// passing the redirect urls back here, so we ignore them if trying to load
127134
// the same url. We'll expose a call to 'reload' to allow a user to load
128135
// the existing page.
129-
if ([request.URL isEqual:_webView.request.URL]) {
136+
if ([request.URL isEqual:_webView.URL]) {
130137
return;
131138
}
132139
if (!request.URL) {
@@ -167,9 +174,9 @@ - (UIColor *)backgroundColor
167174
- (NSMutableDictionary<NSString *, id> *)baseEvent
168175
{
169176
NSMutableDictionary<NSString *, id> *event = [[NSMutableDictionary alloc] initWithDictionary:@{
170-
@"url": _webView.request.URL.absoluteString ?: @"",
177+
@"url": _webView.URL.absoluteString ?: @"",
171178
@"loading" : @(_webView.loading),
172-
@"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"],
179+
@"title": _webView.title,
173180
@"canGoBack": @(_webView.canGoBack),
174181
@"canGoForward" : @(_webView.canGoForward),
175182
}];
@@ -215,58 +222,80 @@ -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView
215222
object_setClass(subview, newClass);
216223
}
217224

218-
#pragma mark - UIWebViewDelegate methods
225+
#pragma mark - WebKit WebView Setup and JS Handler
219226

220-
- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
221-
navigationType:(UIWebViewNavigationType)navigationType
222-
{
223-
BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme];
227+
-(void)setupWebview {
228+
WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc] init];
229+
WKUserContentController *controller = [[WKUserContentController alloc]init];
230+
[controller addScriptMessageHandler:self name:@"observe"];
231+
232+
[theConfiguration setUserContentController:controller];
233+
theConfiguration.allowsInlineMediaPlayback = NO;
224234

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

239+
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
240+
}
241+
242+
-(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
243+
if ([message.body rangeOfString:RCTWebViewBridgeSchema].location == NSNotFound) {
228244
NSMutableDictionary<NSString *, id> *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{
229-
@"messages": [self stringArrayJsonToArray: message]
245+
@"messages": [self stringArrayJsonToArray: message.body]
230246
}];
231247

232248
_onBridgeMessage(onBridgeMessageEvent);
233249

234-
isJSNavigation = YES;
250+
return;
235251
}
236252

237-
// skip this for the JS Navigation handler
238-
if (!isJSNavigation && _onShouldStartLoadWithRequest) {
253+
[_webView evaluateJavaScript:@"WebViewBridge.__fetch__()" completionHandler:^(id result, NSError * _Nullable error) {
254+
if (!error) {
255+
NSMutableDictionary<NSString *, id> *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{
256+
@"messages": [self stringArrayJsonToArray: result]
257+
}];
258+
259+
_onBridgeMessage(onBridgeMessageEvent);
260+
}
261+
}];
262+
}
263+
264+
#pragma mark - WebKit WebView Delegate methods
265+
266+
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
267+
{
268+
_shouldTrackLoadingStart = YES;
269+
}
270+
271+
-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
272+
if (_onLoadingStart && _shouldTrackLoadingStart) {
273+
_shouldTrackLoadingStart = NO;
239274
NSMutableDictionary<NSString *, id> *event = [self baseEvent];
240275
[event addEntriesFromDictionary: @{
241-
@"url": (request.URL).absoluteString,
242-
@"navigationType": @(navigationType)
276+
@"url": (navigationAction.request.URL).absoluteString,
277+
@"navigationType": @(navigationAction.navigationType)
243278
}];
244-
if (![self.delegate webView:self
245-
shouldStartLoadForRequest:event
246-
withCallback:_onShouldStartLoadWithRequest]) {
247-
return NO;
248-
}
279+
_onLoadingStart(event);
249280
}
250281

251-
if (_onLoadingStart) {
252-
// We have this check to filter out iframe requests and whatnot
253-
BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL];
254-
if (isTopFrame) {
255-
NSMutableDictionary<NSString *, id> *event = [self baseEvent];
256-
[event addEntriesFromDictionary: @{
257-
@"url": (request.URL).absoluteString,
258-
@"navigationType": @(navigationType)
259-
}];
260-
_onLoadingStart(event);
282+
if (_onShouldStartLoadWithRequest) {
283+
NSMutableDictionary<NSString *, id> *event = [self baseEvent];
284+
[event addEntriesFromDictionary: @{
285+
@"url": (navigationAction.request.URL).absoluteString,
286+
@"navigationType": @(navigationAction.navigationType)
287+
}];
288+
289+
if (![self.delegate webView:self shouldStartLoadForRequest:event withCallback:_onShouldStartLoadWithRequest]) {
290+
decisionHandler(WKNavigationActionPolicyCancel);
291+
}else{
292+
decisionHandler(WKNavigationActionPolicyAllow);
261293
}
262294
}
263-
264-
// JS Navigation handler
265-
return !isJSNavigation;
295+
decisionHandler(WKNavigationActionPolicyAllow);
266296
}
267297

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

289-
- (void)webViewDidFinishLoad:(UIWebView *)webView
290-
{
291-
//injecting WebViewBridge Script
318+
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
292319
NSString *webViewBridgeScriptContent = [self webViewBridgeScript];
293-
[webView stringByEvaluatingJavaScriptFromString:webViewBridgeScriptContent];
294-
//////////////////////////////////////////////////////////////////////////////
295-
296-
if (_injectedJavaScript != nil) {
297-
NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript];
320+
[webView evaluateJavaScript:webViewBridgeScriptContent completionHandler:^(id result, NSError * _Nullable error) {
321+
_onLoadingFinish([self baseEvent]);
322+
}];
323+
}
298324

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

302-
_onLoadingFinish(event);
303-
}
304-
// we only need the final 'finishLoad' call so only fire the event when we're actually done loading.
305-
else if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) {
306-
_onLoadingFinish([self baseEvent]);
328+
if (!navigationAction.targetFrame.isMainFrame) {
329+
[webView loadRequest:navigationAction.request];
307330
}
331+
332+
return nil;
308333
}
309334

335+
#pragma mark - WebviewBridge helpers
336+
310337
- (NSArray*)stringArrayJsonToArray:(NSString *)message
311338
{
312339
return [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding]
@@ -326,8 +353,6 @@ - (NSString *)webViewBridgeScript {
326353

327354
return NSStringMultiline(
328355
(function (window) {
329-
'use strict';
330-
331356
//Make sure that if WebViewBridge already in scope we don't override it.
332357
if (window.WebViewBridge) {
333358
return;
@@ -339,14 +364,42 @@ - (NSString *)webViewBridgeScript {
339364
var doc = window.document;
340365
var customEvent = doc.createEvent('Event');
341366

367+
function wkWebViewBridgeAvailable() {
368+
return (
369+
window.webkit &&
370+
window.webkit.messageHandlers &&
371+
window.webkit.messageHandlers.observe &&
372+
window.webkit.messageHandlers.observe.postMessage
373+
);
374+
}
375+
376+
function wkWebViewSend(event) {
377+
if (!wkWebViewBridgeAvailable()) {
378+
return;
379+
}
380+
try {
381+
window.webkit.messageHandlers.observe.postMessage(event);
382+
} catch (e) {
383+
console.error('wkWebViewSend error', e.message);
384+
if (window.WebViewBridge.onError) {
385+
window.WebViewBridge.onError(e);
386+
}
387+
}
388+
}
389+
342390
function callFunc(func, message) {
343391
if ('function' === typeof func) {
344392
func(message);
345393
}
346394
}
347395

348396
function signalNative() {
349-
window.location = RNWBSchema + '://message' + new Date().getTime();
397+
if (wkWebViewBridgeAvailable()) {
398+
var event = window.WebViewBridge.__fetch__();
399+
wkWebViewSend(event);
400+
} else { // iOS UIWebview
401+
window.location = RNWBSchema + '://message' + new Date().getTime();
402+
}
350403
}
351404

352405
//I made the private function ugly signiture so user doesn't called them accidently.
@@ -396,7 +449,7 @@ function signalNative() {
396449
//dispatch event
397450
customEvent.initEvent('WebViewBridge', true, true);
398451
doc.dispatchEvent(customEvent);
399-
}(window));
452+
})(this);
400453
);
401454
}
402455

ios/RCTWebViewBridgeManager.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* of patent rights can be found in the PATENTS file in the same directory.
1111
*/
1212

13-
#import "RCTViewManager.h"
13+
#import <React/RCTViewManager.h>
1414

1515
@interface RCTWebViewBridgeManager : RCTViewManager
1616

ios/RCTWebViewBridgeManager.m

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@
1111
*/
1212

1313
#import "RCTWebViewBridgeManager.h"
14-
15-
#import "RCTBridge.h"
16-
#import "RCTUIManager.h"
1714
#import "RCTWebViewBridge.h"
18-
#import "UIView+React.h"
15+
16+
#import <React/RCTBridge.h>
17+
#import <React/RCTUIManager.h>
18+
19+
#import <React/UIView+React.h>
1920

2021
@interface RCTWebViewBridgeManager () <RCTWebViewBridgeDelegate>
2122

0 commit comments

Comments
 (0)