UICollectionView切换分类旧数据残留 reloadData调用位置排查
问题描述
- 异常现象:UISegmentedControl配置商品分类选项后,点击对应分类时UICollectionView未清空旧分类数据,反而在旧数据末尾追加新分类商品,无法正确展示当前分类下的商品列表
- 核心疑问:
reloadData()的正确调用位置在哪里?是否存在其他逻辑疏漏?
原有实现代码
控制器基础逻辑
private lazy var segmentedControl: UISegmentedControl = { var control = UISegmentedControl(items: ["All", "Men", "Women", "Jewelery", "Electro"]) control.selectedSegmentTintColor = .black control.setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected) control.tintColor = .white control.selectedSegmentIndex = 0 control.addTarget(self, action: #selector(segmentChanged(_ :)), for: .valueChanged) return control }() override func viewDidLoad() { super.viewDidLoad() collectionView.delegate = self collectionView.dataSource = self networkManager.delegate = self setupMainStackView() setupCollectionView() networkManager.loadProducts(category: .all) performSearch() } func performSearch() { if let category = NetworkManager.Category(rawValue: segmentedControl.selectedSegmentIndex) { networkManager.loadProducts(category: category) collectionView.reloadData() } } @objc func segmentChanged(_ sender: UISegmentedControl) { performSearch() }
UICollectionView数据源实现
// MARK: - Data Source extension MainViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return productResults.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MainViewCollectionViewCell.identifier, for: indexPath) as! MainViewCollectionViewCell let listOfProduct = productResults[indexPath.row] cell.configure(for: listOfProduct) return cell } }
分类枚举与网络请求逻辑
enum Category: Int { case all = 0 case menSClothing = 1 case womenSClothing = 2 case electronics = 3 case jewelery = 4 var type: String { switch self { case .all: return "" case .menSClothing: return "category/men's%20clothing" case .womenSClothing: return "category/women's%20clothing" case .electronics: return "category/electronics" case .jewelery: return "category/jewelery" } } } private func fakeStoreURL(category: Category) -> URL { let kind = category.type let url = URL(string: "https://fakestoreapi.com/products/" + "\(kind)") return url! } func loadProducts(category: Category) { let url = fakeStoreURL(category: category) URLSession.shared.dataTask(with: url) { data, response, error in if let error = error { print(error) return } guard let data = data else { return } do { var products = try JSONDecoder().decode([Product].self, from: data) products = self.parse(data: data) print(products) DispatchQueue.main.async { self.delegate?.didSendProductData(self, with: products) } } catch { print(error) } }.resume() }
问题原因与修复方案
核心问题点
reloadData()调用时机完全错误
你在发起网络请求的同时立刻调用reloadData(),此时网络请求还未返回,新分类的商品数据还没赋值给productResults,刷新操作拿到的还是旧数据,完全无法加载新分类内容。- 数据源操作逻辑错误
你贴出的代码未给出didSendProductData代理方法的实现,但从“旧数据末尾追加新数据”的现象可以确定,你在这个回调中对productResults数组使用了append类的追加操作,而不是直接替换整个数组。 - 首屏请求冗余
viewDidLoad中你先调用了networkManager.loadProducts(category: .all),紧接着又调用performSearch(),相当于首屏加载时发起了两次重复的全部分类请求。 - 分类枚举映射顺序错误
UISegmentedControl的item顺序是["All", "Men", "Women", "Jewelery", "Electro"],索引3对应珠宝、索引4对应电子产品,但你定义的枚举中索引3是电子产品、索引4是珠宝,会导致点击分类时请求错误的接口。
修复后的核心代码
首先修正分类枚举的顺序,和分段控制器选项对齐:
enum Category: Int { case all = 0 case menSClothing = 1 case womenSClothing = 2 case jewelery = 3 case electronics = 4 var type: String { switch self { case .all: return "" case .menSClothing: return "category/men's%20clothing" case .womenSClothing: return "category/women's%20clothing" case .jewelery: return "category/jewelery" case .electronics: return "category/electronics" } } }
修正viewDidLoad,去掉重复的首屏请求:
override func viewDidLoad() { super.viewDidLoad() collectionView.delegate = self collectionView.dataSource = self networkManager.delegate = self setupMainStackView() setupCollectionView() performSearch() }
修正performSearch,去掉提前触发的无效刷新:
func performSearch() { guard let category = NetworkManager.Category(rawValue: segmentedControl.selectedSegmentIndex) else { return } networkManager.loadProducts(category: category) }
最后实现网络请求回调的代理方法,替换数据源后再刷新列表:
func didSendProductData(_ manager: NetworkManager, with products: [Product]) { // 直接替换整个数据源数组,禁止用append追加 self.productResults = products // 数据更新完成后刷新列表,网络回调已切到主线程可直接操作UI collectionView.reloadData() }
内容的提问来源于stack exchange,提问作者bunbun
相关产品推荐
相关产品推荐

