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
相关产品推荐
相关产品推荐

