【问题标题】:UIWebView: when did a page really finish loading?UIWebView:页面何时真正完成加载?
【发布时间】:2012-06-15 07:09:11
【问题描述】:

我需要知道,当 UIWebView 完全加载网页时。我的意思是,当所有重定向都完成并且动态加载的内容准备好时。 我尝试注入javascript(查询document.readyState == 'complete'),但这似乎不太可靠。

是否有来自私有 api 的事件会给我带来结果?

【问题讨论】:

  • 我认为您不需要私有 API。请参阅下面的答案...
  • 我还添加了一个确实使用私有框架的答案,但仍然可能不会让您被 App Store 拒绝。无论如何,我建议仅在 webViewDidFinishLoad 方法不起作用时才使用estimatedProgress 方法...
  • 您说您尝试注入 javascript,但它似乎并不可靠。根据我的经验,您在哪里/如何注入代码,何时调用该代码,何时以及如何调用 document.readyState?
  • 唯一可行的解​​决方案(我发现):stackoverflow.com/questions/1662565/…

标签: iphone uiwebview


【解决方案1】:

我一直在寻找这个问题的答案,我从 Sebastian 的问题中得到了这个想法。不知何故,它对我有用,也许对遇到此问题的人有用。

我为 UIWebView 设置了一个委托,在 webViewDidFinishLoad 中,我通过执行 Javascript 来检测 webview 是否真的完成了加载。

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

【讨论】:

  • 如果您的答案有效,这似乎是我在堆栈溢出中看到的最佳答案。但是在它出现的 3 年里没有人投票支持这个答案,所以我很怀疑......我会试一试。
  • 太棒了!但是不要忘记在标题中添加 UIWebViewDelegate,在定义 UIWebView 的实现文件中添加 [webViewImage setDelegate:self]
  • 尝试加载以下 URL 时这对我不起作用:imgur.com/A9dlJpQ
  • 对于所有六个“OnPageFinished”调用,状态已完成。所以它不适用于我检测最后一个页面加载事件的方法。
  • 据我了解,webViewDidFinishLoad 只在每次页面加载时被调用一次。因此,如果当 UIWebView 认为加载完成时 document.readyState 不是“完成”。这个委托方法永远不会被再次调用。所以本质上,在这种情况下,我们在 if 块中得到了死代码。
【解决方案2】:

我的解决方案:

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    if (!webView.isLoading) {
        // Tada
    }
}

【讨论】:

  • 这对我来说似乎非常有效。为什么所有复杂的解决方案?我错过了什么吗?
  • 完美?有人能告诉我这比@fedry 的回答更糟吗
  • 我自己已经使用过几次该解决方案并且“它确实有效”。不是更糟,而是更好,因为它不涉及评估 javascript。
  • 不。 isLoading 在真假之间随机变化。
  • UIWebView.isLoading 在加载大量iframes 时有时不可靠或发生错误。
【解决方案3】:

UIWebView 执行 Javascript 的能力与页面是否已加载无关。它可能使用 stringByEvaluatingJavaScriptFromString: 来执行 Javascript,甚至在您首先调用在 UIWebView 中加载页面之前,或者根本不加载页面。

因此我无法理解如何接受此链接中的答案:webViewDidFinishLoad: Firing too soon? 因为调用 any Javascript 根本不会告诉你任何事情(如果他们正在调用一些有趣的 Javascript,例如监控 dom,他们不会提及,如果有的话会因为它很重要)。

您可以调用一些 Javascript,例如,检查 dom 的状态或已加载页面的状态,并将其报告给调用代码,但是如果它报告该页面尚未加载,那么您该怎么办做? - 稍后您将不得不再次调用它,但要多久,何时,何地,如何,多久,...... 轮询通常不是解决任何问题的好方法。

知道页面何时完全加载并准确并控制确切知道其中内容的唯一方法是自己执行此操作 - 将 JavaScript 事件侦听器附加到正在加载的页面并让它们调用您的 shouldStartLoadWithRequest:带有一些专有网址。例如,您可以创建一个 JS 函数来侦听窗口加载或 dom 就绪事件等,具体取决于您是否需要知道页面何时加载,或者是否只是 dom 已加载等。取决于您的需要。

如果网页不是您的,那么您可以将此 javascript 放入一个文件并将其注入您加载的每个页面。

如何创建 javascript 事件侦听器是标准 javascript,与 iOS 无关,例如这里是用于检测 dom 何时以可从 UIWebView 中注入加载页面的表单加载的 JavaScript 和然后调用 shouldStartLoadWithRequest: (这将调用 shouldStartLoadWithRequestwhen: dom 已完成加载,即在显示完整页面内容之前,检测这一点只需更改事件侦听器)。

var script = document.createElement('script');  
script.type = 'text/javascript';  
script.text = function DOMReady() {
    document.location.href = "mydomain://DOMIsReady";
}

function addScript()
{        
    document.getElementsByTagName('head')[0].appendChild(script);
    document.addEventListener('DOMContentLoaded', DOMReady, false);
}
addScript();

【讨论】:

【解决方案4】:

Martin H 的答案在正确的轨道上,但它仍然可以被多次解雇。

您需要做的是向页面添加一个 Javascript onLoad 侦听器,并跟踪您是否已经插入了该侦听器:

#define kPageLoadedFunction @"page_loaded"
#define kPageLoadedStatusVar @"page_status"
#define kPageLoadedScheme @"pageloaded"

- (NSString*) javascriptOnLoadCompletionHandler {
    return [NSString stringWithFormat:@"var %1$@ = function() {window.location.href = '%2$@:' + window.location.href; window.%3$@ = 'loaded'}; \
    \
    if (window.attachEvent) {window.attachEvent('onload', %1$@);} \
    else if (window.addEventListener) {window.addEventListener('load', %1$@, false);} \
    else {document.addEventListener('load', %1$@, false);}",kPageLoadedFunction,kPageLoadedScheme,kPageLoadedStatusVar];
}

- (BOOL) javascriptPageLoaded:(UIWebView*)browser {
    NSString* page_status = [browser stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"window.%@",kPageLoadedStatusVar]];
    return [page_status isEqualToString:@"loaded"];
}


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

    if(!_js_load_indicator_inserted && ![self javascriptPageLoaded:browser]) {
        _js_load_indicator_inserted = YES;
        [browser stringByEvaluatingJavaScriptFromString:[self javascriptOnLoadCompletionHandler]];
    } else
        _js_load_indicator_inserted = NO;
}

页面加载完成后,Javascript 监听器将触发 URL pageloaded:<URL that actually finished> 的新页面加载。然后你只需要更新 shouldStartLoadWithRequest 来查找那个 URL 方案:

- (BOOL)webView:(UIWebView *)browser shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    if([request.URL.scheme isEqualToString:kPageLoadedScheme]){
        _js_load_indicator_inserted = NO;
        NSString* loaded_url = [request.URL absoluteString];
        loaded_url = [loaded_url substringFromIndex:[loaded_url rangeOfString:@":"].location+1];

        <DO STUFF THAT SHOULD HAPPEN WHEN THE PAGE LOAD FINISHES.>

        return NO;
    }
}

【讨论】:

  • 这对我有用。我为处理非 HTML 页面做了一个调整。我发现如果 Web 视图正在加载非 HTML 文档,例如 PDF,webViewDidFinishLoad: 仅被调用一次,并且在 webViewDidFinishLoad: 中安装 Javascript 加载完成处理程序为时已晚,无法触发 page_loaded 函数。我需要在 webViewDidStartLoad: 中安装加载完成处理程序 Javascript,以便在 webViewDidFinishLoad: 被称为第一个(也是唯一一个)时间 page_loaded 已经存在并且可以触发标记页面加载时。
  • _js_load_indicator_inserted 在哪里定义和设置?我没有在答案中看到这一点,我不知道如何在没有它的情况下使用它。
  • 目前我定义如下:BOOL _js_load_indicator_inserted = NO;,但是当webView:shouldStartLoadWithRequest:navigationType: 被调用时,request.URL.scheme 永远不会等于 kPageLoadedScheme (@"pageloaded")。
【解决方案5】:

当 DOM 的 readyState 为 interactive 时,WKWebView 调用 didFinish 委托,这对于加载了 javascript 的网页来说还为时过早。我们需要等待complete 状态。我已经创建了这样的代码:

func waitUntilFullyLoaded(_ completionHandler: @escaping () -> Void) {
    webView.evaluateJavaScript("document.readyState === 'complete'") { (evaluation, _) in
        if let fullyLoaded = evaluation as? Bool {
            if !fullyLoaded {
                DDLogInfo("Webview not fully loaded yet...")
                DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
                    self.waitUntilFullyLoaded(completionHandler)
                })
            } else {
                DDLogInfo("Webview fully loaded!")
                completionHandler()
            }
        }
    }
}

然后:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    DDLogInfo("Webview claims that is fully loaded")

    waitUntilFullyLoaded { [weak self] in
        guard let unwrappedSelf = self else { return }

        unwrappedSelf.activityIndicator?.stopAnimating()
        unwrappedSelf.isFullLoaded = true
    }
}

【讨论】:

    【解决方案6】:

    您也可以在这里使用接受的答案:UIWebView - How to identify the "last" webViewDidFinishLoad message?...

    基本上,使用estimatedProgress 属性,当达到100 时,页面已完成加载...请参阅this guide 以获取帮助。

    虽然它确实使用了私有框架,但根据the guide 的说法,App Store 中有一些应用程序使用它......

    【讨论】:

    • 是的,我试过了,这就是我想要的。虽然我不太喜欢做这些骇人听闻的事情来收集如此正常的信息。或网页的下载状态(我通过私有 api 检索到,如您的第二个链接中所述)
    • 我的 iOS 应用程序由于使用了“_documentView”API(获取估计进度所必需的)而被拒绝。
    • @TsuneoYoshioka 是的 - 正如我在回答中所说,_documentView API 是私有的,会导致您的应用被拒绝。
    • 我不知道这如何成为公认的答案。正确答案来自 Martin H。
    【解决方案7】:

    让您的视图控制器成为 UIWebView 的代表。 (您可以在 Interface Builder 中执行此操作,也可以在 viewDidLoad 方法中使用此代码:

    [self.myWebView setDelegate:self];
    

    然后使用此处接受的答案中描述的方法:webViewDidFinishLoad: Firing too soon?

    代码:

    -(void) webViewDidFinishLoad:(UIWebView *)webView
    {
        NSString *javaScript = @"function myFunction(){return 1+1;}";
        [webView stringByEvaluatingJavaScriptFromString:javaScript];
    
        //Has fully loaded, do whatever you want here
    }
    

    【讨论】:

    • 我想,那不是我要找的那个。你后来的回答成功了:)
    • 我不明白这是如何工作的。
    • 没用。我的 html 中有 4 个 iframe,这是 4 次造成的。
    【解决方案8】:

    多亏了 JavaScript,新资源和新图形可以永远加载——确定加载何时“完成”,对于一个足够变态的页面,相当于找到了halting problem 的解决方案。您真的不知道页面何时加载。

    如果可以的话,只显示页面或它的图像,并使用 webViewDidLoad 更新图像,并让用户决定何时切断 - 或不切断。

    【讨论】:

    • 反对票的任何提示?还是有人只是为错误的答案辩护?
    【解决方案9】:

    这是@jasonjwwilliams 答案的简化版本。

    #define JAVASCRIPT_TEST_VARIBLE @"window.IOSVariable"
    #define JAVASCRIPT_TEST_FUNCTION @"IOSFunction"
    #define LOAD_COMPLETED @"loaded"
    
    // Put this in your init method
    // In Javascript, first define the function, then attach it as a listener.
    _javascriptListener = [NSString stringWithFormat:
        @"function %@() {\
             %@ = '%@';\
        };\
        window.addEventListener(\"load\", %@, false);",
            JAVASCRIPT_TEST_FUNCTION, JAVASCRIPT_TEST_VARIBLE, LOAD_COMPLETED, JAVASCRIPT_TEST_FUNCTION];
    
    // Then use this:
    
    - (void)webViewDidFinishLoad:(UIWebView *)webView;
    {
        // Only want to attach the listener and function once.
        if (_javascriptListener) {
            [_webView stringByEvaluatingJavaScriptFromString:_javascriptListener];
            _javascriptListener = nil;
        }
    
        if ([[webView stringByEvaluatingJavaScriptFromString:JAVASCRIPT_TEST_VARIBLE] isEqualToString:LOAD_COMPLETED]) {
            NSLog(@"UIWebView object has fully loaded.");
        }
    }
    

    【讨论】:

    • 实际上,在进一步的测试中,我发现这种方法也不完全准确。有时,在我的测试中(加载 cnn.com),输出“UIWebView 对象已完全加载”。不止一次发生!
    【解决方案10】:

    由于所有这些答案都是旧的,并且webViewDidFinishLoad 已被贬值,后来让我解释了如何准确知道页面何时在 swift 4.2 的 webkit (webview) 中完成加载

    class ViewController: UIViewController, WKNavigationDelegate {
    
        @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
    
        override func viewDidLoad() {
    
            webView.navigationDelegate = self
    
        }
    
        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    
        // it is done loading, do something!
    
        }
    
    }
    

    当然,我遗漏了一些其他默认代码,因此您要添加到现有函数中。

    【讨论】:

      【解决方案11】:

      尝试类似的东西 https://github.com/Buza/uiwebview-load-completion-tracker 它帮助我加载了 Google 街景

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-12-26
        • 2014-08-15
        • 2012-05-24
        • 1970-01-01
        • 1970-01-01
        • 2010-12-12
        相关资源
        最近更新 更多