TableView重载后UI未正确更新:缺货状态显示异常求助
这种异步请求和TableView数据不同步的问题确实让人头大,我帮你梳理下代码里的几个关键问题,应该就能解决了:
核心问题分析
1. 异步请求的顺序与数据覆盖问题
你在getProductList的循环里每次都清空availableIdArray然后调用getProductAvailability,但网络请求是异步的——这意味着第一个请求还没返回,第二个请求就已经把数组清空了,最后只有最后一个请求的结果会留在数组里,前面的全丢了。而且你每次调用getProductAvailability后立刻发通知,这时候请求可能还没完成,TableView拿到的是不完整的数据。
2. Cell复用与显示逻辑错误
在cellForRowAt里,你遍历整个availableIdArray来判断当前cell的显示状态,这会导致逻辑混乱:比如当前cell的商品ID在数组里存在,遍历到它时你设置isHidden = false,但继续遍历到下一个不匹配的ID时,又会把它设回isHidden = true,最后只有最后一个匹配的cell会显示正确,其他的都会被错误隐藏。
3. 全局数组的管理混乱
切换分类时,你没有确保在新的请求开始前完全清空旧的库存数据,导致新分类的商品和旧分类的库存数据混在一起,显示自然出错。
修复后的代码示例
第一步:重构数据请求逻辑,确保异步请求完成后再更新UI
我们用DispatchGroup来管理所有异步库存请求,等全部完成后再刷新TableView,避免数据混乱:
public func getProductList(request : ProductRequest) { User.apiProductList(Request: request) {(response, error) in guard let array = response.obj as? [Products], !array.isEmpty else { print("No Products…\n") // 清空旧数据并刷新 Home.productListArray = [] Home.productAvailableArray = [] Home.availableIdArray = [] NotificationCenter.default.post(name: .reload, object: nil) return } Home.productListArray = array // 清空旧的库存数据 Home.productAvailableArray.removeAll() Home.availableIdArray.removeAll() let dispatchGroup = DispatchGroup() for product in array { dispatchGroup.enter() // 复制请求对象,修改为当前商品的ID(这里需要你根据实际请求结构调整) var productAvailabilityRequest = availabilityRequest productAvailabilityRequest.productId = product.productId User.apiProductAvailability(Request: productAvailabilityRequest, callback: {(response, error) in defer { dispatchGroup.leave() } guard let object = response.obj as? AvailableProduct else { return } Home.availableIdArray.append(object.productId! as! Int) Home.productAvailableArray.append(object) }) } // 所有库存请求完成后统一刷新TableView dispatchGroup.notify(queue: .main) { NotificationCenter.default.post(name: .reload, object: nil) } } }
注意:你需要调整
availabilityRequest的创建逻辑,让它能接收当前商品的ID,不然每个请求都会查同一个商品的库存,这肯定不对!
第二步:修复Cell的显示逻辑
把遍历数组的逻辑改成判断当前商品ID是否在缺货列表里,同时先设置默认状态避免复用旧UI:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "productcell", for: indexPath) as! ProductViewCell let data = Home.productListArray[indexPath.row] // 先设置默认隐藏,避免复用旧cell的显示状态 cell.availableProductView.isHidden = true expandedRows.remove(indexPath) // 只需要判断当前商品ID是否在缺货列表中 if data.productId != 0 && Home.availableIdArray.contains(data.productId) { cell.availableProductView.isHidden = false expandedRows.insert(indexPath) } // 别忘了设置商品详情的UI数据 // cell.productDetailView.configure(with: data) return cell }
第三步:优化刷新和分类切换的逻辑
在刷新和分类切换时,确保先清空所有旧数据,再发起新请求:
@objc func refreshAction(){ self.refreshControl.endRefreshing() // 清空所有旧数据,避免新旧数据混杂 Home.productListArray.removeAll() Home.productAvailableArray.removeAll() Home.availableIdArray.removeAll() getProductList(request: productListRequest) }
第四步:移除不必要的通知发送
删掉getProductAvailability里单独发送的刷新通知,因为我们已经用DispatchGroup在所有请求完成后统一刷新了,单独发送会导致TableView在数据不完整时更新。
额外建议
- 尽量不要用全局数组管理TableView数据,最好把数据封装在当前ViewController里,或者用ViewModel来管理,全局变量很容易导致数据混乱。
- 给模型添加可选值安全处理,避免强制解包(比如
object.productId! as! Int),减少崩溃风险。
内容的提问来源于stack exchange,提问作者Bella

