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

如何让UITableViewController支持任意符合协议的Swift枚举作为数据源?

Got it, let's tackle this problem step by step. The core issue here is that your data property in ChoiceListTableViewController can't access the static properties of your enums because static members are tied to the type itself, not an instance. Here's how to fix this using Swift protocols and generics:


Step 1: Refine Your TableViewSelectable Protocol

First, update your protocol to explicitly declare the static properties/methods you need to access. This ensures any enum conforming to it will expose these static members for your table view to use.

// Option 1: Use Swift's built-in CaseIterable for auto-generated allCases
protocol TableViewSelectable: CaseIterable {
    var displayName: String { get } // For cell text
    var key: String { get } // If you need a backend/identifier value
}

// Option 2: Define a custom static array if you don't want CaseIterable
protocol TableViewSelectable {
    static var allItems: [Self] { get } // Your custom static data source
    var displayName: String { get }
    var key: String { get }
}

Step 2: Make ChoiceListTableViewController Generic

Convert your table view controller to a generic class, with its type constrained to TableViewSelectable. This lets you directly access the static properties of the enum type tied to the controller instance.

class ChoiceListTableViewController<T: TableViewSelectable>: UITableViewController {
    // Track selected item (optional)
    var selectedItem: T?
    // Callback for when user selects an item
    var onSelection: ((T) -> Void)?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "SelectionCell")
    }
    
    // MARK: UITableView Data Source
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // Access the static data source from the generic type
        return T.allItems.count // Use T.allCases.count if you chose Option 1
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "SelectionCell", for: indexPath)
        let item = T.allItems[indexPath.row] // Or T.allCases[indexPath.row]
        
        cell.textLabel?.text = item.displayName
        // Add checkmark for selected item
        cell.accessoryType = (selectedItem == item) ? .checkmark : .none
        
        return cell
    }
    
    // MARK: UITableView Delegate
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let selectedItem = T.allItems[indexPath.row]
        self.selectedItem = selectedItem
        onSelection?(selectedItem)
        
        tableView.reloadData()
        tableView.deselectRow(at: indexPath, animated: true)
    }
}

Step 3: Conform Your Enums to the Protocol

Update your Genders, Goals, and other enums to follow the refined protocol:

// Example 1: String-backed enum using CaseIterable
enum Genders: String, TableViewSelectable {
    case male = "Male"
    case female = "Female"
    case other = "Other"
    
    var displayName: String { rawValue }
    var key: String { rawValue }
    // CaseIterable auto provides allCases, no need to define allItems
}

// Example 2: Non-string enum with custom display values
enum Goals: TableViewSelectable {
    case loseWeight
    case gainMuscle
    case maintain
    
    var displayName: String {
        switch self {
        case .loseWeight: return "Lose Weight"
        case .gainMuscle: return "Gain Muscle"
        case .maintain: return "Maintain"
        }
    }
    
    var key: String {
        switch self {
        case .loseWeight: return "lose_weight"
        case .gainMuscle: return "gain_muscle"
        case .maintain: return "maintain"
        }
    }
    
    static var allItems: [Goals] {
        [.loseWeight, .gainMuscle, .maintain]
    }
}

Step 4: Use the Generic Table View Controller

Now you can initialize instances of the controller for any enum conforming to TableViewSelectable:

// Initialize a gender selection controller
let genderVC = ChoiceListTableViewController<Genders>()
genderVC.selectedItem = .male
genderVC.onSelection = { selectedGender in
    print("Selected gender: \(selectedGender.displayName)")
}

// Initialize a goal selection controller
let goalVC = ChoiceListTableViewController<Goals>()
goalVC.onSelection = { selectedGoal in
    print("Selected goal key: \(selectedGoal.key)")
}

Why This Works

Your original issue came from trying to access static enum properties via an instance (data). By using a generic controller tied to the enum type, you can directly reference the static members of that type—no instance needed. This approach keeps your code type-safe and reusable across all your TableViewSelectable enums.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:41:48