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

MVVM架构下UITextField代理实现TableView搜索问题咨询

MVVM架构下TableView搜索功能完整实现指南

看起来你已经搭好了基础框架——ViewModel里写了搜索逻辑,ViewController也加了必要的代理,接下来咱们一步步把剩下的部分补全,确保功能稳定运行:

1. 先修复ViewModel里的潜在崩溃问题

你当前的search函数里直接强制解包searchText!,如果搜索文本为空或者nil,肯定会崩溃。咱们先把这个逻辑优化一下,同时处理空搜索的场景(展示全部数据):

func search(searchText: String?) {
    // 处理空搜索文本:直接返回全部数据
    guard let searchQuery = searchText, !searchQuery.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
        filteredListArray = datasourceModel.dataListArray
        return
    }
    // 非空时执行过滤,同时处理dataListArray为nil的情况
    filteredListArray = datasourceModel.dataListArray?.filter {
        $0.restaurantname?.range(of: searchQuery, options: .caseInsensitive) != nil
    } ?? []
}

2. 给ViewModel添加UI刷新通知机制

MVVM里ViewModel不能直接操作View,所以需要把filteredListArray变成可观察的属性,让ViewController能监听它的变化来刷新TableView。这里推荐用Combine的@Published(如果你的项目支持Combine):

import Combine

class YourViewModel {
    // 把filteredListArray改成@Published属性
    @Published var filteredListArray: [YourRestaurantModel]? = []
    var datasourceModel: YourDatasourceModel!

    // 初始化方法
    init(datasourceModel: YourDatasourceModel) {
        self.datasourceModel = datasourceModel
    }

    // 上面优化后的search函数
    func search(searchText: String?) {
        // ... 逻辑同上
    }
}

3. 在ViewController里绑定ViewModel并监听数据变化

首先确保ViewController持有ViewModel实例,然后在viewDidLoad里设置监听,当filteredListArray变化时自动刷新TableView:

class QM_SearchViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate { 
    @IBOutlet private weak var tableView: UITableView!
    @IBOutlet private weak var searchTextField: UITextField!
    
    // 持有ViewModel实例
    private var viewModel: YourViewModel!
    // 用来存储Combine订阅,防止被释放
    private var cancellables = Set<AnyCancellable>()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 初始化ViewModel(这里假设你已经有datasourceModel的实例)
        let datasource = YourDatasourceModel()
        viewModel = YourViewModel(datasourceModel: datasource)
        
        // 绑定ViewModel的filteredListArray变化,刷新TableView
        viewModel.$filteredListArray
            .receive(on: DispatchQueue.main) // 确保在主线程刷新UI
            .sink { [weak self] _ in
                self?.tableView.reloadData()
            }
            .store(in: &cancellables)
        
        // 初始加载全部数据
        viewModel.search(searchText: nil)
        
        // 设置代理
        searchTextField.delegate = self
        tableView.delegate = self
        tableView.dataSource = self
        // 注册TableViewCell(如果没在Storyboard里注册的话)
        tableView.register(YourTableViewCell.self, forCellReuseIdentifier: "YourCellID")
    }
}

4. 实现UITextField代理方法,触发搜索

我们需要监听搜索框的文本变化,调用ViewModel的search方法。这里用textFieldDidChangeSelection或者shouldChangeCharactersIn都可以,推荐前者更简洁:

// MARK: - UITextFieldDelegate
extension QM_SearchViewController {
    func textFieldDidChangeSelection(_ textField: UITextField) {
        // 把当前搜索框的文本传给ViewModel执行搜索
        viewModel.search(searchText: textField.text)
    }

    // 可选:处理搜索框的清除按钮点击
    func textFieldShouldClear(_ textField: UITextField) -> Bool {
        viewModel.search(searchText: nil)
        return true
    }
}

5. 让TableView使用ViewModel的过滤后数据

修改TableView的数据源方法,从viewModel.filteredListArray里取数据:

// MARK: - UITableViewDataSource & Delegate
extension QM_SearchViewController {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return viewModel.filteredListArray?.count ?? 0
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellID", for: indexPath) as! YourTableViewCell
        // 取出对应的数据模型
        guard let restaurant = viewModel.filteredListArray?[indexPath.row] else {
            return cell
        }
        // 给Cell赋值(假设你的Cell有configure方法)
        cell.configure(with: restaurant)
        return cell
    }

    // 可选:实现didSelectRowAt等代理方法
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        guard let selectedRestaurant = viewModel.filteredListArray?[indexPath.row] else {
            return
        }
        // 处理点击事件,比如跳转详情页
    }
}

额外优化建议

  • 如果你的项目不支持Combine,可以用闭包回调或者自定义Delegate来替换@Published的监听逻辑;
  • 可以给搜索添加防抖(比如延迟0.3秒再执行搜索),避免频繁调用过滤函数影响性能;
  • 当filteredListArray为空时,给TableView添加一个空状态占位视图,提升用户体验。

内容的提问来源于stack exchange,提问作者clydececiljohn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 03:50:17