Progress bar (and network activity status) for UIWebView

走远了吗. 提交于 2019-12-21 06:25:30

问题


After a long break I've started doing ios development again. With that in mind I downloaded the latest version of XCode (5.02) and set about coding an app with ios7 as the target. Everything was going fine until I added a "webView page" to my app. I was naively expecting UIWebView to work just like a page in Safari, but it doesn't. The most obvious thing missing is that there's absolutely no feedback to the user while the page is loading. i.e. the network activity indicator doesn't appear at all and there's no progress bar displayed as the page loads. So I figured you just have to add those manually. That shouldn't be too hard, should it?

Several days later and I've got it working quite well but I'm not 100% happy with the result. It took quite a bit of debugging and experimenting to get where I am now. I guess my question is "Is there anything that can be done to improve my approach?" and, also, perhaps, "Should I report the loading and request property issues to Apple and ask them to fix them"?

Initially I looked on the internet for solutions to this problem and there are quite a few questions on stackoverflow about it, but it appears that nobody has really cracked it. Some of the suggested approaches:

  1. Use a private UIWebView API (no good if you want to release your app to the store)
  2. Download the page using NSURLConnection (gives progress for the initial page download only)
  3. Use a 3rd party library for downloading and processing the page (sledgehammer to crack a nut?)
  4. Use a UIWebViewDelegate and keep a track of the load starts and finishes
  5. Use a UIWebViewDelegate to manually show and hide the network activity indicator

In the end I decided to use 4. and 5. but it's tricky because:

  1. The start and finish calls can come in any order
  2. Some requests start but never finish
  3. Some requests complete with an error

The first problem can be solved by counting the starts and finishes, by making sure your calculated progress percentage only increases, and by only turning off the network indicator when the last request is completed.

The second problem is more difficult to solve. The UIWebView has two properties that in principle can be used to do it (loading and request), but they seem to work in mysterious ways. Some answers and comments I found on here suggested that the loading property is never updated but the request one is. This initially made sense to me because pages never really stop loading (they may have dynamic content), so it made sense that the loading property would only tell you if the web view was actively loading content (i.e. if loadRequest has been called and stopLoading hasn't).

However, on ios7 at least, it actually works in the opposite way. The request property is never updated, so you can't use the request property to determine if the view has finished loading everything (i.e. by checking if the request that has finished is the initial request). The loading property is updated though and it does get set to NO when the page has fully loaded. This helps a lot, but I've noticed that dynamic content (e.g. page ads) causes loading to be reset back to YES, so it's still a bit tricky to deal with.

The third problem wouldn't be an issue except I've observed that inside webView:didFailLoadWithError the loading property is always set to NO. This appears to be a temporary state, however, and if you want you can ignore errors and wait for loading to be set to NO in webViewDidFinishLoad. Since the request property isn't updated it's impossible to know if it's the initial request that has failed or one of the sub-requests (that you may or may not care about). So I've decided to go with a solution that allows the programmer to choose the behaviour they want.

Things would be easier if:

  1. the request property was updated (or the current request was passed in, like it is in webView:shouldStartLoadWithRequest:navigationType)
  2. the loading property was set correctly in webView:didFailLoadWithError

Here's my code inside the view controller for the webView:

- (void)resetProgress {
    framesToLoad = 0;
    framesLoaded = 0;
    currentProgress = 0.0f;
}

- (void)resetForNewPage {
    // Reset progress
    [self resetProgress];

    // Monitor the page load
    monitorProgress = YES;

    // Keep going if errors occur
    // completeIfError = NO;

    // Stop updates if an error occurs
    completeIfError = YES;
}

- (void)webViewLoaded {
    [self resetProgress];
    [entryProgressView setProgress: 0.0f animated: YES];
}

- (void)viewDidLoad {
    [super viewDidLoad];

    // Reset state for new page load
    [self resetForNewPage];

    // Load the page
    NSURLRequest *entryRequest = [NSURLRequest requestWithURL:entryURL];
    [entryWebView loadRequest:entryRequest];
}

-(void)viewWillDisappear:(BOOL)animated {
    [entryWebView stopLoading];

    entryWebView.delegate = nil;
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    if (navigationType == UIWebViewNavigationTypeLinkClicked) {
        // Reset state for new page load
        [self resetForNewPage];
    }

    return YES;
}

-(void)webViewDidStartLoad:(UIWebView *)webView {
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
    if (!monitorProgress) {
        return;
    }

    // Increment frames to load counter
    framesToLoad++;
}

-(void)webViewDidFinishLoad:(UIWebView *) webView {
    if (!monitorProgress) {
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
        return;
    }

    // Increment frames loaded counter
    framesLoaded++;

    // Update progress display
    float newProgress = ((float) framesLoaded) / framesToLoad;
    if (newProgress > currentProgress) {
        currentProgress = newProgress;
        [entryProgressView setProgress: newProgress animated: YES];
    }

    // Finish progress updates if loading is complete
    if (!webView.loading) {
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
        monitorProgress = NO;

        [entryProgressView setProgress: 1.0 animated: YES];
        [self performSelector:@selector(webViewLoaded) withObject: nil afterDelay: 1.0];
    }
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    if (!monitorProgress) {
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
        return;
    }

    // Increment frames loaded counter
    framesLoaded++;

    // Update progress display
    float newProgress = ((float) framesLoaded) / framesToLoad;
    if (newProgress > currentProgress) {
        currentProgress = newProgress;
        [entryProgressView setProgress: newProgress animated: YES];
    }

    // Finish progress updates if required
    if (completeIfError) {
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
        monitorProgress = NO;

        [entryProgressView setProgress: 1.0 animated: YES];
        [self performSelector:@selector(webViewLoaded) withObject: nil afterDelay: 1.0];
    }
}

来源:https://stackoverflow.com/questions/21224170/progress-bar-and-network-activity-status-for-uiwebview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!