You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在iOS App中为Web Service调用显示进度条

嘿,这个问题问到点子上了!用假进度条应付用户确实省事,但真实的进度反馈对提升体验帮助太大了。下面我分两种核心场景给你拆解实现方案,都是iOS原生的做法:

场景1:服务器返回Content-Length(已知总数据量)

这是最理想的情况,服务器会在响应头里告诉你要传输的数据总大小,我们可以精准计算实时进度。

实现思路(以URLSessionDownloadTask为例,最省心)

DownloadTask天生自带进度监听的代理方法,不用自己维护数据长度,直接拿参数计算就行:

  1. 初始化带代理的URLSession,确保代理回调在主线程(方便直接更新UI)
  2. 发起下载任务,监听进度回调更新进度条
  3. 请求完成后收尾处理

代码示例

class APIPProgressVC: UIViewController, URLSessionDownloadDelegate {
    // 进度条控件
    private let progressView = UIProgressView(progressViewStyle: .default)
    private var urlSession: URLSession!

    override func viewDidLoad() {
        super.viewDidLoad()
        setupProgressView()
        setupURLSession()
    }

    private func setupProgressView() {
        progressView.frame = CGRect(x: 20, y: 120, width: view.bounds.width - 40, height: 24)
        view.addSubview(progressView)
    }

    private func setupURLSession() {
        let config = URLSessionConfiguration.default
        // 把代理队列设为主线程,省去手动切线程的麻烦
        urlSession = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
    }

    // 启动API请求
    func startAPIRequest() {
        guard let apiURL = URL(string: "https://your-api-domain.com/target-endpoint") else { return }
        let downloadTask = urlSession.downloadTask(with: apiURL)
        downloadTask.resume()
    }

    // 核心:监听进度回调
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        // 先判断总大小是否可获取
        guard totalBytesExpectedToWrite != NSURLSessionTransferSizeUnknown else {
            // 走到这里说明是场景2的情况,后面会讲
            progressView.progress = 0.6
            return
        }
        // 计算进度比例并更新UI
        let progressRatio = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
        progressView.setProgress(progressRatio, animated: true)
    }

    // 请求完成回调
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        // 进度条拉满
        progressView.setProgress(1.0, animated: true)
        // 这里处理返回的数据,比如读取临时文件里的内容
        do {
            let responseData = try Data(contentsOf: location)
            // 解析数据、更新页面...
        } catch {
            print("读取响应数据失败:\(error.localizedDescription)")
        }
    }
}

如果是用URLSessionDataTask(比如不需要保存到文件,直接内存处理数据),只需要自己维护已接收数据的长度,再从响应头里拿Content-Length计算就行,逻辑类似。

场景2:服务器用Chunked传输(无Content-Length

这种情况服务器不会告诉你总数据量,没法做精准进度。这时候我们可以做「伪真实」的进度反馈,避免用户觉得卡住:

  1. 渐进式伪进度:先让进度条平滑走到80%左右,然后停住,等请求完成后直接拉到100%
  2. 阶段式进度:如果你的API是多步骤处理(比如上传→校验→处理→返回),可以和后端协作,让接口返回当前处理阶段(比如"progress": "30%", "stage": "正在校验数据"),前端对应更新进度
  3. 波动动画:用CAAnimation做一个来回波动的进度条,告诉用户「请求正在进行中」

渐进式伪进度的代码示例

// 启动请求时先开伪进度动画
func startAPIRequest() {
    // 3秒内从0走到0.8,模拟加载进度
    UIView.animate(withDuration: 3.0, delay: 0, options: .curveEaseInOut) {
        self.progressView.progress = 0.8
    } completion: { _ in
        // 动画结束后保持在0.8,直到请求完成
    }

    // 发起实际的API请求
    guard let apiURL = URL(string: "https://your-api-domain.com/chunked-endpoint") else { return }
    let dataTask = URLSession.shared.dataTask(with: apiURL) { [weak self] data, response, error in
        DispatchQueue.main.async {
            // 请求完成,进度条拉满
            self?.progressView.setProgress(1.0, animated: true)
            // 后续处理...
        }
    }
    dataTask.resume()
}
额外注意事项
  • 所有UI更新必须在主线程执行!如果你的URLSession代理队列不是主线程,记得用DispatchQueue.main.async包裹更新代码
  • 如果是上传请求,用URLSessionUploadTaskurlSession(_:uploadTask:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)方法监听上传进度,逻辑和下载一致
  • 要处理错误场景:比如请求失败时,把进度条重置或者显示错误提示,别让进度条停在半路

内容的提问来源于stack exchange,提问作者SURESH SANKE

火山引擎 最新活动