I have implement search functionality on table view and it working fine and able to filter the data. while data is filtering , the table view cell is update and reloaded correctly. When I done typing data into search bar and select the filtered cell , I am expecting to show selected filtered cell data into details view controller when the didSelectRowAt function is called. But it not showing the data from filtered cell , it showing different cell data.
Here is my view model code where I have search function.
// MARK: - Search function.
enum MoviesViewModelState {
case loading
case loaded([Movie])
case error
var movies: [Movie] {
switch self {
case .loaded(let movies):
return movies
case .loading, .error:
return []
}
}
}
/// Calling API for movie data. here
final class MoviesViewModel {
/// API call is happening here.
}
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) })
}
}
}
Here is the table view controller code ..
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")
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.tintColor = .purple
searchController.searchBar.setImage(UIImage(named: "Filter"), for: .bookmark, state: .normal)
searchController.searchBar.setLeftImage(UIImage(named: "Search"))
searchController.searchBar.showsBookmarkButton = true
}
@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)
tableView.reloadData()
}
}
Here is the screenshot of the filtering data.. when I select the first filtered cell (the Godfather) , I am expecting to show the details of that cell into details view controller.
[
But here is the result when I select the filtered cell and it showing the wrong data into details view controller.
