带有进度条的UIWebView

24

嗨,我是新手程序员,尝试在Xcode上制作我的第一个iPhone应用程序。 我的应用包含一个按钮,按下它会打开一个UIWebView并加载主页。 现在我想添加一个进度视图到WebView中,就像Safari一样使用,它可以显示加载页面的进度。我该怎么做呢?
到目前为止,我加载UIWebView中URL的代码如下:

.h

IBOutlet UIWebView *webView;
.m
- (void)viewDidLoad
{
[webView loadRequest:[NSURLRequest requestWithURL: [NSURL URLWithString:@"http:/www.google.com/"]]];

感谢您的帮助!


请在此处查看答案,以获得有关进度条代码放置位置的更好解决方案:http://stackoverflow.com/questions/23966697/show-progress-bar-until-it-load-the-data-in-uiwebview-ios7 - Suhaib
9个回答

74
要使UIProgressView准确无误,需要有一些任务:
  • 您可以获取信息但该任务尚未完成
  • 根据该信息将其“完整性”量化为百分比。
现在,当您加载UIWebView时,这是不可能的。苹果也不这样做。苹果经常使用虚假的UIProgressView,在页面加载时提供给您一些看的东西。邮箱也使用虚假的进度视图。尝试一下。这是苹果虚假进度视图的工作方式:
  • 进度视图开始以缓慢、恒定的速度移动
  • 如果任务在条形完成之前完成,则它会突然跨越其余部分到100%,然后消失
  • 如果任务花费很长时间,进度视图将停在95%并保持在那里,直到任务完成。
要实现此目的,您将不得不手动动画化progressView。您可以对其进行子类化,但这可能对您来说有点高级。最简单的方法是这样的:
在我的ViewController.h中:
@interface myViewController : UIViewController {
     BOOL theBool;
     //IBOutlet means you can place the progressView in Interface Builder and connect it to your code
     IBOutlet UIProgressView* myProgressView;
     NSTimer *myTimer;
}
@end

在 myViewController.m 文件中

#import "myViewController.h"
@implementation myViewController
- (void)webViewDidStartLoad:(UIWebView *)webView{
     myProgressView.progress = 0;
     theBool = false;
     //0.01667 is roughly 1/60, so it will update at 60 FPS
     myTimer = [NSTimer scheduledTimerWithTimeInterval:0.01667 target:self selector:@selector(timerCallback) userInfo:nil repeats:YES];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView{
     theBool = true;
}
-(void)timerCallback {
    if (theBool) {
         if (myProgressView.progress >= 1) {
              myProgressView.hidden = true;
              [myTimer invalidate];
         }
         else {
              myProgressView.progress += 0.1;
         }
    }
    else {
         myProgressView.progress += 0.05;
         if (myProgressView.progress >= 0.95) {
              myProgressView.progress = 0.95;
         }
    }
}
@end

然后,在任务完成的地方,设置 theBool = true;,进度视图会自动处理。更改if语句中的值以控制动画速度。


2
+1 我在提交我的答案之前没有仔细阅读你的答案,它也建议使用虚假进度条 :) - Tricertops
谢谢您的帮助。但是对我没有用。我做错了什么?我把一个进度视图放进我的Web View里,并将其与'IBOutlet'连接。现在我可以在模拟器运行时看到一个进度视图,但它不会动画。 - user3220034
1
确保您的计时器正常工作。timerCallback是否被调用? - WolfLink
1
@danihvilla 注意到了我犯的一个错误。使用scheduledTimerWithInterval:而不是timerWithInterval: - WolfLink
3
我明白您想隐藏进度条,但计时器仍应该在运行,对吗?难道我们不应该也停止计时器吗? - KD.
显示剩余6条评论

5

Swift 2.2

class ViewController: UIViewController,UIWebViewDelegate {

    @IBOutlet weak var webView: UIWebView!
    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
    @IBOutlet weak var myProgressView: UIProgressView!

    var myTimer = NSTimer()
    var theBool = Bool()

    override func viewDidLoad() {
        super.viewDidLoad()

        let url = NSURL(string: "https://www.youtube.com")
        let request = NSURLRequest(URL: url!)
        activityIndicator.hidesWhenStopped = true
        activityIndicator.startAnimating()
        webView.loadRequest(request)

    }
    func timerCallback(){
        if theBool {
            if myProgressView.progress >= 1 {
                myProgressView.hidden = true
                myTimer.invalidate()
            }else{
                myProgressView.progress += 0.1
            }
        }else{
            myProgressView.progress += 0.05
            if myProgressView.progress >= 0.95 {
                myProgressView.progress = 0.95
            }
        }
    }

    func webViewDidStartLoad(webView: UIWebView) {
        activityIndicator.startAnimating()
        myProgressView.progress = 0
        theBool = false
        myTimer =  NSTimer.scheduledTimerWithTimeInterval(0.01667,target: self,selector: #selector(ViewController.timerCallback),userInfo: nil,repeats: true)
    }
    func webViewDidFinishLoad(webView: UIWebView) {
        activityIndicator.stopAnimating()
        theBool = true
    }
}

4
如果有人想在Swift 3中完成这个任务,我花了几天时间试图弄清楚它,最终成功了。
以下是代码 :)
override func viewDidLoad() {
    super.viewDidLoad()

    let url = NSURL(string: "http://google.com")
    let request = NSURLRequest(url: url as! URL)
    webView.loadRequest(request as URLRequest)
    webView.delegate=self

}

func webViewDidStartLoad(_ webView: UIWebView) {

    self.progressView.setProgress(0.1, animated: false)
}


func webViewDidFinishLoad(_ webView: UIWebView) {

    self.progressView.setProgress(1.0, animated: true)
}

func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {

    self.progressView.setProgress(1.0, animated: true)
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

3

我按照Wolflink的答案进行操作,但是发现进度视图没有动画。

阅读了NSTimer类参考文档之后:

https://developer.apple.com/library/Mac/documentation/Cocoa/Reference/Foundation/Classes/NSTimer_Class/Reference/NSTimer.html#//apple_ref/occ/clm/NSTimer/timerWithTimeInterval:target:selector:userInfo:repeats:

在“讨论”中,您可以阅读到: “您必须使用addTimer:forMode:将新计时器添加到运行循环中。”
所以我添加了:
[[NSRunLoop currentRunLoop] addTimer:myTimer forMode:NSDefaultRunLoopMode];

myTimer = [NSTimer timerWithTimeInterval:0.01667 target:self selector:@selector(timerCallback)userInfo:nil repeats:YES];
之后。
对我有用(正如WolfLink建议我做的那样,我对UIProgressView进行了子类化)。

谢谢您的注意!我本意是使用[NSTimer scheduledtimerWithTimeInterval:0.01667 target:self selector:@selector(timerCallback) userInfo:nil repeats:YES];,它会自动将计时器添加到运行循环中。 - WolfLink
更加简洁,我会使用那种方式。我看到你已经编辑了你的答案,这样@user3220034就可以将其标记为正确的答案。至少这就是我一直在寻找的。 - danihvilla

2
很难(甚至可能不可能)做到,因为您必须跟踪站点加载的所有资源,但是......
我有一个想法。这更像是一个“技巧”,而不是真正的解决方案,但我认为甚至苹果也在消息应用程序中使用了这种方法:)
1.当您开始加载页面时,启动90%进度的动画(例如持续时间为1.5秒,Wi-Fi、LTE、3G等可能不同)。
2.当页面同时加载时,快速将进度条动画到100%。完成!
3.如果页面需要更长时间才能加载,则进度条将停止在90%处并等待。用户看到状态栏中缓慢旋转指示器的沮丧时刻!然后最终,页面完成加载,并且(如第2点所述)播放快速动画到100%。完成!
我认为我们都知道,这就是消息应用程序的工作方式,因为我不相信发送短信可以以如此准确的进度进行跟踪 :)

1

现在,苹果提供了 WebKit 框架,其中包括 WKWebView 类,它允许您使用属性 'estimatedProgress' 查询预估进度,因此您不再需要像在 UIWebView 上显示进度条时那样“伪造”进度。


这是现在的正确答案。从 WebView 更改为 WKWebView。 - O'Rooney

0

我本想添加一条评论,而不是回答,但我还没有足够的声望来进行评论!

就在几天前,我几乎问了完全相同的问题。在那个问题中,我包括了我想出的解决方案。我的解决方案比WolfLink的更复杂,但它确实跟踪页面加载的真实进度,虽然它并不是100%准确的。正如WolfLink所说的那样,这是不可能的。我的问题可以在这里找到:Progress bar for UIWebView

请注意,无论您以哪种方式处理,都需要检查UIWebView的加载属性,以确定页面加载何时完成。


谢谢。我肯定没有想到它会这么复杂 :p - user3220034

0
我尝试过以下模式。它给了我稍微好一点的结果。我正在使用WKWebview。
@IBOutlet weak var progressView: UIProgressView! // added in storyboard
@IBOutlet weak var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        setupEstimatedProgressObserver()
    }

    // Observe the WebViewClient for estimatedProgress value
    private func setupEstimatedProgressObserver() {
        estimatedProgressObserver = webView.observe(\.estimatedProgress, options: [.new]) { [weak self] webView, _ in
            self?.progressView.progress = Float(webView.estimatedProgress)
        }
    }

    //When WebView load finished in WKNavigationDelegate in didFinish method we will use the following approach
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        self.progressView.progress =  1.0
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            // your code here
             self.progressView.isHidden = true
        }
    }

0

这里的一些答案并不完全正确,因为它们忘记了为webview注册代理。不幸的是,仅实现UIWebViewDelegate并重写两种方法是不够的。您还需要将webview的代理设置为self

class ViewController: UIViewController,UIWebViewDelegate {

    @IBOutlet weak var webView: UIWebView!
    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
    @IBOutlet weak var myProgressView: UIProgressView!

    var myTimer = NSTimer()
    var theBool = Bool()

    override func viewDidLoad() {
        super.viewDidLoad()

        let url = NSURL(string: "https://www.youtube.com")
        let request = NSURLRequest(URL: url!)
        activityIndicator.hidesWhenStopped = true
        activityIndicator.startAnimating()

        // !
        webView.delegate = self // <--- important
        // ! 

        webView.loadRequest(request)

    }

    func timerCallback(){
        if theBool {
            if myProgressView.progress >= 1 {
                myProgressView.hidden = true
                myTimer.invalidate()
            }else{
                myProgressView.progress += 0.1
            }
        }else{
            myProgressView.progress += 0.05
            if myProgressView.progress >= 0.95 {
                myProgressView.progress = 0.95
            }
        }
    }

    func webViewDidStartLoad(webView: UIWebView) {
        activityIndicator.startAnimating()
        myProgressView.progress = 0
        theBool = false
        myTimer =  NSTimer.scheduledTimerWithTimeInterval(0.01667,target: self,selector: #selector(ViewController.timerCallback),userInfo: nil,repeats: true)
    }

    func webViewDidFinishLoad(webView: UIWebView) {
        activityIndicator.stopAnimating()
        theBool = true
    } 
}

这段代码是参考 @ZAFAR007 的答案编写的


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