Swift开发:双TableView每行复用PickerView的实现及选中填充问题
嘿,这个场景我之前做类似需求的时候也折腾过,给你分享下我当时的解决方案,应该能完美解决你的问题~
核心解决方案思路
1. 先把数据模型捋清楚
首先得给两个TableView的行分别定义对应的数据模型,这样选中Picker后能快速定位并填充内容。比如:
// 猫咪行的数据模型 struct CatRowItem { var selectedBreed: String? var ageInfo: String? var traitInfo: String? // 你需要的其他填充字段 } // 狗狗行的数据模型 struct DogRowItem { var selectedBreed: String? var ageInfo: String? var traitInfo: String? // 你需要的其他填充字段 }
然后在ViewController里维护两个数组作为TableView的数据源:
var catRowItems: [CatRowItem] = [] // TableA的数据源 var dogRowItems: [DogRowItem] = [] // TableB的数据源
2. 自定义TableViewCell,内嵌PickerView
因为每行都有独立的PickerView,最好把Picker封装到自定义Cell里,同时给Cell加个回调,用来通知ViewController用户选中了哪个品种,以及这个Cell对应的行索引。以猫咪Cell为例:
class CatTableViewCell: UITableViewCell, UIPickerViewDataSource, UIPickerViewDelegate { // 初始化UI组件:PickerView+需要填充的标签 let breedPicker = UIPickerView() let ageLabel = UILabel() let traitLabel = UILabel() // 猫咪品种列表 private let catBreeds = ["英短", "美短", "布偶", "暹罗", "缅因"] // 回调:传递选中的品种和当前cell的行索引 var onBreedSelected: ((String, Int) -> Void)? // 记录当前cell对应的行索引(用于数据源更新) var rowIndex: Int = 0 override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupLayout() breedPicker.dataSource = self breedPicker.delegate = self } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // 布局UI(这里可以用AutoLayout/SnapKit实现,根据你的习惯来) private func setupLayout() { contentView.addSubview(breedPicker) contentView.addSubview(ageLabel) contentView.addSubview(traitLabel) // 省略具体约束代码,把Picker放在首列,标签依次排列在右侧即可 } // PickerView数据源方法 func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return catBreeds.count } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return catBreeds[row] } // Picker选中后的回调 func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { let selectedBreed = catBreeds[row] onBreedSelected?(selectedBreed, rowIndex) } // 根据数据源更新Cell内容 func configure(with item: CatRowItem, row: Int) { rowIndex = row // 恢复之前选中的品种(避免Cell复用导致状态错乱) if let breed = item.selectedBreed, let index = catBreeds.firstIndex(of: breed) { breedPicker.selectRow(index, inComponent: 0, animated: false) } // 更新填充内容的标签 ageLabel.text = item.ageInfo traitLabel.text = item.traitInfo } }
狗狗的Cell逻辑完全类似,只需要把品种列表换成狗狗的,模型换成DogRowItem就行。
3. 绑定TableView数据源与Picker选中逻辑
在ViewController的TableView代理方法里,给自定义Cell设置数据和选中回调:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if tableView == tableA { let cell = tableView.dequeueReusableCell(withIdentifier: "CatCell", for: indexPath) as! CatTableViewCell let item = catRowItems[indexPath.row] cell.configure(with: item, row: indexPath.row) // 设置Picker选中后的处理逻辑 cell.onBreedSelected = { [weak self] breed, row in guard let self = self else { return } // 根据选中的品种获取对应填充数据(这里可以用字典映射/本地数据库/接口请求) let catDetail = self.getCatDetail(for: breed) // 更新数据源数组里的对应项 var updatedItem = self.catRowItems[row] updatedItem.selectedBreed = breed updatedItem.ageInfo = catDetail.age updatedItem.traitInfo = catDetail.trait self.catRowItems[row] = updatedItem // 只刷新当前行,性能更优 tableView.reloadRows(at: [IndexPath(row: row, section: 0)], with: .automatic) } return cell } else { // TableB的逻辑和上面一致,替换成DogCell和dogRowItems即可 let cell = tableView.dequeueReusableCell(withIdentifier: "DogCell", for: indexPath) as! DogTableViewCell let item = dogRowItems[indexPath.row] cell.configure(with: item, row: indexPath.row) cell.onBreedSelected = { [weak self] breed, row in guard let self = self else { return } let dogDetail = self.getDogDetail(for: breed) var updatedItem = self.dogRowItems[row] updatedItem.selectedBreed = breed updatedItem.ageInfo = dogDetail.age updatedItem.traitInfo = dogDetail.trait self.dogRowItems[row] = updatedItem tableView.reloadRows(at: [IndexPath(row: row, section: 0)], with: .automatic) } return cell } }
这里的getCatDetail和getDogDetail是你自己实现的内容映射方法,比如:
func getCatDetail(for breed: String) -> (age: String, trait: String) { switch breed { case "英短": return ("3-5岁", "温顺亲人,适应力极强") case "美短": return ("2-4岁", "活泼好动,智商在线") case "布偶": return ("4-6岁", "软萌粘人,玻璃胃需注意") default: return ("未知", "暂无详细信息") } }
4. 处理用户自定义行数的逻辑
比如用户点击“添加猫咪行”按钮时,直接往数据源数组里加一个空模型,再刷新TableView:
@IBAction func addCatRowBtnTapped(_ sender: UIButton) { catRowItems.append(CatRowItem()) tableA.reloadData() }
狗狗的添加逻辑同理,往dogRowItems里加空模型即可。
关键注意事项
- 一定要用
weak self避免循环引用(Cell持有回调,ViewController持有Cell,回调又持有ViewController,很容易出现内存泄漏) - 刷新行时用
reloadRows而不是reloadData,只刷新选中的那一行,体验更流畅 - 在Cell的
configure方法里恢复Picker的选中状态,避免TableView滚动时Cell复用导致选中状态错乱
这样一套流程下来,每个PickerView的选中逻辑就清晰了,选中后自动更新对应行的内容完全没问题~
内容的提问来源于stack exchange,提问作者Wan
相关产品推荐
相关产品推荐

