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

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()
}
问题原因与修复方案

核心问题点

  1. reloadData()调用时机完全错误
    你在发起网络请求的同时立刻调用reloadData(),此时网络请求还未返回,新分类的商品数据还没赋值给productResults,刷新操作拿到的还是旧数据,完全无法加载新分类内容。
  2. 数据源操作逻辑错误
    你贴出的代码未给出didSendProductData代理方法的实现,但从“旧数据末尾追加新数据”的现象可以确定,你在这个回调中对productResults数组使用了append类的追加操作,而不是直接替换整个数组。
  3. 首屏请求冗余
    viewDidLoad中你先调用了networkManager.loadProducts(category: .all),紧接着又调用performSearch(),相当于首屏加载时发起了两次重复的全部分类请求。
  4. 分类枚举映射顺序错误
    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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 12:48:17