Reputation:
I have created the search bar programatically and I am expecting to filter the data when the text is enter into search bar. Problem is not filtering the data although I have data already downloaded form api call and tried to search the text based on the title.
Here is the view model code including the search function..
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) })
}
}
}
Here is the view controller code.
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)
}
}
Here is the screenshot ..
Upvotes: 0
Views: 46