2018-09-08 04:11:49 +00:00
/ * *
* Copyright ( c ) 2015 - present , Facebook , Inc .
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree .
* /
2018-09-18 16:32:16 +00:00
# import "RNCWKWebView.h"
2018-09-08 04:11:49 +00:00
# import < React / RCTConvert . h >
# import < React / RCTAutoInsetsProtocol . h >
2018-12-14 10:18:48 +00:00
# import < UIKit / UIKit . h >
2018-09-08 04:11:49 +00:00
2018-10-17 14:59:19 +00:00
# import "objc/runtime.h"
2018-09-08 04:11:49 +00:00
static NSString * const MessageHanderName = @ "ReactNative" ;
2018-10-17 14:59:19 +00:00
// runtime trick to remove 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 ;
}
@ end
2018-09-18 16:32:16 +00:00
@ interface RNCWKWebView ( ) < WKUIDelegate , WKNavigationDelegate , WKScriptMessageHandler , UIScrollViewDelegate , RCTAutoInsetsProtocol >
2018-09-08 04:11:49 +00:00
@ property ( nonatomic , copy ) RCTDirectEventBlock onLoadingStart ;
@ property ( nonatomic , copy ) RCTDirectEventBlock onLoadingFinish ;
@ property ( nonatomic , copy ) RCTDirectEventBlock onLoadingError ;
2018-10-17 13:08:52 +00:00
@ property ( nonatomic , copy ) RCTDirectEventBlock onLoadingProgress ;
2018-09-08 04:11:49 +00:00
@ property ( nonatomic , copy ) RCTDirectEventBlock onShouldStartLoadWithRequest ;
@ property ( nonatomic , copy ) RCTDirectEventBlock onMessage ;
@ property ( nonatomic , copy ) WKWebView * webView ;
@ end
2018-09-18 16:32:16 +00:00
@ implementation RNCWKWebView
2018-09-08 04:11:49 +00:00
{
UIColor * _savedBackgroundColor ;
2018-10-17 14:59:19 +00:00
BOOL _savedHideKeyboardAccessoryView ;
2018-09-08 04:11:49 +00:00
}
2018-11-20 00:48:26 +00:00
- ( void ) dealloc { }
2018-09-08 04:11:49 +00:00
/ * *
* See https : // developer . apple . com / library / content / documentation / Cocoa / Conceptual / DisplayWebContent / Tasks / WebKitAvail . html .
* /
+ ( BOOL ) dynamicallyLoadWebKitIfAvailable
{
static BOOL _webkitAvailable = NO ;
static dispatch_once _t onceToken ;
dispatch_once ( & onceToken , ^ {
NSBundle * webKitBundle = [ NSBundle bundleWithPath : @ "/System/Library/Frameworks/WebKit.framework" ] ;
if ( webKitBundle ) {
_webkitAvailable = [ webKitBundle load ] ;
}
} ) ;
return _webkitAvailable ;
}
- ( instancetype ) initWithFrame : ( CGRect ) frame
{
if ( ( self = [ super initWithFrame : frame ] ) ) {
super . backgroundColor = [ UIColor clearColor ] ;
_bounces = YES ;
_scrollEnabled = YES ;
_automaticallyAdjustContentInsets = YES ;
_contentInset = UIEdgeInsetsZero ;
}
return self ;
}
- ( void ) didMoveToWindow
{
2018-10-18 06:34:00 +00:00
if ( self . window ! = nil && _webView = = nil ) {
2018-09-08 04:11:49 +00:00
if ( ! [ [ self class ] dynamicallyLoadWebKitIfAvailable ] ) {
return ;
} ;
WKWebViewConfiguration * wkWebViewConfig = [ WKWebViewConfiguration new ] ;
wkWebViewConfig . userContentController = [ WKUserContentController new ] ;
[ wkWebViewConfig . userContentController addScriptMessageHandler : self name : MessageHanderName ] ;
wkWebViewConfig . allowsInlineMediaPlayback = _allowsInlineMediaPlayback ;
# if WEBKIT_IOS _10 _APIS _AVAILABLE
wkWebViewConfig . mediaTypesRequiringUserActionForPlayback = _mediaPlaybackRequiresUserAction
? WKAudiovisualMediaTypeAll
: WKAudiovisualMediaTypeNone ;
2018-11-19 10:17:57 +00:00
wkWebViewConfig . dataDetectorTypes = _dataDetectorTypes ;
# else
wkWebViewConfig . mediaPlaybackRequiresUserAction = _mediaPlaybackRequiresUserAction ;
2018-09-08 04:11:49 +00:00
# endif
_webView = [ [ WKWebView alloc ] initWithFrame : self . bounds configuration : wkWebViewConfig ] ;
_webView . scrollView . delegate = self ;
_webView . UIDelegate = self ;
_webView . navigationDelegate = self ;
_webView . scrollView . scrollEnabled = _scrollEnabled ;
2018-11-22 12:58:07 +00:00
_webView . scrollView . pagingEnabled = _pagingEnabled ;
2018-09-08 04:11:49 +00:00
_webView . scrollView . bounces = _bounces ;
2018-11-25 10:17:28 +00:00
_webView . allowsLinkPreview = _allowsLinkPreview ;
2018-10-17 13:08:52 +00:00
[ _webView addObserver : self forKeyPath : @ "estimatedProgress" options : NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context : nil ] ;
2018-10-21 21:12:59 +00:00
_webView . allowsBackForwardNavigationGestures = _allowsBackForwardNavigationGestures ;
2018-11-25 10:17:28 +00:00
2018-11-19 10:13:31 +00:00
if ( _userAgent ) {
_webView . customUserAgent = _userAgent ;
}
2018-09-08 04:11:49 +00:00
# if defined ( __IPHONE _OS _VERSION _MAX _ALLOWED ) && __IPHONE _OS _VERSION _MAX _ALLOWED >= 110000 / * __IPHONE _11 _0 * /
if ( [ _webView . scrollView respondsToSelector : @ selector ( setContentInsetAdjustmentBehavior : ) ] ) {
_webView . scrollView . contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever ;
}
# endif
[ self addSubview : _webView ] ;
2018-10-17 14:59:19 +00:00
[ self setHideKeyboardAccessoryView : _savedHideKeyboardAccessoryView ] ;
2018-09-08 04:11:49 +00:00
[ self visitSource ] ;
}
}
2018-11-20 00:48:26 +00:00
- ( void ) removeFromSuperview
{
if ( _webView ) {
[ _webView . configuration . userContentController removeScriptMessageHandlerForName : MessageHanderName ] ;
[ _webView removeObserver : self forKeyPath : @ "estimatedProgress" ] ;
[ _webView removeFromSuperview ] ;
_webView = nil ;
}
2018-11-22 12:58:07 +00:00
2018-11-20 00:48:26 +00:00
[ super removeFromSuperview ] ;
}
2018-10-17 13:08:52 +00:00
- ( void ) observeValueForKeyPath : ( NSString * ) keyPath ofObject : ( id ) object change : ( NSDictionary < NSKeyValueChangeKey , id > * ) change context : ( void * ) context {
if ( [ keyPath isEqual : @ "estimatedProgress" ] && object = = self . webView ) {
if ( _onLoadingProgress ) {
NSMutableDictionary < NSString * , id > * event = [ self baseEvent ] ;
[ event addEntriesFromDictionary : @ { @ "progress" : [ NSNumber numberWithDouble : self . webView . estimatedProgress ] } ] ;
_onLoadingProgress ( event ) ;
}
} else {
[ super observeValueForKeyPath : keyPath ofObject : object change : change context : context ] ;
}
}
2018-09-08 04:11:49 +00:00
- ( void ) setBackgroundColor : ( UIColor * ) backgroundColor
{
_savedBackgroundColor = backgroundColor ;
if ( _webView = = nil ) {
return ;
}
CGFloat alpha = CGColorGetAlpha ( backgroundColor . CGColor ) ;
self . opaque = _webView . opaque = ( alpha = = 1.0 ) ;
_webView . scrollView . backgroundColor = backgroundColor ;
_webView . backgroundColor = backgroundColor ;
}
/ * *
* This method is called whenever JavaScript running within the web view calls :
* - window . webkit . messageHandlers . [ MessageHanderName ] . postMessage
* /
- ( void ) userContentController : ( WKUserContentController * ) userContentController
didReceiveScriptMessage : ( WKScriptMessage * ) message
{
if ( _onMessage ! = nil ) {
NSMutableDictionary < NSString * , id > * event = [ self baseEvent ] ;
[ event addEntriesFromDictionary : @ { @ "data" : message . body } ] ;
_onMessage ( event ) ;
}
}
- ( void ) setSource : ( NSDictionary * ) source
{
if ( ! [ _source isEqualToDictionary : source ] ) {
_source = [ source copy ] ;
if ( _webView ! = nil ) {
[ self visitSource ] ;
}
}
}
- ( void ) setContentInset : ( UIEdgeInsets ) contentInset
{
_contentInset = contentInset ;
[ RCTView autoAdjustInsetsForView : self
withScrollView : _webView . scrollView
updateOffset : NO ] ;
}
- ( void ) refreshContentInset
{
[ RCTView autoAdjustInsetsForView : self
withScrollView : _webView . scrollView
updateOffset : YES ] ;
}
- ( void ) visitSource
{
// Check for a static html source first
NSString * html = [ RCTConvert NSString : _source [ @ "html" ] ] ;
if ( html ) {
NSURL * baseURL = [ RCTConvert NSURL : _source [ @ "baseUrl" ] ] ;
if ( ! baseURL ) {
baseURL = [ NSURL URLWithString : @ "about:blank" ] ;
}
[ _webView loadHTMLString : html baseURL : baseURL ] ;
return ;
}
NSURLRequest * request = [ RCTConvert NSURLRequest : _source ] ;
// 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 ( [ request . URL isEqual : _webView . URL ] ) {
return ;
}
if ( ! request . URL ) {
// Clear the webview
[ _webView loadHTMLString : @ "" baseURL : nil ] ;
return ;
}
[ _webView loadRequest : request ] ;
}
2018-10-17 14:59:19 +00:00
- ( void ) setHideKeyboardAccessoryView : ( BOOL ) hideKeyboardAccessoryView
{
2018-11-19 10:17:57 +00:00
2018-10-17 14:59:19 +00:00
if ( _webView = = nil ) {
_savedHideKeyboardAccessoryView = hideKeyboardAccessoryView ;
return ;
}
if ( _savedHideKeyboardAccessoryView = = false ) {
return ;
}
2018-11-19 10:17:57 +00:00
2018-10-17 14:59:19 +00:00
UIView * subview ;
for ( UIView * view in _webView . scrollView . subviews ) {
if ( [ [ view . class description ] hasPrefix : @ "WK" ] )
subview = view ;
}
2018-11-19 10:17:57 +00:00
2018-10-17 14:59:19 +00:00
if ( subview = = nil ) return ;
2018-11-19 10:17:57 +00:00
2018-10-17 14:59:19 +00:00
NSString * name = [ NSString stringWithFormat : @ "%@_SwizzleHelperWK" , subview . class . superclass ] ;
Class newClass = NSClassFromString ( name ) ;
2018-11-19 10:17:57 +00:00
2018-10-17 14:59:19 +00:00
if ( newClass = = nil )
{
newClass = objc_allocateClassPair ( subview . class , [ name cStringUsingEncoding : NSASCIIStringEncoding ] , 0 ) ;
if ( ! newClass ) return ;
2018-11-19 10:17:57 +00:00
2018-10-17 14:59:19 +00:00
Method method = class_getInstanceMethod ( [ _SwizzleHelperWK class ] , @ selector ( inputAccessoryView ) ) ;
class_addMethod ( newClass , @ selector ( inputAccessoryView ) , method_getImplementation ( method ) , method_getTypeEncoding ( method ) ) ;
2018-11-19 10:17:57 +00:00
2018-10-17 14:59:19 +00:00
objc_registerClassPair ( newClass ) ;
}
2018-11-19 10:17:57 +00:00
2018-10-17 14:59:19 +00:00
object_setClass ( subview , newClass ) ;
}
2018-09-08 04:11:49 +00:00
- ( void ) scrollViewWillBeginDragging : ( UIScrollView * ) scrollView
{
scrollView . decelerationRate = _decelerationRate ;
}
- ( void ) setScrollEnabled : ( BOOL ) scrollEnabled
{
_scrollEnabled = scrollEnabled ;
_webView . scrollView . scrollEnabled = scrollEnabled ;
}
- ( void ) postMessage : ( NSString * ) message
{
NSDictionary * eventInitDict = @ { @ "data" : message } ;
NSString * source = [ NSString
stringWithFormat : @ "document.dispatchEvent(new MessageEvent('message', %@));" ,
RCTJSONStringify ( eventInitDict , NULL )
] ;
[ self evaluateJS : source thenCall : nil ] ;
}
- ( void ) layoutSubviews
{
[ super layoutSubviews ] ;
2018-09-18 16:32:16 +00:00
// Ensure webview takes the position and dimensions of RNCWKWebView
2018-09-08 04:11:49 +00:00
_webView . frame = self . bounds ;
}
- ( NSMutableDictionary < NSString * , id > * ) baseEvent
{
NSDictionary * event = @ {
@ "url" : _webView . URL . absoluteString ? : @ "" ,
@ "title" : _webView . title ,
@ "loading" : @ ( _webView . loading ) ,
@ "canGoBack" : @ ( _webView . canGoBack ) ,
@ "canGoForward" : @ ( _webView . canGoForward )
} ;
return [ [ NSMutableDictionary alloc ] initWithDictionary : event ] ;
}
# pragma mark - WKNavigationDelegate methods
2018-12-14 10:18:48 +00:00
/ * *
* alert
* /
- ( void ) webView : ( WKWebView * ) webView runJavaScriptAlertPanelWithMessage : ( NSString * ) message initiatedByFrame : ( WKFrameInfo * ) frame completionHandler : ( void ( ^ ) ( void ) ) completionHandler
{
UIAlertController * alert = [ UIAlertController alertControllerWithTitle : @ "" message : message preferredStyle : UIAlertControllerStyleAlert ] ;
[ alert addAction : [ UIAlertAction actionWithTitle : @ "Ok" style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * action ) {
completionHandler ( ) ;
} ] ] ;
[ [ self topViewController ] presentViewController : alert animated : YES completion : NULL ] ;
}
/ * *
* confirm
* /
- ( void ) webView : ( WKWebView * ) webView runJavaScriptConfirmPanelWithMessage : ( NSString * ) message initiatedByFrame : ( WKFrameInfo * ) frame completionHandler : ( void ( ^ ) ( BOOL ) ) completionHandler {
UIAlertController * alert = [ UIAlertController alertControllerWithTitle : @ "" message : message preferredStyle : UIAlertControllerStyleAlert ] ;
[ alert addAction : [ UIAlertAction actionWithTitle : @ "Ok" style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * action ) {
completionHandler ( YES ) ;
} ] ] ;
[ alert addAction : [ UIAlertAction actionWithTitle : @ "Cancel" style : UIAlertActionStyleCancel handler : ^ ( UIAlertAction * action ) {
completionHandler ( NO ) ;
} ] ] ;
[ [ self topViewController ] presentViewController : alert animated : YES completion : NULL ] ;
}
/ * *
* prompt
* /
- ( void ) webView : ( WKWebView * ) webView runJavaScriptTextInputPanelWithPrompt : ( NSString * ) prompt defaultText : ( NSString * ) defaultText initiatedByFrame : ( WKFrameInfo * ) frame completionHandler : ( void ( ^ ) ( NSString * ) ) completionHandler {
UIAlertController * alert = [ UIAlertController alertControllerWithTitle : @ "" message : prompt preferredStyle : UIAlertControllerStyleAlert ] ;
[ alert addTextFieldWithConfigurationHandler : ^ ( UITextField * textField ) {
textField . textColor = [ UIColor lightGrayColor ] ;
textField . placeholder = defaultText ;
} ] ;
[ alert addAction : [ UIAlertAction actionWithTitle : @ "Ok" style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * action ) {
completionHandler ( [ [ alert . textFields lastObject ] text ] ) ;
} ] ] ;
[ [ self topViewController ] presentViewController : alert animated : YES completion : NULL ] ;
}
/ * *
* topViewController
* /
- ( UIViewController * ) topViewController {
UIViewController * controller = [ self topViewControllerWithRootViewController : [ self getCurrentWindow ] . rootViewController ] ;
return controller ;
}
/ * *
* topViewControllerWithRootViewController
* /
- ( UIViewController * ) topViewControllerWithRootViewController : ( UIViewController * ) viewController {
if ( viewController = = nil ) return nil ;
if ( viewController . presentedViewController ! = nil ) {
return [ self topViewControllerWithRootViewController : viewController . presentedViewController ] ;
} else if ( [ viewController isKindOfClass : [ UITabBarController class ] ] ) {
return [ self topViewControllerWithRootViewController : [ ( UITabBarController * ) viewController selectedViewController ] ] ;
} else if ( [ viewController isKindOfClass : [ UINavigationController class ] ] ) {
return [ self topViewControllerWithRootViewController : [ ( UINavigationController * ) viewController visibleViewController ] ] ;
} else {
return viewController ;
}
}
/ * *
* getCurrentWindow
* /
- ( UIWindow * ) getCurrentWindow {
UIWindow * window = [ UIApplication sharedApplication ] . keyWindow ;
if ( window . windowLevel ! = UIWindowLevelNormal ) {
for ( UIWindow * wid in [ UIApplication sharedApplication ] . windows ) {
if ( window . windowLevel = = UIWindowLevelNormal ) {
window = wid ;
break ;
}
}
}
return window ;
}
2018-09-08 04:11:49 +00:00
/ * *
* Decides whether to allow or cancel a navigation .
* @ see https : // fburl . com / 42 r9fxob
* /
- ( void ) webView : ( WKWebView * ) webView
decidePolicyForNavigationAction : ( WKNavigationAction * ) navigationAction
decisionHandler : ( void ( ^ ) ( WKNavigationActionPolicy ) ) decisionHandler
{
static NSDictionary < NSNumber * , NSString * > * navigationTypes ;
static dispatch_once _t onceToken ;
dispatch_once ( & onceToken , ^ {
navigationTypes = @ {
@ ( WKNavigationTypeLinkActivated ) : @ "click" ,
@ ( WKNavigationTypeFormSubmitted ) : @ "formsubmit" ,
@ ( WKNavigationTypeBackForward ) : @ "backforward" ,
@ ( WKNavigationTypeReload ) : @ "reload" ,
@ ( WKNavigationTypeFormResubmitted ) : @ "formresubmit" ,
@ ( WKNavigationTypeOther ) : @ "other" ,
} ;
} ) ;
WKNavigationType navigationType = navigationAction . navigationType ;
NSURLRequest * request = navigationAction . request ;
if ( _onShouldStartLoadWithRequest ) {
NSMutableDictionary < NSString * , id > * event = [ self baseEvent ] ;
[ event addEntriesFromDictionary : @ {
@ "url" : ( request . URL ) . absoluteString ,
@ "navigationType" : navigationTypes [ @ ( navigationType ) ]
} ] ;
if ( ! [ self . delegate webView : self
shouldStartLoadForRequest : event
withCallback : _onShouldStartLoadWithRequest ] ) {
decisionHandler ( WKNavigationResponsePolicyCancel ) ;
return ;
}
}
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" : navigationTypes [ @ ( navigationType ) ]
} ] ;
_onLoadingStart ( event ) ;
}
}
// Allow all navigation by default
decisionHandler ( WKNavigationResponsePolicyAllow ) ;
}
/ * *
* Called when an error occurs while the web view is loading content .
* @ see https : // fburl . com / km6vqenw
* /
- ( void ) webView : ( WKWebView * ) webView
didFailProvisionalNavigation : ( 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
// 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 < NSString * , id > * event = [ self baseEvent ] ;
[ event addEntriesFromDictionary : @ {
@ "didFailProvisionalNavigation" : @ YES ,
@ "domain" : error . domain ,
@ "code" : @ ( error . code ) ,
@ "description" : error . localizedDescription ,
} ] ;
_onLoadingError ( event ) ;
}
[ self setBackgroundColor : _savedBackgroundColor ] ;
}
- ( void ) evaluateJS : ( NSString * ) js
thenCall : ( void ( ^ ) ( NSString * ) ) callback
{
[ self . webView evaluateJavaScript : js completionHandler : ^ ( id result , NSError * error ) {
2018-12-14 10:24:52 +00:00
if ( error = = nil ) {
if ( callback ! = nil ) {
callback ( [ NSString stringWithFormat : @ "%@" , result ] ) ;
}
} else {
RCTLogError ( @ "Error evaluating injectedJavaScript: This is possibly due to an unsupported return type. Try adding true to the end of your injectedJavaScript string." ) ;
2018-09-08 04:11:49 +00:00
}
} ] ;
}
/ * *
* Called when the navigation is complete .
* @ see https : // fburl . com / rtys6jlb
* /
- ( void ) webView : ( WKWebView * ) webView
didFinishNavigation : ( WKNavigation * ) navigation
{
if ( _messagingEnabled ) {
# if RCT_DEV
// Implementation inspired by Lodash . isNative .
NSString * isPostMessageNative = @ "String(String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage'))" ;
[ self evaluateJS : isPostMessageNative thenCall : ^ ( NSString * result ) {
if ( ! [ result isEqualToString : @ "true" ] ) {
RCTLogError ( @ "Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined" ) ;
}
} ] ;
# endif
NSString * source = [ NSString stringWithFormat :
@ "(function() {"
"window.originalPostMessage = window.postMessage;"
"window.postMessage = function(data) {"
"window.webkit.messageHandlers.%@.postMessage(String(data));"
"};"
"})();" ,
MessageHanderName
] ;
[ self evaluateJS : source thenCall : nil ] ;
}
if ( _injectedJavaScript ) {
[ self evaluateJS : _injectedJavaScript thenCall : ^ ( NSString * jsEvaluationValue ) {
NSMutableDictionary * event = [ self baseEvent ] ;
event [ @ "jsEvaluationValue" ] = jsEvaluationValue ;
if ( self . onLoadingFinish ) {
self . onLoadingFinish ( event ) ;
}
} ] ;
} else if ( _onLoadingFinish ) {
_onLoadingFinish ( [ self baseEvent ] ) ;
}
[ self setBackgroundColor : _savedBackgroundColor ] ;
}
- ( void ) injectJavaScript : ( NSString * ) script
{
[ self evaluateJS : script thenCall : nil ] ;
}
- ( void ) goForward
{
[ _webView goForward ] ;
}
- ( void ) goBack
{
[ _webView goBack ] ;
}
- ( void ) reload
{
/ * *
* When the initial load fails due to network connectivity issues ,
* [ _webView reload ] doesn ' t reload the webpage . Therefore , we must
* manually call [ _webView loadRequest : request ] .
* /
NSURLRequest * request = [ RCTConvert NSURLRequest : self . source ] ;
if ( request . URL && ! _webView . URL . absoluteString . length ) {
[ _webView loadRequest : request ] ;
}
else {
[ _webView reload ] ;
}
}
- ( void ) stopLoading
{
[ _webView stopLoading ] ;
}
- ( void ) setBounces : ( BOOL ) bounces
{
_bounces = bounces ;
_webView . scrollView . bounces = bounces ;
}
@ end