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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:17:49