user21666271
user21666271

Reputation:

SeachBar functionality is not working with UISearchController

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 ..

enter image description here

Upvotes: 0

Views: 46

Answers (0)

Related Questions