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

嵌套UITableView的UICollectionView数据显示异常问题求助

iOS嵌套UICollectionView数据匹配异常问题解决

问题描述

UITableView内部嵌套UICollectionView,使用包含7个section的data1数组作为数据源时,重新加载数据会出现图片与section名称不匹配的问题;但将数据源限制为4个section时,功能完全正常。

核心原因

问题源于UITableViewCell的复用机制:

  • 当UITableView的cell被复用时,仅修改UICollectionView的tag但未强制触发其重新加载数据,导致复用的cell仍显示之前section的内容。
  • CategoriesTableViewCell的awakeFromNib()仅在cell首次创建时执行,复用阶段不会重新同步数据源逻辑,进一步加剧数据错位。

解决方案

1. 给TableViewCell添加数据源绑定,替代全局依赖

避免直接依赖全局data1数组和collectionView.tag,改为给CategoriesTableViewCell传递对应section的数据源,确保数据与cell强绑定:

修改CategoriesTableViewCell:

class CategoriesTableViewCell: UITableViewCell {
    // Outlets
    @IBOutlet weak var collectionView: UICollectionView!

    //Constants
    let defaults = UserDefaults.standard

    // Variables
    var resetIndex: Bool = false
    var myCat: String = ""
    var didSelectItemAction: ((String) -> Void)?
    // 新增:当前section的数据源
    var sectionData: quoteData? {
        didSet {
            // 数据源更新时自动刷新collectionView
            collectionView.reloadData()
        }
    }


    //Methods
    override func awakeFromNib() {
        super.awakeFromNib()
        collectionView.delegate = self
        collectionView.dataSource = self
        myCat = defaults.string(forKey: "DefaultQuotes")!
        resetIndex = defaults.bool(forKey: "ResetIndex")
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }
}

在CategoriesViewController的tableView(_:cellForRowAt:)方法中传递数据源:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell1", for: indexPath) as! CategoriesTableViewCell
    // 绑定当前section的数据源
    cell.sectionData = data1[indexPath.section]

    cell.didSelectItemAction = { [weak self] selectedCat in
        guard let self = self else { return }
        self.existingCat = self.defaults.string(forKey: "DefaultQuotes") ?? ""
        self.defaultCat = selectedCat
        if !self.defaultCat.isEmpty {
            if self.defaultCat != "General" {
                self.adAlert()
            } else {
                self.defaults.set(self.defaultCat, forKey: "DefaultQuotes")
                self.navigationController?.popViewController(animated: true)
            }
        } else {
            self.navigationController?.popViewController(animated: true)
        }
    }
    return cell
}

2. 修改CollectionView代理方法,使用绑定的数据源

不再依赖collectionView.tag和全局data1,直接用cell内的sectionData获取数据:

extension CategoriesTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        guard let sectionData = sectionData, indexPath.row < sectionData.quotes.count else { return }
        let quotePath = sectionData.quotes[indexPath.row]
        let r = quotePath.index(quotePath.startIndex, offsetBy: 11)..<quotePath.index(quotePath.endIndex, offsetBy: -4)
        myCat = String(quotePath[r])
        didSelectItemAction?(myCat)
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return sectionData?.quotes.count ?? 0
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell1", for: indexPath) as! CategoriesCollectionViewCell
        let defaults = UserDefaults.standard
        let defQuote = defaults.string(forKey: "DefaultQuotes")
        
        guard let sectionData = sectionData, indexPath.row < sectionData.quotes.count else { return cell }
        
        let quotePath = sectionData.quotes[indexPath.row]
        cell.images.image = UIImage(named: quotePath)
        
        let r = quotePath.index(quotePath.startIndex, offsetBy: 11)..<quotePath.index(quotePath.endIndex, offsetBy: -4)
        let currentCat = String(quotePath[r])
        
        let myLbl = cell.viewWithTag(80) as? UILabel
        myLbl?.text = currentCat
        
        let myIcon = cell.viewWithTag(83) as? UIImageView
        let padlock = cell.viewWithTag(81) as? UIImageView
        
        // 设置选中边框状态
        if myLbl?.text == defQuote {
            cell.images?.layer.borderWidth = 2.5
            cell.images?.layer.borderColor = UIColor.red.cgColor
            cell.images?.clipsToBounds = true
        } else {
            cell.images?.layer.borderWidth = 0.0
            cell.images?.layer.borderColor = UIColor.clear.cgColor
            cell.images?.clipsToBounds = false
        }
        
        // 设置分类图标
        let iconName = getImg(Inb: currentCat)
        if iconName.isEmpty {
            myIcon?.image = UIImage(named: "CatIcons/\(currentCat).png")
        } else {
            myIcon?.image = UIImage(systemName: iconName)
        }
        
        // 设置锁图标显示逻辑
        if currentCat == "General" || sectionData.sectionType == "Free Today" {
            padlock?.isHidden = true
        } else {
            padlock?.isHidden = false
        }
        
        return cell
    }

    func getImg(Inb: String) -> String {
        switch Inb {
        case "General": return "rainbow"
        case "Hope": return "sunrise"
        case "Sleep": return "bed.double.circle"
        case "Mindfulness": return "brain.head.profile"
        case "Enjoy the Moment": return "figure.2"
        case "Calm": return "figure.yoga"
        default: return ""
        }
    }
}

3. 修复didSelectItemAt的错误逻辑

原代码在didSelectItemAt中错误调用dequeueReusableCell,上述修改已移除该错误逻辑,直接通过数据源获取选中项信息。

为什么4个section时正常?

当section数量较少时,所有UITableViewCell都处于屏幕可视范围内,无需复用,因此每个CollectionView的tag和数据都能正确匹配;当section超过4个后,cell开始复用,旧数据未及时更新,导致显示错位。


内容的提问来源于stack exchange,提问作者Douglas W. Palme

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 21:28:10