You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift 5开发:UITableView所有Cell及远程图片加载完成前显示进度条

Swift 5 UITableView所有Cell图片加载完成后隐藏进度条实现方案

当前代码存在3个核心问题:

  • viewDidLoad中调用完异步请求后立刻隐藏进度条,不会等待请求和图片加载完成
  • cellForRow中使用Data(contentsOf: url)同步加载图片,会阻塞主线程导致列表卡顿,且没有统计加载完成状态
  • getCart、getProductByCategory两个异步请求和图片加载任务并行执行,进度条的显示隐藏逻辑混乱

第一步:新增状态追踪属性

在控制器中新增属性,用来统计所有异步任务的完成状态:

// 需要加载的商品图片总数量
var totalImageCount = 0
// 已经加载完成的图片数量
var loadedImageCount = 0
// 商品列表接口请求完成标记
var isProductRequestFinish = false
// 购物车接口请求完成标记
var isCartRequestFinish = false

第二步:修改viewDidLoad逻辑

删除viewDidLoad中错误的提前隐藏进度条代码:

override func viewDidLoad() {
    super.viewDidLoad()
    self.showProgressBar()
    tableViewData.delegate = self
    tableViewData.dataSource = self
    self.settingUi()
    getCart()
    getProductByCategory()
    // 移除原有的self.hideProgressBar(),改为所有任务完成后自动隐藏
}

第三步:修改getProductByCategory方法

移除方法内重复的进度条显示/隐藏逻辑,请求完成后更新状态并触发全局检查:

func getProductByCategory(){
    // 移除原有的self.showProgressBar()
    var paramDict : [String:AnyObject] = [:]
    paramDict["store_id"]  = store.id as AnyObject
    paramDict["category_id"]  = category.id as AnyObject
    paramDict["activate"]  = "1" as AnyObject
    CommunicationManeger.callPostServiceReturnJson(apiUrl: RouterProd.get_product_by_store.url(), parameters: paramDict,  parentViewController: self, successBlock: { (responseData, message) in

        DispatchQueue.main.async { [self] in
            do {
                let products = try JSONDecoder().decode(ResponseProductModel.self, from: responseData as! Data)
                if(products.status == "1") {
                    self.products = products.result
                    // 赋值需要加载的图片总数量
                    self.totalImageCount = self.products.count
                    self.tableViewData.reloadData()
                } else {
                    self.products = []
                    Utility.noDataFound(products.message, tableViewOt: self.tableViewData, parentViewController: self)
                }
            }catch{
               print("数据解码错误: \(error)")
               Utility.noDataFound("错误", tableViewOt: self.tableViewData, parentViewController: self)
           }
            // 标记商品接口请求完成
            self.isProductRequestFinish = true
            self.checkIfAllTaskFinish()
        }
    }, failureBlock: { (error : Error) in
        Utility.showAlertMessage(withTitle: "", message: (error.localizedDescription), delegate: nil,parentViewController: self)
        // 接口失败也标记完成
        self.isProductRequestFinish = true
        self.checkIfAllTaskFinish()
    })
}

第四步:修改getCart方法

移除方法内重复的进度条显示/隐藏逻辑,请求完成后更新状态并触发全局检查:

func getCart(){
    // 移除原有的self.showProgressBar()
    var paramDict : [String:AnyObject] = [:]
    paramDict["user_id"]  = UserDefaults.standard.value(forKey: USERID) as AnyObject?
    CommunicationManeger.callPostServiceReturnJson(apiUrl: RouterProd.get_cart.url(), parameters: paramDict,  parentViewController: self, successBlock: { (responseData, message) in

        DispatchQueue.main.async { [self] in
            do {
                self.cart = try JSONDecoder().decode(ResponseCartModel.self, from: responseData as! Data)
                if(cart.status == "1") {
                    for product in cart.result {
                        if(product.type == TYPE_PRODUCT){
                            let detail = product.productDetails
                            self.updateQuantity(quantita: Int(product.quantity)!)
                            if(product.ingredientPrice.isEmpty){
                                self.updateTotalPrice(quantita: Int(product.quantity)!, prezzo: Float(detail.price)!)
                            }else{
                                self.updateTotalPrice(quantita: Int(product.quantity)!, prezzo: Float(product.ingredientPrice)!)
                            }
                        }
                    }
                }
            }catch{
               print("数据解码错误: \(error)")
           }
            // 标记购物车接口请求完成
            self.isCartRequestFinish = true
            self.checkIfAllTaskFinish()
        }
    }, failureBlock: { (error : Error) in
        Utility.showAlertMessage(withTitle: "", message: (error.localizedDescription), delegate: nil,parentViewController: self)
        // 接口失败也标记完成
        self.isCartRequestFinish = true
        self.checkIfAllTaskFinish()
    })
}

第五步:优化cellForRow图片加载逻辑

把同步加载改为异步加载,避免卡顿,每次加载完成后更新计数并触发全局检查:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableViewData.dequeueReusableCell(withIdentifier: "MenuSelectedCell") as! MenuSelectedCell
    let obj = self.products[indexPath.row]
    // 先设置占位图,避免Cell复用出现图片错乱
    cell.imgFood.image = UIImage(named: "占位图名称")
    let imageUrlStr = obj.image[0].image
    guard let url = URL(string: imageUrlStr) else {
        // 图片链接无效,直接标记为加载完成
        self.loadedImageCount += 1
        self.checkIfAllTaskFinish()
        return cell
    }
    // 异步加载图片,不阻塞主线程
    URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
        guard let self = self else { return }
        DispatchQueue.main.async {
            if let data = data, error == nil, let image = UIImage(data: data) {
                cell.imgFood.image = image
            }
            // 无论加载成功失败,计数+1
            self.loadedImageCount += 1
            self.checkIfAllTaskFinish()
        }
    }.resume()
    
    // 原有其他逻辑保持不变
    cell.lblNameProduct.text = obj.productName
    cell.lblDescriptionProduct.text = obj.resultDescription
    cell.lblPriceProduct.text = obj.price + " €"
    do{
        let cartObj = try? searchProduct(nomeProdotto: obj.productName)
        if(cartObj != nil){
            cell.lblQuantity.text = cartObj?.quantity
            cell.quantita = Int(cartObj!.quantity)
        }else{
            cell.lblQuantity.text = "0"
            cell.quantita = 0
        }
    }catch{
        
    }
    cell.buttonAdd.tag = indexPath.row
    cell.buttonAdd.addTarget(self, action: #selector(addQuantity(button:)), for: .touchUpInside)
    cell.buttonMinus.addTarget(self, action: #selector(removeQuantity(button:)), for: .touchUpInside)
    return cell
}

第六步:新增全局任务检查方法

新增统一的任务完成检查方法,所有任务完成后隐藏进度条:

/// 检查所有异步任务是否全部完成
func checkIfAllTaskFinish() {
    let isAllTaskDone = isProductRequestFinish && isCartRequestFinish && (loadedImageCount == totalImageCount)
    if isAllTaskDone {
        self.hideProgressBar()
    }
}

可选优化建议

如果项目中已经集成了Kingfisher、SDWebImage等第三方图片加载框架,可以直接使用框架的加载回调替代原生URLSession逻辑,同时自带缓存、占位图、失败重试等功能,性能更优。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 04:36:07