// // NJKWebViewProgress.m // // Created by Satoshi Aasano on 4/20/13. // Copyright (c) 2013 Satoshi Asano. All rights reserved. // #import "NJKWebViewProgress.h" NSString *completeRPCURLPath = @"/njkwebviewprogressproxy/complete"; const float NJKInitialProgressValue = 0.1f; const float NJKInteractiveProgressValue = 0.5f; const float NJKFinalProgressValue = 0.9f; @implementation NJKWebViewProgress { NSUInteger _loadingCount; NSUInteger _maxLoadCount; NSURL *_currentURL; BOOL _interactive; } - (id)init { self = [super init]; if (self) { _maxLoadCount = _loadingCount = 0; _interactive = NO; } return self; } - (void)startProgress { if (_progress < NJKInitialProgressValue) { [self setProgress:NJKInitialProgressValue]; } } - (void)incrementProgress { float progress = self.progress; float maxProgress = _interactive ? NJKFinalProgressValue : NJKInteractiveProgressValue; float remainPercent = (float)_loadingCount / (float)_maxLoadCount; float increment = (maxProgress - progress) * remainPercent; progress += increment; progress = fmin(progress, maxProgress); [self setProgress:progress]; } - (void)completeProgress { [self setProgress:1.0]; } - (void)setProgress:(float)progress { // progress should be incremental only if (progress > _progress || progress == 0) { _progress = progress; if ([_progressDelegate respondsToSelector:@selector(webViewProgress:updateProgress:)]) { [_progressDelegate webViewProgress:self updateProgress:progress]; } if (_progressBlock) { _progressBlock(progress); } } } - (void)reset { _maxLoadCount = _loadingCount = 0; _interactive = NO; [self setProgress:0.0]; } #pragma mark - #pragma mark UIWebViewDelegate - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { if ([request.URL.path isEqualToString:completeRPCURLPath]) { [self completeProgress]; return NO; } BOOL ret = YES; if ([_webViewProxyDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) { ret = [_webViewProxyDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType]; } BOOL isFragmentJump = NO; if (request.URL.fragment) { NSString *nonFragmentURL = [request.URL.absoluteString stringByReplacingOccurrencesOfString:[@"#" stringByAppendingString:request.URL.fragment] withString:@""]; isFragmentJump = [nonFragmentURL isEqualToString:webView.request.URL.absoluteString]; } BOOL isTopLevelNavigation = [request.mainDocumentURL isEqual:request.URL]; BOOL isHTTP = [request.URL.scheme isEqualToString:@"http"] || [request.URL.scheme isEqualToString:@"https"]; if (ret && !isFragmentJump && isHTTP && isTopLevelNavigation) { _currentURL = request.URL; [self reset]; } return ret; } - (void)webViewDidStartLoad:(UIWebView *)webView { if ([_webViewProxyDelegate respondsToSelector:@selector(webViewDidStartLoad:)]) { [_webViewProxyDelegate webViewDidStartLoad:webView]; } _loadingCount++; _maxLoadCount = fmax(_maxLoadCount, _loadingCount); [self startProgress]; } - (void)webViewDidFinishLoad:(UIWebView *)webView { if ([_webViewProxyDelegate respondsToSelector:@selector(webViewDidFinishLoad:)]) { [_webViewProxyDelegate webViewDidFinishLoad:webView]; } _loadingCount--; [self incrementProgress]; NSString *readyState = [webView stringByEvaluatingJavaScriptFromString:@"document.readyState"]; BOOL interactive = [readyState isEqualToString:@"interactive"]; if (interactive) { _interactive = YES; NSString *waitForCompleteJS = [NSString stringWithFormat:@"window.addEventListener('load',function() { var iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = '%@://%@%@'; document.body.appendChild(iframe); }, false);", webView.request.mainDocumentURL.scheme, webView.request.mainDocumentURL.host, completeRPCURLPath]; [webView stringByEvaluatingJavaScriptFromString:waitForCompleteJS]; } BOOL isNotRedirect = _currentURL && [_currentURL isEqual:webView.request.mainDocumentURL]; BOOL complete = [readyState isEqualToString:@"complete"]; if (complete && isNotRedirect) { [self completeProgress]; } } - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { if ([_webViewProxyDelegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) { [_webViewProxyDelegate webView:webView didFailLoadWithError:error]; } _loadingCount--; [self incrementProgress]; NSString *readyState = [webView stringByEvaluatingJavaScriptFromString:@"document.readyState"]; BOOL interactive = [readyState isEqualToString:@"interactive"]; if (interactive) { _interactive = YES; NSString *waitForCompleteJS = [NSString stringWithFormat:@"window.addEventListener('load',function() { var iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = '%@://%@%@'; document.body.appendChild(iframe); }, false);", webView.request.mainDocumentURL.scheme, webView.request.mainDocumentURL.host, completeRPCURLPath]; [webView stringByEvaluatingJavaScriptFromString:waitForCompleteJS]; } BOOL isNotRedirect = _currentURL && [_currentURL isEqual:webView.request.mainDocumentURL]; BOOL complete = [readyState isEqualToString:@"complete"]; if (complete && isNotRedirect) { [self completeProgress]; } } #pragma mark - #pragma mark Method Forwarding // for future UIWebViewDelegate impl - (BOOL)respondsToSelector:(SEL)aSelector { if ( [super respondsToSelector:aSelector] ) return YES; if ([self.webViewProxyDelegate respondsToSelector:aSelector]) return YES; return NO; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { NSMethodSignature *signature = [super methodSignatureForSelector:selector]; if(!signature) { if([_webViewProxyDelegate respondsToSelector:selector]) { return [(NSObject *)_webViewProxyDelegate methodSignatureForSelector:selector]; } } return signature; } - (void)forwardInvocation:(NSInvocation*)invocation { if ([_webViewProxyDelegate respondsToSelector:[invocation selector]]) { [invocation invokeWithTarget:_webViewProxyDelegate]; } } @end