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

UITableView嵌套UICollectionView选中状态保存异常求助

解决多组UITableView嵌套UICollectionView的选中状态持久化问题

你的问题确实出在没有关联UICollectionView所属的UITableView分组信息——不同UITableView组里的UICollectionView可能有相同的IndexPath,仅保存CollectionView的IndexPath会导致恢复时无法区分归属,从而出现批量误选的情况。下面是具体的解决思路和代码修改方案:

核心思路

创建一个自定义结构体,同时保存UITableView的IndexPath(标记所属分组)和UICollectionView的IndexPath(标记组内选中项),将这个结构体数组持久化到UserDefaults,恢复时通过两个IndexPath的组合判断是否应该选中单元格。

具体实现步骤

1. 定义可持久化的选中项结构体

使用Swift的Codable协议(比NSCoding更简洁),定义包含两个IndexPath的结构体:

import UIKit

// 保存选中项的归属分组和自身位置
struct SelectedItem: Codable {
    let tableViewIndexPath: IndexPath
    let collectionViewIndexPath: IndexPath
}

2. 替换原有选中项数组

把原来的arrSelectedIndex: [IndexPath]替换为:

var arrSelectedItems: [SelectedItem] = []

3. 修改读取持久化数据的逻辑

在awakeFromNib中,用JSONDecoder解码结构体数组:

override func awakeFromNib() {
    super.awakeFromNib()
    let usrDefault = UserDefaults.standard
    
    if let data = usrDefault.data(forKey: "selected") {
        do {
            // 解码成SelectedItem数组
            arrSelectedItems = try JSONDecoder().decode([SelectedItem].self, from: data)
        } catch {
            print("解码选中项失败:\(error.localizedDescription)")
            arrSelectedItems = []
        }
    } else {
        arrSelectedItems = []
    }
}

4. 修改UICollectionView的单元格状态判断逻辑

在cellForItemAt中,先获取当前CollectionView所属的UITableView分组,再通过双IndexPath组合判断是否选中:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let optionItemCell = collectionView.dequeueReusableCell(withReuseIdentifier: "optionCell", for: indexPath) as! SDFilterCollectionCell
    let title = itemFilter[indexPath.section].value[indexPath.item].option_name
    
    // 获取当前CollectionView所属的UITableView IndexPath
    guard let parentCell = collectionView.superview?.superview as? UITableViewCell,
          let tableViewIndexPath = tableView.indexPath(for: parentCell) else {
        // 默认未选中状态
        optionItemCell.filterSelectionComponent?.bind(title: title!, style: .unselect)
        optionItemCell.backgroundColor = UIColor(hexaString: SDDSColor.color_white.rawValue)
        optionItemCell.layer.borderColor = UIColor(hexaString: SDDSColor.color_grey_100.rawValue).cgColor
        return optionItemCell
    }
    
    // 判断当前项是否被选中(同时匹配分组和组内位置)
    let isSelected = arrSelectedItems.contains { item in
        item.tableViewIndexPath == tableViewIndexPath && item.collectionViewIndexPath == indexPath
    }
    
    if isSelected {
        optionItemCell.filterSelectionComponent?.bind(title: title!, style: .select)
        optionItemCell.backgroundColor = UIColor(hexaString: SDDSColor.color_red_50.rawValue)
        optionItemCell.layer.borderColor = UIColor(hexaString: SDDSColor.color_red_300.rawValue).cgColor
    } else {
        optionItemCell.backgroundColor = UIColor(hexaString: SDDSColor.color_white.rawValue)
        optionItemCell.layer.borderColor = UIColor(hexaString: SDDSColor.color_grey_100.rawValue).cgColor
        optionItemCell.filterSelectionComponent?.bind(title: title!, style: .unselect)
    }
    
    optionItemCell.layoutSubviews()
    return optionItemCell
}

5. 修改选中/取消选中的逻辑

在didSelectItemAt中,先获取所属UITableView分组,再添加/移除对应的SelectedItem,最后持久化:

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    let strData = itemFilter[indexPath.section].value[indexPath.item]
    let cell = collectionView.cellForItem(at: indexPath) as? SDFilterCollectionCell
    
    // 获取当前CollectionView所属的UITableView IndexPath
    guard let parentCell = collectionView.superview?.superview as? UITableViewCell,
          let tableViewIndexPath = tableView.indexPath(for: parentCell) else {
        return
    }
    
    // 创建当前选中项的结构体实例
    let currentItem = SelectedItem(tableViewIndexPath: tableViewIndexPath, collectionViewIndexPath: indexPath)
    
    if arrSelectedItems.contains(currentItem) {
        // 取消选中:移除数组中的对应项
        arrSelectedItems.removeAll { $0 == currentItem }
        arrSelectedData.removeAll { $0 == strData }
        
        // 更新单元格为未选中状态
        cell?.filterSelectionComponent?.bind(title: strData.option_name!, style: .unselect)
        cell?.backgroundColor = UIColor(hexaString: SDDSColor.color_white.rawValue)
        cell?.layer.borderColor = UIColor(hexaString: SDDSColor.color_grey_100.rawValue).cgColor
    } else {
        // 添加选中:加入数组
        arrSelectedItems.append(currentItem)
        arrSelectedData.append(strData)
        
        // 更新单元格为选中状态
        cell?.filterSelectionComponent?.bind(title: strData.option_name!, style: .select)
        cell?.backgroundColor = UIColor(hexaString: SDDSColor.color_red_50.rawValue)
        cell?.layer.borderColor = UIColor(hexaString: SDDSColor.color_red_300.rawValue).cgColor
    }
    
    // 持久化选中数据到UserDefaults
    do {
        let encodedData = try JSONEncoder().encode(arrSelectedItems)
        UserDefaults.standard.set(encodedData, forKey: "selected")
    } catch {
        print("编码选中项失败:\(error.localizedDescription)")
    }
    
    // 原有代理逻辑保持不变
    if let delegate = delegate {
        if itemFilter[indexPath.section].search_key.count > 0 {
            if (strData.option_id != "") {
                input.add(strData.option_id!)
                let output = input.componentsJoined(by: ",")
                data["search_key"] = itemFilter[indexPath.section].search_key.count > 0 ? itemFilter[indexPath.section].search_key : strData.search_key;
                data["option_id"] = output
            }
        } else {
            data["search_key"] = itemFilter[indexPath.section].search_key.count > 0 ? itemFilter[indexPath.section].search_key : strData.search_key;
            data["option_id"] = strData.option_id
        }
        delegate.filterTableCellDidSelectItem(item: data, indexPath: indexPath)
    }
}

补充说明

  • 获取UITableView IndexPath的方式:这里通过collectionView.superview?.superview获取父UITableViewCell,你可能需要根据自己的视图层级调整(比如如果CollectionView在自定义Cell的某个容器视图里,可能需要多几层superview)。也可以给每个CollectionView设置tag为UITableView的indexPath.row,这样直接通过collectionView.tag获取分组信息,更稳定。
  • SelectedItem不需要额外遵循Equatable:因为IndexPath已经遵循Equatable,结构体默认会自动实现该协议,可以直接用contains和==判断。

这样修改后,每个选中项都会关联到所属的UITableView分组,恢复时就能精准匹配,不会出现跨分组误选的问题了。

内容的提问来源于stack exchange,提问作者HHHHH

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 08:17:31