如何让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:
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 } }
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) } }
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] } }
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

