如何通过Delegate实现多按钮数据过滤?类型转换错误排查
Hey there! Let's fix that error first, then we can even clean up the code to be more maintainable.
What's Causing the Error?
The error Cannot convert value of type 'RoundButton?' to expected argument type 'String?' happens because your acceptSelections method is passing button instances (jewelryBtn, hatBtn, etc.) to your delegate method—but that method expects optional String values (the actual category names), not the buttons themselves.
Step 1: Fix the Immediate Error
Update your acceptSelections method in FilterViewController to pass category strings (instead of buttons) based on each button's selected state:
@IBAction func acceptSelections(_ sender: Any) { // Map each button's selected state to its category string (or nil if not selected) let selectedJewelry = jewelryBtn.isSelected ? "Jewelry" : nil let selectedHat = hatBtn.isSelected ? "Hat" : nil let selectedShoe = shoeBtn.isSelected ? "Shoe" : nil let selectedGear = gearBtn.isSelected ? "Gear" : nil let selectedApparel = apparelBtn.isSelected ? "Apparel" : nil // Now pass the correct string values to the delegate delegate?.controller(self, didSelectCategory: selectedJewelry, hat: selectedHat, shoe: selectedShoe, gear: selectedGear, apparel: selectedApparel) dismiss(animated: true) }
This will resolve the type mismatch error immediately.
Bonus: Clean Up Your Delegate Pattern (Optional but Recommended)
Your current delegate method has 5 separate optional parameters, which can get messy as you add more categories. Let's simplify it to pass a set of selected categories instead:
1. Update the FiltersViewControllerDelegate Protocol
protocol FiltersViewControllerDelegate: NSObjectProtocol { // Pass a Set of selected category names (nil if none are selected) func controller(_ controller: FilterViewController, didSelectCategories categories: Set<String>?) }
2. Update acceptSelections in FilterViewController
@IBAction func acceptSelections(_ sender: Any) { var selectedCategories = Set<String>() // Add category names to the set if their corresponding button is selected if jewelryBtn.isSelected { selectedCategories.insert("Jewelry") } if hatBtn.isSelected { selectedCategories.insert("Hat") } if shoeBtn.isSelected { selectedCategories.insert("Shoe") } if gearBtn.isSelected { selectedCategories.insert("Gear") } if apparelBtn.isSelected { selectedCategories.insert("Apparel") } // Pass nil if no categories are selected, otherwise pass the set delegate?.controller(self, didSelectCategories: selectedCategories.isEmpty ? nil : selectedCategories) dismiss(animated: true) }
3. Update the Delegate Implementation in HomeViewController
This makes your filtering logic cleaner and easier to maintain:
extension HomeViewController: FiltersViewControllerDelegate{ func query(withCategories categories: Set<String>?) -> Query { var filteredQuery = baseQuery guard let categories = categories, !categories.isEmpty else { // No filters selected: hide the active filters bar stackViewHeightConstraint.constant = 0 activeFiltersStackView.isHidden = true return filteredQuery } // Show the active filters bar stackViewHeightConstraint.constant = 44 activeFiltersStackView.isHidden = false // Filter Firestore query to include any of the selected categories filteredQuery = filteredQuery.whereField("category", in: categories) return filteredQuery } func controller(_ controller: FilterViewController, didSelectCategories categories: Set<String>?) { let filteredQuery = query(withCategories: categories) self.query = filteredQuery // Update filter labels visibility and text jewelryFilterLbl.isHidden = !(categories?.contains("Jewelry") ?? false) jewelryFilterLbl.text = "Jewelry" hatFilterLbl.isHidden = !(categories?.contains("Hat") ?? false) hatFilterLbl.text = "Hat" shoeFilterLbl.isHidden = !(categories?.contains("Shoe") ?? false) shoeFilterLbl.text = "Shoe" gearFilterLbl.isHidden = !(categories?.contains("Gear") ?? false) gearFilterLbl.text = "Gear" apparelFilterLbl.isHidden = !(categories?.contains("Apparel") ?? false) apparelFilterLbl.text = "Apparel" // Fetch filtered products and refresh the table view fetchFilteredProducts() } // Add this method to fetch products based on the filtered query private func fetchFilteredProducts() { guard let query = query else { return } query.addSnapshotListener { [weak self] snapshot, error in guard let self = self, error == nil, let snapshot = snapshot else { self?.productSetup = [] self?.tableView.reloadData() return } // Parse and sort the filtered products self.productSetup = snapshot.documents.compactMap { ProductList(dictionary: $0.data()) } .sorted(by: { $0.itemName < $1.itemName }) // Refresh the table view self.tableView.reloadData() } } }
Important Note
I noticed your original code wasn't refreshing the table view after setting the filtered query. The fetchFilteredProducts method above fixes this by listening to the filtered query's changes and updating your product list + reloading the table.
内容的提问来源于stack exchange,提问作者Evelyn

