UIWebView - 如何识别“最后一个”webViewDidFinishLoad消息?

23
webViewDidFinishLoad消息似乎每次加载页面中的任何对象时都会发送。有没有办法确定所有内容的加载都已完成?

1
这个问题的被接受答案和更新最多的答案都是不完整的。看看这个帖子,但忽略那个只是重定向到这个帖子的被接受答案。https://dev59.com/emgu5IYBdhLWcg3w5LBa#10998856?noredirect=1#comment35581055_10998856 - Gruntcakes
10个回答

61

我猜测iframes会引起webViewDidStartLoad/webViewDidFinishLoad 这一对方法的调用。

一个回答中提到的[webView isLoading]检查对我不起作用;即使在两次webViewDidFinishLoad中的第一个调用后,它也返回了false。相反,我通过以下方式跟踪加载:

- (void)webViewDidStartLoad:(UIWebView *)webView {
  webViewLoads_++;
}


- (void)webViewDidFinishLoad:(UIWebView *)webView {
  webViewLoads_--;

  if (webViewLoads_ > 0) {
    return;
  }

  …
}

- (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error {
  webViewLoads_--;
}

(请注意,这仅在开始/完成对不是连续出现时起作用,但根据我的经验,到目前为止尚未发生。)


2
@phopkins 加上这个吧 :) (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error { -- webViewLoads_; } - user692463
4
我讨厌通过诸如这样的愚蠢变通方法来解决问题 - 但这仍然比被接受的答案更好,因为那个答案涉及使用私有的苹果API。感谢你的回答。 - nont
3
对我没有用。挂起计数器在完成过程中几次降至零。我正在同时下载多个不同的UIWebView。 - Bill Cheswick
这个方法非常好用(只要使用所有三个委托方法);现在许多网站似乎都有10-20个以上的嵌入式iframe调用(分享按钮等),ajax调用等,而这种跟踪方法可以帮助我的webViewDidFinishLoad代码只运行一两次,而不是10多次。谢谢! - geerlingguy
4
和 Ches 一样。这对我完全没用。叹气。 - Joe D'Andrea
显示剩余3条评论

19

检查这个,它一定适合你使用:

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    if ([[webView stringByEvaluatingJavaScriptFromString:@"document.readyState"] isEqualToString:@"complete"]) {
        // UIWebView object has fully loaded.
    }
}

很棒!非常正规,而且准确地检查了我们想要的状态。 - Jonathan Zhan
6
不幸的是,这并非没有问题。有时webViewDidFinishLoad:方法会因为某个资源下载完成而触发,但此时渲染器尚未构建DOM / 加载Javascript等,因此文档的readyState尚未等于“complete”。解决方案可能是在第一个webViewDidFinishLoad:调用中启动一个重复定时器,每0.5秒左右评估一次document.readyState。然而,这显然不是一个优雅的解决方案。 - Oliver Pearmain
对我没用。我的状态始终是交互式的,从未完成。 - Alok C
完美无缺,是唯一有效的解决方案!这应该被接受为答案。 - Mohammad Zekrallah
使用了NSTimer解决方案(间隔为0.1)。虽然不太好看,但能胜任工作。将代码放在单独的答案中,以便像我这样懒得打字的人可以轻松复制粘贴 :) - codrut

9
有趣,我没想到它会这样工作。虽然我相信还有其他方法可以实现(是否有一种方法从webViewDidFinishLoad消息中提取URL,以便您可以看到哪个是主页面完成加载?),但我能想到的主要方法是使用estimatedProgress来检查页面的进度,并在100%完成加载时触发任何您想要执行的操作,这就是我在我的应用程序中所做的。谷歌“iphone webview estimatedprogress”,并单击第一个链接以获取我撰写的指南。
更新:
请使用phopkins的答案,而不是我的!在您的应用程序中使用私有API是一个坏主意,您可能会被拒绝,他的解决方案是正确的。

我在我的一个应用程序中遇到了类似的问题,并实施了AriX的解决方案,但我的问题是,苹果是否真的允许这样做进入应用商店?其中一个评论似乎表明他们不允许(至少在2009年没有)。有人最近有过更多的经验吗? - Jon
2
我相信苹果曾经允许这样的应用进入他们的商店,但我可以确认他们现在不再允许了。我在去年11月份提交了一个使用这个API的应用,但被拒绝了。为什么四年后iPhone SDK发布后,苹果仍然不允许开发者访问 web view 的进度呢?谁知道呢。 - AriX
我的应用程序因为使用了非公开的"_documentView"而被AppStore拒绝了。要小心哦 :-) - Tsuneo Yoshioka
1
在谷歌上搜索“iphone webview estimatedprogress”会将您带回到此页面。我不认为您真正考虑过链接策略。 :) - Wayne

1

所有选项对我的使用情况都不太适用。我使用了 phopkins 的示例,稍作修改。我发现,如果加载到 WebView 中的 HTML 包含一个图像,则会发生单独的请求,因此我们必须考虑这一点,并使用定时器来解决。这不是最好的解决方案,但似乎可以工作。

- (void)webViewActuallyFinished {
     _webViewLoads--;

    if (_webViewLoads > 0) {
        return;
    }

    //Actually done loading

}

- (void)webViewDidStartLoad:(UIWebView *)webView {
    _webViewLoads++;
}


- (void)webViewDidFinishLoad:(UIWebView *)webView {


    [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(webViewActuallyFinished) userInfo:nil repeats:NO];
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
  _webViewLoads--;
}

这对我有效。我尝试使用JavaScript的readyState但没有成功。我不喜欢使用计时器,但它可以用。如果webView的didFinishLoad实际上是didFinishLoad,那么就不需要这个了。 - Jacob Boyd

1
我需要从页面中捕获一个尚未完全加载的变量。
这对我有用:
```javascript // 翻译代码 ```
- (void) webViewDidFinishLoad:(UIWebView *)webView {
    NSString *valueID = [webView stringByEvaluatingJavaScriptFromString:@"document.valueID;"];

    if([valueID isEqualToString:@""]){
        [webView reload];
        return;
    }

    //page loaded
}

1

另一种在较少控制下监视负载进度的方法是观察WebViewProgressEstimateChangedNotification、WebViewProgressFinishedNotification和WebViewProgressStartedNotification通知。例如,您可以观察这些通知以在应用程序中实现简单的进度指示器。您可以通过调用estimatedProgress方法来更新进度指示器,以获取当前已加载内容的估计量。

来自http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/WebKit/Classes/WebView_Class/Reference/Reference.html


1

您还可以使用我编写的这个简短的类别,将块添加到UIWebView中,并让您选择默认UIWebView行为(在每个对象加载后得到通知),或“预期”的行为(仅在页面完全加载后得到通知)。

https://github.com/freak4pc/UIWebView-Blocks


这对我来说无法正常工作,并发出了一个信号(我相信是SIGTRAP)。尽管如此,我阅读了代码并在我的代码中使用了它的逻辑,然后它正常运行了。 - Pedro Rolo
1
很奇怪,我这边可以正常运行,不确定你的确切用法是什么,如果你想的话随时可以在这里写下来。干杯。 - Shai Mishali
如果有任何问题,请随意拉取您的副本并添加修复 :) - Shai Mishali

0

这是我用来在DOM加载时显示旋转器的代码,基于@Vinod的答案构建。

请注意,在webViewDidStartLoadwebViewDidFinishLoad之间,readyState从上一个请求(如果有)中变为completed,因此轮询应该在webViewDidFinishLoad之后开始。

readyState可能的值为loadinginteractivecompleted(也许还有nil?)

- (void)webViewDidStartLoad:(UIWebView *)webView {
    [self spinnerOn];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    [self startDOMCompletionPolling];
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    [self startDOMCompletionPolling];
}


- (void)startDOMCompletionPolling {
    if (self.domCompletionListener) {
        [self.domCompletionListener invalidate];
    }
    self.domCompletionListener = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(checkDOMCompletion) userInfo:nil repeats:YES];
}

- (void)checkDOMCompletion {
    NSString *readyState = [self.webView stringByEvaluatingJavaScriptFromString:@"document.readyState"];

    if (readyState != nil) {
        if (readyState.length > 0) {
            if ([readyState isEqualToString:@"loading"]) { //keep polling
                return;
            }
        }
    }
    //'completed', 'interactive', nil, others -> hide spinner
    [self.domCompletionListener invalidate];
    [self spinnerOff];
}

0

嗨,可能有点远古了,但我希望这可以帮到你。

我刚刚在进入webViewDidFinishLoad方法时使用了一个通知,以便我可以捕获该方法的一个实例,然后我将检测通知并从那里执行我的逻辑。

希望这可以帮助你。它不会捕获webViewDidFinishLoad方法的最后一个调用实例,但允许您在调用特定方法后执行某些操作(例如显示按钮)而不重复调用该特定方法。

*********编辑*********

我进行了测试并成功测试了它。它实际上是有效的,并且从通知中调用的方法将在完整页面加载后调用。

另外,我认为您可以在委托方法webViewDidStartLoad和webViewDidFinishLoad上进行计数,以确保在运行代码之前它们是相同的。不过,这是一种丑陋的hack,因为我们永远不知道它会被调用多少次,除非像我一样,您正在加载一个HTML源并可以添加JavaScript来检查您正在加载的页面上有多少元素。我只是分享了一些我尝试解决此问题的方法。希望能有所帮助。

鼓励反馈。谢谢!


将代码放置在webViewDidFinishLoad和在webViewDidFinishLoad中创建通知来调用另一个方法之间有何区别? - Aaron Brager
取决于你的代码实际要做什么。通知会触发另一堆代码,所以如果你已经有一个需要单独或并行运行的方法或其他东西,那么最好调用另一个方法。否则,我想这真的取决于你的代码要做什么。 - faterpig

-7

我的做法是这样的:

- (void)webViewDidFinishLoad:(UIWebView *)webview  {
    if (webview.loading)
    return;
    // now really done loading code goes next
    [logic]
}

1
它绝对不起作用。每次调用“webViewDidFinishLoad”时都是错误的。但是,它仍然被多次调用。是的,有2个开始和3个结束。例如:开始加载--文章23个理由走出去1,开始加载--文章23个理由走出去1,完成加载--文章23个理由走出去0,完成加载--文章23个理由走出去0,完成加载--文章23个理由走出去0。 - christophercotton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接