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

Swift3中基于MVVM实现TextField触发TableView搜索的方法

Great question! Implementing search with MVVM in UIKit means keeping all data filtering logic in your ViewModel, while the ViewController handles UI interactions and updates. Let's break this down step by step, building on your existing ViewModel code.

Step 1: Update the ViewModel

We'll add properties to manage filtered data, the search query, and a way to notify the ViewController when data changes. I'll show two approaches—one using a closure for simple cases, and another using Combine for reactive updates (iOS 13+).

Option A: Closure-based approach (compatible with older iOS versions)

class ViewModel: NSObject {
    var datasourceModel: DataSourceModel
    // Holds the filtered results for display
    private(set) var filteredModels: [Model] = []
    // Closure to notify the View when data updates
    var onDataUpdated: (() -> Void)?
    
    // Search text that triggers filtering when changed
    var searchText: String = "" {
        didSet {
            filterData()
        }
    }
    
    init(withdatasource newDatasourceModel: DataSourceModel) {
        datasourceModel = newDatasourceModel
        super.init()
        // Initialize with full dataset
        filteredModels = datasourceModel.models // Assume your DataSourceModel has a `models` array of [Model]
        onDataUpdated?()
    }
    
    // Core filtering logic—adjust based on your Model's properties
    private func filterData() {
        guard !searchText.isEmpty else {
            // Reset to full data when search is empty
            filteredModels = datasourceModel.models
            onDataUpdated?()
            return
        }
        
        // Example: Filter by model's name (case-insensitive)
        filteredModels = datasourceModel.models.filter { model in
            model.name.lowercased().contains(searchText.lowercased())
        }
        onDataUpdated?()
    }
    
    // Updated method to get display data (uses filtered results)
    func dataForDisplay(at indexPath: IndexPath) -> Model {
        return filteredModels[indexPath.row]
    }
    
    // Helper to get row count for TableView
    func numberOfRows() -> Int {
        return filteredModels.count
    }
}

Option B: Combine-based approach (reactive, iOS 13+)

If you're targeting iOS 13 or later, using Combine makes the data flow cleaner and adds nice features like debouncing to avoid frequent filtering:

import Combine

class ViewModel: NSObject {
    var datasourceModel: DataSourceModel
    @Published private(set) var filteredModels: [Model] = []
    @Published var searchText: String = ""
    
    private var cancellables = Set<AnyCancellable>()
    
    init(withdatasource newDatasourceModel: DataSourceModel) {
        datasourceModel = newDatasourceModel
        super.init()
        filteredModels = datasourceModel.models
        setupBindings()
    }
    
    private func setupBindings() {
        $searchText
            // Add a 300ms delay to avoid filtering on every keystroke
            .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
            .sink { [weak self] text in
                self?.filterData(with: text)
            }
            .store(in: &cancellables)
    }
    
    private func filterData(with text: String) {
        guard !text.isEmpty else {
            filteredModels = datasourceModel.models
            return
        }
        
        filteredModels = datasourceModel.models.filter {
            $0.name.lowercased().contains(text.lowercased())
        }
    }
    
    func dataForDisplay(at indexPath: IndexPath) -> Model {
        return filteredModels[indexPath.row]
    }
    
    func numberOfRows() -> Int {
        return filteredModels.count
    }
}

Step 2: Update the ViewController

Now we'll connect the TextField to the ViewModel, and link the TableView to the filtered data.

Closure-based ViewController

class ViewController: UIViewController {
    // UI Elements
    private let searchTextField = UITextField()
    private let tableView = UITableView()
    
    // ViewModel instance
    private var viewModel: ViewModel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        setupViewModel()
        setupTextField()
        setupTableView()
    }
    
    private func setupUI() {
        // Add and layout UI elements (using auto layout)
        searchTextField.placeholder = "Search..."
        searchTextField.borderStyle = .roundedRect
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        
        view.addSubview(searchTextField)
        view.addSubview(tableView)
        
        searchTextField.translatesAutoresizingMaskIntoConstraints = false
        tableView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            searchTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
            searchTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            searchTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
            
            tableView.topAnchor.constraint(equalTo: searchTextField.bottomAnchor, constant: 16),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
        ])
    }
    
    private func setupViewModel() {
        // Replace with your actual DataSourceModel initialization (from your API load)
        let dataSourceModel = DataSourceModel()
        viewModel = ViewModel(withdatasource: dataSourceModel)
        
        // Listen for data updates to reload TableView
        viewModel.onDataUpdated = { [weak self] in
            DispatchQueue.main.async {
                self?.tableView.reloadData()
            }
        }
    }
    
    private func setupTextField() {
        // Update ViewModel's searchText when TextField changes
        searchTextField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
    }
    
    private func setupTableView() {
        tableView.dataSource = self
        tableView.delegate = self
    }
    
    @objc private func textFieldDidChange() {
        viewModel.searchText = searchTextField.text ?? ""
    }
}

// MARK: - UITableView DataSource & Delegate
extension ViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return viewModel.numberOfRows()
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        let model = viewModel.dataForDisplay(at: indexPath)
        cell.textLabel?.text = model.name // Replace with your Model's display property
        return cell
    }
}

Combine-based ViewController

If you used the Combine ViewModel, adjust the setupViewModel method and add a cancellables set:

class ViewController: UIViewController {
    // ... (same UI setup as above)
    
    private var cancellables = Set<AnyCancellable>()
    
    private func setupViewModel() {
        let dataSourceModel = DataSourceModel()
        viewModel = ViewModel(withdatasource: dataSourceModel)
        
        // Subscribe to filteredModels changes
        viewModel.$filteredModels
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in
                self?.tableView.reloadData()
            }
            .store(in: &cancellables)
    }
    
    // ... (rest of the code remains the same)
}

Key Notes

  • MVVM Separation: All filtering logic lives in the ViewModel—your ViewController doesn't know anything about how data is filtered, it just displays what the ViewModel provides.
  • Filtering Logic: Adjust the filterData method to search the properties relevant to your Model (e.g., description, ID, etc.).
  • Retain Cycles: Always use [weak self] in closures/subscriptions to avoid memory leaks.
  • Debouncing: The Combine approach includes a debounce to prevent excessive filtering while the user is typing—you can add this to the closure approach using a timer if needed.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:50:26