diff --git a/WebViewJavascriptBridge/WebViewEx.h b/WebViewJavascriptBridge/WebViewEx.h new file mode 100644 index 0000000..6122069 --- /dev/null +++ b/WebViewJavascriptBridge/WebViewEx.h @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTView.h" + +@class RCTEventDispatcher; + +@interface WebViewEx : RCTView + +@property (nonatomic, strong) NSURL *URL; +@property (nonatomic, assign) UIEdgeInsets contentInset; +@property (nonatomic, assign) BOOL shouldInjectAJAXHandler; +@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; + +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; + +- (void)goForward; +- (void)goBack; +- (void)reload; + +@end \ No newline at end of file diff --git a/WebViewJavascriptBridge/WebViewEx.m b/WebViewJavascriptBridge/WebViewEx.m new file mode 100644 index 0000000..ede495a --- /dev/null +++ b/WebViewJavascriptBridge/WebViewEx.m @@ -0,0 +1,205 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "WebViewEx.h" + +#import + +#import "RCTAutoInsetsProtocol.h" +#import "RCTEventDispatcher.h" +#import "RCTLog.h" +#import "RCTUtils.h" +#import "RCTView.h" +#import "UIView+React.h" + +@interface WebViewEx () + +@end + +@implementation WebViewEx +{ + RCTEventDispatcher *_eventDispatcher; + UIWebView *_webView; +} + +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher +{ + if ((self = [super initWithFrame:CGRectZero])) { + super.backgroundColor = [UIColor clearColor]; + _automaticallyAdjustContentInsets = YES; + _contentInset = UIEdgeInsetsZero; + _eventDispatcher = eventDispatcher; + _webView = [[UIWebView alloc] initWithFrame:self.bounds]; + _webView.delegate = self; + [self addSubview:_webView]; + } + return self; +} + +- (void)goForward +{ + [_webView goForward]; +} + +- (void)goBack +{ + [_webView goBack]; +} + +- (void)reload +{ + [_webView reload]; +} + +- (void)setURL:(NSURL *)URL +{ + // Because of the way React works, as pages redirect, we actually end up + // 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 ([URL isEqual:_webView.request.URL]) { + return; + } + if (!URL) { + // Clear the webview + [_webView loadHTMLString:nil baseURL:nil]; + return; + } + [_webView loadRequest:[NSURLRequest requestWithURL:URL]]; +} + +- (void)setHTML:(NSString *)HTML +{ + [_webView loadHTMLString:HTML baseURL:nil]; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + _webView.frame = self.bounds; + [RCTView autoAdjustInsetsForView:self + withScrollView:_webView.scrollView + updateOffset:YES]; +} + +- (void)setContentInset:(UIEdgeInsets)contentInset +{ + _contentInset = contentInset; + [RCTView autoAdjustInsetsForView:self + withScrollView:_webView.scrollView + updateOffset:NO]; +} + +- (void)setBackgroundColor:(UIColor *)backgroundColor +{ + CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor); + self.opaque = _webView.opaque = (alpha == 1.0); + _webView.backgroundColor = backgroundColor; +} + +- (UIColor *)backgroundColor +{ + return _webView.backgroundColor; +} + +- (NSMutableDictionary *)baseEvent +{ + NSURL *url = _webView.request.URL; + NSString *title = [_webView stringByEvaluatingJavaScriptFromString:@"document.title"]; + NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary: @{ + @"target": self.reactTag, + @"url": url ? [url absoluteString] : @"", + @"loading" : @(_webView.loading), + @"title": title, + @"canGoBack": @([_webView canGoBack]), + @"canGoForward" : @([_webView canGoForward]), + }]; + + return event; +} + +#pragma mark - UIWebViewDelegate methods + +static NSString *const RCTJSAJAXScheme = @"react-ajax"; + +- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request + navigationType:(UIWebViewNavigationType)navigationType +{ + // We have this check to filter out iframe requests and whatnot + BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL]; + if (isTopFrame) { + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary: @{ + @"url": [request.URL absoluteString], + @"navigationType": @(navigationType) + }]; + [_eventDispatcher sendInputEventWithName:@"topLoadingStart" body:event]; + } + + // AJAX handler + return ![request.URL.scheme isEqualToString:RCTJSAJAXScheme]; +} + +- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error +{ + if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { + // NSURLErrorCancelled is reported when a page has a redirect OR if you load + // a new URL in the WebView before the previous one came back. We can just + // ignore these since they aren't real errors. + // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os + return; + } + + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary: @{ + @"domain": error.domain, + @"code": @(error.code), + @"description": [error localizedDescription], + }]; + [_eventDispatcher sendInputEventWithName:@"topLoadingError" body:event]; +} + +- (void)webViewDidFinishLoad:(UIWebView *)webView +{ + if (_shouldInjectAJAXHandler) { + + // From http://stackoverflow.com/questions/5353278/uiwebviewdelegate-not-monitoring-xmlhttprequest + + [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"\ + var s_ajaxListener = new Object(); \n\ + s_ajaxListener.tempOpen = XMLHttpRequest.prototype.open; \n\ + s_ajaxListener.tempSend = XMLHttpRequest.prototype.send; \n\ + s_ajaxListener.callback = function() { \n\ + window.location.href = '%@://' + this.url; \n\ + } \n\ + XMLHttpRequest.prototype.open = function(a,b) { \n\ + s_ajaxListener.tempOpen.apply(this, arguments); \n\ + s_ajaxListener.method = a; \n\ + s_ajaxListener.url = b; \n\ + if (a.toLowerCase() === 'get') { \n\ + s_ajaxListener.data = (b.split('?'))[1]; \n\ + } \n\ + } \n\ + XMLHttpRequest.prototype.send = function(a,b) { \n\ + s_ajaxListener.tempSend.apply(this, arguments); \n\ + if (s_ajaxListener.method.toLowerCase() === 'post') { \n\ + s_ajaxListener.data = a; \n\ + } \n\ + s_ajaxListener.callback(); \n\ + } \n\ + ", RCTJSAJAXScheme]]; + } + + // we only need the final 'finishLoad' call so only fire the event when we're actually done loading. + if (!webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) { + [_eventDispatcher sendInputEventWithName:@"topLoadingFinish" body:[self baseEvent]]; + } +} + +@end \ No newline at end of file diff --git a/WebViewJavascriptBridge/WebViewExManager.h b/WebViewJavascriptBridge/WebViewExManager.h new file mode 100644 index 0000000..b4ddbc7 --- /dev/null +++ b/WebViewJavascriptBridge/WebViewExManager.h @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTViewManager.h" + +@interface WebViewExManager : RCTViewManager + +@end \ No newline at end of file diff --git a/WebViewJavascriptBridge/WebViewExManager.m b/WebViewJavascriptBridge/WebViewExManager.m new file mode 100644 index 0000000..40db627 --- /dev/null +++ b/WebViewJavascriptBridge/WebViewExManager.m @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "WebViewExManager.h" + +#import "RCTBridge.h" +#import "RCTSparseArray.h" +#import "RCTUIManager.h" +#import "RCTWebView.h" + +@implementation WebViewExManager + +RCT_EXPORT_MODULE() + +- (UIView *)view +{ + return [[RCTWebView alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; +} + +RCT_REMAP_VIEW_PROPERTY(url, URL, NSURL); +RCT_REMAP_VIEW_PROPERTY(html, HTML, NSString); +RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL); +RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL); +RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets); +RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL); +RCT_EXPORT_VIEW_PROPERTY(shouldInjectAJAXHandler, BOOL); + +- (NSDictionary *)constantsToExport +{ + return @{ + @"NavigationType": @{ + @"LinkClicked": @(UIWebViewNavigationTypeLinkClicked), + @"FormSubmitted": @(UIWebViewNavigationTypeFormSubmitted), + @"BackForward": @(UIWebViewNavigationTypeBackForward), + @"Reload": @(UIWebViewNavigationTypeReload), + @"FormResubmitted": @(UIWebViewNavigationTypeFormResubmitted), + @"Other": @(UIWebViewNavigationTypeOther) + }, + }; +} + +RCT_EXPORT_METHOD(goBack:(NSNumber *)reactTag) +{ + [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + RCTWebView *view = viewRegistry[reactTag]; + if (![view isKindOfClass:[RCTWebView class]]) { + RCTLogError(@"Invalid view returned from registry, expecting RKWebView, got: %@", view); + } + [view goBack]; + }]; +} + +RCT_EXPORT_METHOD(goForward:(NSNumber *)reactTag) +{ + [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + id view = viewRegistry[reactTag]; + if (![view isKindOfClass:[RCTWebView class]]) { + RCTLogError(@"Invalid view returned from registry, expecting RKWebView, got: %@", view); + } + [view goForward]; + }]; +} + + +RCT_EXPORT_METHOD(reload:(NSNumber *)reactTag) +{ + [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + RCTWebView *view = viewRegistry[reactTag]; + if (![view isKindOfClass:[RCTWebView class]]) { + RCTLogMustFix(@"Invalid view returned from registry, expecting RKWebView, got: %@", view); + } + [view reload]; + }]; +} + +@end \ No newline at end of file diff --git a/jsx/index.ios.js b/jsx/index.ios.js index 6166612..fda42f2 100644 --- a/jsx/index.ios.js +++ b/jsx/index.ios.js @@ -5,28 +5,19 @@ 'use strict'; var React = require('react-native'); +var WebViewEx = require('./webviewex.js'); + var { AppRegistry, StyleSheet, Text, - View, + View } = React; var webview = React.createClass({ render: function() { return ( - - - Welcome to React Native! - - - To get started, edit index.ios.js - - - Press Cmd+R to reload,{'\n'} - Cmd+D or shake for dev menu - - + ); } }); diff --git a/jsx/webviewex.js b/jsx/webviewex.js new file mode 100644 index 0000000..60c7d17 --- /dev/null +++ b/jsx/webviewex.js @@ -0,0 +1,253 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule WebViewEx + * @flow + */ +'use strict'; + +var ActivityIndicatorIOS = require('ActivityIndicatorIOS'); +var EdgeInsetsPropType = require('EdgeInsetsPropType'); +var React = require('React'); +var StyleSheet = require('StyleSheet'); +var Text = require('Text'); +var View = require('View'); + +var invariant = require('invariant'); +var keyMirror = require('keyMirror'); +var requireNativeComponent = require('requireNativeComponent'); + +var PropTypes = React.PropTypes; +var WebViewExManager = require('NativeModules').WebViewExManager; + +var BGWASH = 'rgba(255,255,255,0.8)'; +var RCT_WEBVIEW_REF = 'webviewex'; + +var WebViewState = keyMirror({ + IDLE: null, + LOADING: null, + ERROR: null, +}); + +var NavigationType = { + click: WebViewExManager.NavigationType.LinkClicked, + formsubmit: WebViewExManager.NavigationType.FormSubmitted, + backforward: WebViewExManager.NavigationType.BackForward, + reload: WebViewExManager.NavigationType.Reload, + formresubmit: WebViewExManager.NavigationType.FormResubmitted, + other: WebViewExManager.NavigationType.Other, +}; + +type ErrorEvent = { + domain: any; + code: any; + description: any; +} + +type Event = Object; + +var defaultRenderLoading = () => ( + + + +); +var defaultRenderError = (errorDomain, errorCode, errorDesc) => ( + + + Error loading page + + + {'Domain: ' + errorDomain} + + + {'Error Code: ' + errorCode} + + + {'Description: ' + errorDesc} + + +); + +var WebViewEx = React.createClass({ + statics: { + NavigationType: NavigationType, + }, + + propTypes: { + url: PropTypes.string, + html: PropTypes.string, + renderError: PropTypes.func, // view to show if there's an error + renderLoading: PropTypes.func, // loading indicator to show + bounces: PropTypes.bool, + scrollEnabled: PropTypes.bool, + automaticallyAdjustContentInsets: PropTypes.bool, + shouldInjectAJAXHandler: PropTypes.bool, + contentInset: EdgeInsetsPropType, + onNavigationStateChange: PropTypes.func, + startInLoadingState: PropTypes.bool, // force WebViewEx to show loadingView on first load + style: View.propTypes.style, + /** + * Used for android only, JS is enabled by default for WebViewEx on iOS + */ + javaScriptEnabledAndroid: PropTypes.bool, + }, + + getInitialState: function() { + return { + viewState: WebViewState.IDLE, + lastErrorEvent: (null: ?ErrorEvent), + startInLoadingState: true, + }; + }, + + componentWillMount: function() { + if (this.props.startInLoadingState) { + this.setState({viewState: WebViewState.LOADING}); + } + }, + + render: function() { + var otherView = null; + + if (this.state.viewState === WebViewState.LOADING) { + otherView = (this.props.renderLoading || defaultRenderLoading)(); + } else if (this.state.viewState === WebViewState.ERROR) { + var errorEvent = this.state.lastErrorEvent; + invariant( + errorEvent != null, + 'lastErrorEvent expected to be non-null' + ); + otherView = (this.props.renderError || defaultRenderError)( + errorEvent.domain, + errorEvent.code, + errorEvent.description + ); + } else if (this.state.viewState !== WebViewState.IDLE) { + console.error( + 'WebViewEx invalid state encountered: ' + this.state.loading + ); + } + + var webViewStyles = [styles.container, styles.webView, this.props.style]; + if (this.state.viewState === WebViewState.LOADING || + this.state.viewState === WebViewState.ERROR) { + // if we're in either LOADING or ERROR states, don't show the webView + webViewStyles.push(styles.hidden); + } + + var webView = + ; + + return ( + + {webView} + {otherView} + + ); + }, + + goForward: function() { + WebViewExManager.goForward(this.getWebWiewHandle()); + }, + + goBack: function() { + WebViewExManager.goBack(this.getWebWiewHandle()); + }, + + reload: function() { + WebViewExManager.reload(this.getWebWiewHandle()); + }, + + /** + * We return an event with a bunch of fields including: + * url, title, loading, canGoBack, canGoForward + */ + updateNavigationState: function(event: Event) { + if (this.props.onNavigationStateChange) { + this.props.onNavigationStateChange(event.nativeEvent); + } + }, + + getWebWiewHandle: function(): any { + return React.findNodeHandle(this.refs[RCT_WEBVIEW_REF]); + }, + + onLoadingStart: function(event: Event) { + this.updateNavigationState(event); + }, + + onLoadingError: function(event: Event) { + event.persist(); // persist this event because we need to store it + console.error('Encountered an error loading page', event.nativeEvent); + + this.setState({ + lastErrorEvent: event.nativeEvent, + viewState: WebViewState.ERROR + }); + }, + + onLoadingFinish: function(event: Event) { + this.setState({ + viewState: WebViewState.IDLE, + }); + this.updateNavigationState(event); + }, +}); + +var WebViewEx = requireNativeComponent('WebViewEx', WebViewEx); + +var styles = StyleSheet.create({ + container: { + flex: 1, + }, + errorContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: BGWASH, + }, + errorText: { + fontSize: 14, + textAlign: 'center', + marginBottom: 2, + }, + errorTextTitle: { + fontSize: 15, + fontWeight: '500', + marginBottom: 10, + }, + hidden: { + height: 0, + flex: 0, // disable 'flex:1' when hiding a View + }, + loadingView: { + backgroundColor: BGWASH, + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + webView: { + backgroundColor: '#ffffff', + } +}); + +module.exports = WebViewEx; diff --git a/webview.xcodeproj/project.pbxproj b/webview.xcodeproj/project.pbxproj index 4fb28cd..1d6e85d 100644 --- a/webview.xcodeproj/project.pbxproj +++ b/webview.xcodeproj/project.pbxproj @@ -22,6 +22,9 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; + 41117F301B25EB7800342843 /* WebViewJavascriptBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 41117F2F1B25EB7800342843 /* WebViewJavascriptBridge.m */; }; + 41117F3D1B25ED7700342843 /* WebViewExManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 41117F3C1B25ED7700342843 /* WebViewExManager.m */; }; + 41117F401B260BBB00342843 /* WebViewEx.m in Sources */ = {isa = PBXBuildFile; fileRef = 41117F3F1B260BBB00342843 /* WebViewEx.m */; }; 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; /* End PBXBuildFile section */ @@ -107,16 +110,16 @@ /* Begin PBXFileReference section */ 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = main.jsbundle; path = iOS/main.jsbundle; sourceTree = ""; }; - 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = ""; }; - 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = ""; }; - 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = node_modules/react-native/Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; - 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; - 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = ""; }; + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = "node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj"; sourceTree = ""; }; + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = "node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj"; sourceTree = ""; }; + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = "node_modules/react-native/Libraries/Image/RCTImage.xcodeproj"; sourceTree = ""; }; + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = "node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj"; sourceTree = ""; }; + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = ""; }; 00E356EE1AD99517003FC87E /* webviewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = webviewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* webviewTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = webviewTests.m; sourceTree = ""; }; - 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj; sourceTree = ""; }; - 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = ""; }; + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; }; + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* webview.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = webview.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = iOS/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = iOS/AppDelegate.m; sourceTree = ""; }; @@ -124,9 +127,15 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = iOS/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = iOS/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = iOS/main.m; sourceTree = ""; }; - 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = node_modules/react-native/React/React.xcodeproj; sourceTree = ""; }; - 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; }; - 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = node_modules/react-native/Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; + 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; + 41117F2E1B25EB7800342843 /* WebViewJavascriptBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebViewJavascriptBridge.h; sourceTree = ""; }; + 41117F2F1B25EB7800342843 /* WebViewJavascriptBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebViewJavascriptBridge.m; sourceTree = ""; }; + 41117F3B1B25ED7700342843 /* WebViewExManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebViewExManager.h; sourceTree = ""; }; + 41117F3C1B25ED7700342843 /* WebViewExManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebViewExManager.m; sourceTree = ""; }; + 41117F3E1B260BBB00342843 /* WebViewEx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebViewEx.h; sourceTree = ""; }; + 41117F3F1B260BBB00342843 /* WebViewEx.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebViewEx.m; sourceTree = ""; }; + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -252,6 +261,19 @@ name = Products; sourceTree = ""; }; + 41117F2D1B25EB7800342843 /* WebViewJavascriptBridge */ = { + isa = PBXGroup; + children = ( + 41117F2E1B25EB7800342843 /* WebViewJavascriptBridge.h */, + 41117F2F1B25EB7800342843 /* WebViewJavascriptBridge.m */, + 41117F3B1B25ED7700342843 /* WebViewExManager.h */, + 41117F3C1B25ED7700342843 /* WebViewExManager.m */, + 41117F3E1B260BBB00342843 /* WebViewEx.h */, + 41117F3F1B260BBB00342843 /* WebViewEx.m */, + ); + path = WebViewJavascriptBridge; + sourceTree = ""; + }; 78C398B11ACF4ADC00677621 /* Products */ = { isa = PBXGroup; children = ( @@ -288,6 +310,7 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( + 41117F2D1B25EB7800342843 /* WebViewJavascriptBridge */, 13B07FAE1A68108700A75B9A /* webview */, 832341AE1AAA6A7D00B99B32 /* Libraries */, 00E356EF1AD99517003FC87E /* webviewTests */, @@ -526,8 +549,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 41117F3D1B25ED7700342843 /* WebViewExManager.m in Sources */, 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, + 41117F301B25EB7800342843 /* WebViewJavascriptBridge.m in Sources */, 13B07FC11A68108700A75B9A /* main.m in Sources */, + 41117F401B260BBB00342843 /* WebViewEx.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/webview.xcodeproj/project.xcworkspace/xcuserdata/ali.xcuserdatad/UserInterfaceState.xcuserstate b/webview.xcodeproj/project.xcworkspace/xcuserdata/ali.xcuserdatad/UserInterfaceState.xcuserstate index 01b0fa2..5a2c314 100644 Binary files a/webview.xcodeproj/project.xcworkspace/xcuserdata/ali.xcuserdatad/UserInterfaceState.xcuserstate and b/webview.xcodeproj/project.xcworkspace/xcuserdata/ali.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/webview.xcodeproj/xcuserdata/ali.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/webview.xcodeproj/xcuserdata/ali.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..fe2b454 --- /dev/null +++ b/webview.xcodeproj/xcuserdata/ali.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,5 @@ + + +