Swift MVVM架构下TableView搜索结果点击跳转展示数据问题
Got it, let's walk through how to make this navigation and data handoff work smoothly in your Swift MVVM setup. Here's a step-by-step approach tailored to your three-page structure:
1. Define a Unified Source Identifier for Search Results
First, you need a way to track where each search item comes from (foodlist or menulist) and carry its corresponding data. Create an enum to encapsulate this:
enum SearchItemSource { case food(FoodModel) // Replace with your actual FoodModel type case menu(MenuModel) // Replace with your actual MenuModel type }
Update your SearchingListModel to include this source property—so every search result holds a reference to its original model and which list it belongs to.
2. Add a Selection Callback in SearchingListViewModel
In MVVM, ViewModels handle business logic, so we'll add a closure to notify the ViewController when an item is selected:
class SearchingListViewModel { // Your existing properties (like searchResults array) var searchResults: [SearchingListModel] = [] // Closure to trigger navigation var onItemSelected: ((SearchItemSource) -> Void)? // Call this when a table cell is tapped func handleItemSelection(at indexPath: IndexPath) { let selectedItem = searchResults[indexPath.row] onItemSelected?(selectedItem.source) } }
3. Bind the Callback and Handle Navigation in SearchingListViewController
In your SearchingListViewController, connect the ViewModel's closure to trigger navigation. Depending on the source, instantiate the correct ViewController and pass the data:
class SearchingListViewController: UIViewController { private let viewModel = SearchingListViewModel() @IBOutlet weak var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() setupViewModelBindings() // Your existing table setup code } private func setupViewModelBindings() { viewModel.onItemSelected = { [weak self] source in guard let self = self else { return } switch source { case .food(let selectedFood): // Instantiate FoodListViewController (adjust for Storyboards or code-based) let foodVC = FoodListViewController() // Pass the selected food to its ViewModel foodVC.viewModel.selectedFood = selectedFood // Push to navigation stack (ensure this VC is in a UINavigationController) self.navigationController?.pushViewController(foodVC, animated: true) case .menu(let selectedMenu): // Repeat the same pattern for MenuList if needed let menuVC = MenuListViewController() menuVC.viewModel.selectedMenu = selectedMenu self.navigationController?.pushViewController(menuVC, animated: true) } } } // TableView didSelectRowAt delegate method func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { viewModel.handleItemSelection(at: indexPath) tableView.deselectRow(at: indexPath, animated: true) } }
Note: If you're using Storyboards, replace the ViewController instantiation with storyboard?.instantiateViewController(withIdentifier: "YourStoryboardID") as! FoodListViewController.
4. Update FoodList to Display the Selected Data
Modify FoodListViewModel to receive the selected food and adjust the data source accordingly. You can either filter the list to show only the selected item, or scroll to its position in the full list:
Option A: Filter the List to Show Only the Selected Food
class FoodListViewModel { var allFoods: [FoodModel] = [] // Your full food dataset var filteredFoods: [FoodModel] = [] var selectedFood: FoodModel? { didSet { if let food = selectedFood { filteredFoods = [food] // Notify ViewController to reload the table onDataUpdated?() } } } var onDataUpdated: (() -> Void)? }
Option B: Scroll to the Selected Food in the Full List
class FoodListViewModel { var allFoods: [FoodModel] = [] var selectedFood: FoodModel? { didSet { if let food = selectedFood, let index = allFoods.firstIndex(where: { $0.id == food.id }) { selectedIndex = index onSelectedIndexReady?() } } } var selectedIndex: Int? var onSelectedIndexReady: (() -> Void)? }
Then update FoodListViewController to respond to these changes:
class FoodListViewController: UIViewController { let viewModel = FoodListViewModel() @IBOutlet weak var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() setupViewModelBindings() } private func setupViewModelBindings() { // For Option A: Reload table when filtered data changes viewModel.onDataUpdated = { [weak self] in self?.tableView.reloadData() } // For Option B: Scroll to the selected row viewModel.onSelectedIndexReady = { [weak self] in guard let self = self, let index = self.viewModel.selectedIndex else { return } let indexPath = IndexPath(row: index, section: 0) self.tableView.scrollToRow(at: indexPath, at: .middle, animated: true) } } // Update your TableView data source methods to use viewModel.filteredFoods (Option A) or viewModel.allFoods (Option B) func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return viewModel.filteredFoods.count // Or viewModel.allFoods.count for Option B } }
Key Notes
- Ensure all your ViewControllers are embedded in a
UINavigationControllersopushViewControllerworks. - If you're using dependency injection (a common MVVM best practice), you can pass the ViewModel to the FoodListViewController instead of setting properties directly.
- Make sure your model types (
FoodModel,MenuModel) have a unique identifier (like anidproperty) to reliably find the correct item in the full list.
内容的提问来源于stack exchange,提问作者nahlaabdulnawal

