You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
    }
}

问题原因分析

  1. 过滤后未触发表格刷新:ViewModel修改filteredMovie后,没有通知ViewController更新表格。只有state属性变化时才会调用updatedState回调,过滤操作不会改变state,导致表格数据无法更新。
  2. 搜索模式下选中逻辑错误:点击表格行时,无论是否处于搜索模式,都直接从原始电影数组取数据,会导致选中的电影与显示的电影不匹配。
  3. 搜索控制器上下文设置错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 23:45:05