UISearchController搜索栏功能失效,无法过滤电影数据
问题:UISearchController无法过滤电影数据
我通过代码创建了UISearchController搜索栏,期望输入文本时能根据标题过滤已通过API获取的电影数据,但目前搜索栏无法实现过滤功能。
ViewModel代码
import Foundation import UIKit enum MoviesViewModelState { case loading case loaded([Movie]) case error var movies: [Movie] { switch self { case .loaded(let movies): return movies case .loading, .error: return [] } } } final class MoviesViewModel { private let apiManager: APIManaging var filteredMovie: [Movie] = [] init(apiManager: APIManaging = APIManager()) { self.apiManager = apiManager } var updatedState: (() -> Void)? var state: MoviesViewModelState = .loading { didSet { updatedState?() } } func fetchData() { apiManager.execute(Movie.topRated) { [weak self] result in switch result { case .success(let page): self?.state = .loaded(page.results) case .failure: self?.state = .error } } } } // MARK: - Search function. extension MoviesViewModel { public func inSearchMode(_ searchController: UISearchController) -> Bool { let isActive = searchController.isActive let searchText = searchController.searchBar.text ?? "" return isActive && !searchText.isEmpty } public func updateSearchController(searchBarText: String?) { self.filteredMovie = state.movies if let searchText = searchBarText?.lowercased() { guard !searchText.isEmpty else { return } self.filteredMovie = self.filteredMovie.filter({ $0.title.lowercased().contains(searchText) }) } } }
ViewController代码
import UIKit final class MoviesViewController: UITableViewController { private let viewModel: MoviesViewModel // MARK: - UI Components private let searchController = UISearchController(searchResultsController: nil) init(viewModel: MoviesViewModel) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() title = LocalizedString(key: "movies.title") NotificationCenter.default.addObserver(self, selector: #selector(textSizeChanged), name: UIContentSizeCategory.didChangeNotification, object: nil) setupSearchController() configureTableView() updateFromViewModel() bindViewModel() viewModel.fetchData() } private func configureTableView() { tableView.dm_registerClassWithDefaultIdentifier(cellClass: MovieCell.self) tableView.rowHeight = UITableView.automaticDimension refreshControl = UIRefreshControl() refreshControl?.addTarget(self, action: #selector(refreshData), for: .valueChanged) } private func bindViewModel() { viewModel.updatedState = { [weak self] in guard let self else { return } DispatchQueue.main.async { self.updateFromViewModel() } } } private func updateFromViewModel() { switch viewModel.state { case .loading, .loaded: tableView.reloadData() case .error: showError() } refreshControl?.endRefreshing() } // MARK: setUpSearch Property. private func setupSearchController() { self.searchController.searchResultsUpdater = self self.searchController.obscuresBackgroundDuringPresentation = false self.searchController.hidesNavigationBarDuringPresentation = false self.searchController.searchBar.placeholder = "Search Movie" self.navigationItem.searchController = searchController self.definesPresentationContext = false self.navigationItem.hidesSearchBarWhenScrolling = false searchController.delegate = self searchController.searchBar.delegate = self searchController.searchBar.showsBookmarkButton = true searchController.searchBar.setImage(UIImage(systemName: "line.horizontal.3.decrease"), for: .bookmark, state: .normal) } private func showError() { let alertController = UIAlertController(title: "", message: LocalizedString(key: "movies.load.error.body"), preferredStyle: .alert) let alertAction = UIAlertAction(title: LocalizedString(key: "movies.load.error.actionButton"), style: .default, handler: nil) alertController.addAction(alertAction) present(alertController, animated: true, completion: nil) } @objc private func refreshData() { viewModel.fetchData() } @objc private func textSizeChanged() { tableView.reloadData() } } // MARK: - UITableViewDataSource extension MoviesViewController { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let inSearchMode = self.viewModel.inSearchMode(searchController) return inSearchMode ? self.viewModel.filteredMovie.count : self.viewModel.state.movies.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: MovieCell = tableView.dm_dequeueReusableCellWithDefaultIdentifier() let inSearchMode = self.viewModel.inSearchMode(searchController) let movie = inSearchMode ? self.viewModel.filteredMovie[indexPath.row] : self.viewModel.state.movies[indexPath.row] cell.configure(movie) return cell } } // MARK: - UITableViewControllerDelegate extension MoviesViewController { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let movie = viewModel.state.movies[indexPath.row] let viewModel = MoviesDetailsViewModel(movie: movie, apiManager: APIManager()) let viewController = MovieDetailsViewController(viewModel: viewModel) self.navigationController?.pushViewController(viewController, animated: true) } } // MARK: - Search Controller Functions extension MoviesViewController: UISearchResultsUpdating, UISearchControllerDelegate, UISearchBarDelegate { func updateSearchResults(for searchController: UISearchController) { self.viewModel.updateSearchController(searchBarText: searchController.searchBar.text) } }
问题原因分析
- 过滤后未触发表格刷新:ViewModel修改
filteredMovie后,没有通知ViewController更新表格。只有state属性变化时才会调用updatedState回调,过滤操作不会改变state,导致表格数据无法更新。 - 搜索模式下选中逻辑错误:点击表格行时,无论是否处于搜索模式,都直接从原始电影数组取数据,会导致选中的电影与显示的电影不匹配。
- 搜索控制器上下文设置错误:
definesPresentationContext设为false可能导致搜索控制器展示异常,影响功能触发。
修复方案
1. 让过滤操作触发表格刷新
修改ViewModel的updateSearchController方法,在过滤完成后调用updatedState?():
public func updateSearchController(searchBarText: String?) { self.filteredMovie = state.movies if let searchText = searchBarText?.lowercased() { guard !searchText.isEmpty else { updatedState?() return } self.filteredMovie = self.filteredMovie.filter({ $0.title.lowercased().contains(searchText) }) } updatedState?() }
2. 修正表格选中逻辑
修改ViewController的didSelectRowAt方法,根据搜索模式选择对应数组:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let inSearchMode = self.viewModel.inSearchMode(searchController) let movie = inSearchMode ? self.viewModel.filteredMovie[indexPath.row] : self.viewModel.state.movies[indexPath.row] let viewModel = MoviesDetailsViewModel(movie: movie, apiManager: APIManager()) let viewController = MovieDetailsViewController(viewModel: viewModel) self.navigationController?.pushViewController(viewController, animated: true) }
3. 修正搜索控制器上下文设置
在setupSearchController方法中,将definesPresentationContext设为true:
private func setupSearchController() { // 其他代码保持不变 self.definesPresentationContext = true // 其他代码保持不变 }
内容的提问来源于stack exchange,提问作者user21666271
相关产品推荐
相关产品推荐

