Skip to content

Commit

Permalink
long-awaited arrival of a loading progress bar
Browse files Browse the repository at this point in the history
  • Loading branch information
mtigas committed Feb 1, 2014
1 parent 485c60a commit bc0e094
Show file tree
Hide file tree
Showing 10 changed files with 436 additions and 8 deletions.
26 changes: 26 additions & 0 deletions LICENSE
Expand Up @@ -222,6 +222,32 @@ freely, subject to the following restrictions:
3. This notice may not be removed or altered from any source
distribution.

----------
Contains NJKWebViewProgress
https://github.com/ninjinkun/NJKWebViewProgress

The MIT License (MIT)

Copyright (c) 2013 Satoshi Asano

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

----------
OpenSSL is not bundled in the source tree of this application, but is required
to compile the application. NOTE: Binary forms of this application are subject
Expand Down
21 changes: 21 additions & 0 deletions OnionBrowser/NJKWebViewProgress/LICENSE
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2013 Satoshi Asano

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
31 changes: 31 additions & 0 deletions OnionBrowser/NJKWebViewProgress/NJKWebViewProgress.h
@@ -0,0 +1,31 @@
//
// NJKWebViewProgress.h
//
// Created by Satoshi Aasano on 4/20/13.
// Copyright (c) 2013 Satoshi Asano. All rights reserved.
//

#import <Foundation/Foundation.h>

#undef njk_weak
#if __has_feature(objc_arc_weak)
#define njk_weak weak
#else
#define njk_weak unsafe_unretained
#endif

typedef void (^NJKWebViewProgressBlock)(float progress);
@protocol NJKWebViewProgressDelegate;
@interface NJKWebViewProgress : NSObject<UIWebViewDelegate>
@property (nonatomic, njk_weak) id<NJKWebViewProgressDelegate>progressDelegate;
@property (nonatomic, njk_weak) id<UIWebViewDelegate>webViewProxyDelegate;
@property (nonatomic, copy) NJKWebViewProgressBlock progressBlock;
@property (nonatomic, readonly) float progress; // 0.0..1.0

- (void)reset;
@end

@protocol NJKWebViewProgressDelegate <NSObject>
- (void)webViewProgress:(NJKWebViewProgress *)webViewProgress updateProgress:(float)progress;
@end

193 changes: 193 additions & 0 deletions OnionBrowser/NJKWebViewProgress/NJKWebViewProgress.m
@@ -0,0 +1,193 @@
//
// NJKWebViewProgress.m
//
// Created by Satoshi Aasano on 4/20/13.
// Copyright (c) 2013 Satoshi Asano. All rights reserved.
//

#import "NJKWebViewProgress.h"

NSString *completeRPCURL = @"webviewprogressproxy:///complete";

static const float initialProgressValue = 0.1;
static const float beforeInteractiveMaxProgressValue = 0.5;
static const float afterInteractiveMaxProgressValue = 0.9;

@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 < initialProgressValue) {
[self setProgress:initialProgressValue];
}
}

- (void)incrementProgress
{
float progress = self.progress;
float maxProgress = _interactive ? afterInteractiveMaxProgressValue : beforeInteractiveMaxProgressValue;
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.absoluteString isEqualToString:completeRPCURL]) {
[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);", completeRPCURL];
[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);", completeRPCURL];
[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

- (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
21 changes: 21 additions & 0 deletions OnionBrowser/NJKWebViewProgress/NJKWebViewProgressView.h
@@ -0,0 +1,21 @@
//
// NJKWebViewProgressView.h
// iOS 7 Style WebView Progress Bar
//
// Created by Satoshi Aasano on 11/16/13.
// Copyright (c) 2013 Satoshi Asano. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface NJKWebViewProgressView : UIView
@property (nonatomic) float progress;

@property (nonatomic) UIView *progressBarView;
@property (nonatomic) NSTimeInterval barAnimationDuration; // default 0.1
@property (nonatomic) NSTimeInterval fadeAnimationDuration; // default 0.27
@property (nonatomic) NSTimeInterval fadeOutDelay; // default 0.1

- (void)setProgress:(float)progress animated:(BOOL)animated;

@end
64 changes: 64 additions & 0 deletions OnionBrowser/NJKWebViewProgress/NJKWebViewProgressView.m
@@ -0,0 +1,64 @@
//
// NJKWebViewProgressView.m
//
// Created by Satoshi Aasanoon 11/16/13.
// Copyright (c) 2013 Satoshi Asano. All rights reserved.
//

#import "NJKWebViewProgressView.h"

@implementation NJKWebViewProgressView

- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.userInteractionEnabled = NO;
self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
_progressBarView = [[UIView alloc] initWithFrame:self.bounds];
_progressBarView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
UIColor *tintColor = [UIColor colorWithRed:22.f / 255.f green:126.f / 255.f blue:251.f / 255.f alpha:1.0]; // iOS7 Safari bar color
if ([UIApplication.sharedApplication.delegate.window respondsToSelector:@selector(setTintColor:)]) {
tintColor = UIApplication.sharedApplication.delegate.window.tintColor;
}
_progressBarView.backgroundColor = tintColor;
[self addSubview:_progressBarView];

_barAnimationDuration = 0.27f;
_fadeAnimationDuration = 0.27f;
_fadeOutDelay = 0.1f;
}
return self;
}

-(void)setProgress:(float)progress
{
[self setProgress:progress animated:NO];
}

- (void)setProgress:(float)progress animated:(BOOL)animated
{
BOOL isGrowing = progress > 0.0;
[UIView animateWithDuration:(isGrowing && animated) ? _barAnimationDuration : 0.0 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
CGRect frame = _progressBarView.frame;
frame.size.width = progress * self.bounds.size.width;
_progressBarView.frame = frame;
} completion:nil];

if (progress >= 1.0) {
[UIView animateWithDuration:animated ? _fadeAnimationDuration : 0.0 delay:_fadeOutDelay options:UIViewAnimationOptionCurveEaseInOut animations:^{
_progressBarView.alpha = 0.0;
} completion:^(BOOL completed){
CGRect frame = _progressBarView.frame;
frame.size.width = 0;
_progressBarView.frame = frame;
}];
}
else {
[UIView animateWithDuration:animated ? _fadeAnimationDuration : 0.0 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
_progressBarView.alpha = 1.0;
} completion:nil];
}
}

@end

0 comments on commit bc0e094

Please sign in to comment.