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
相关产品推荐
相关产品推荐

