使用 UISearchController 时如何删除排序/过滤的项目

Posted

技术标签:

【中文标题】使用 UISearchController 时如何删除排序/过滤的项目【英文标题】:How to delete sorted/filtered items when using UISearchController 【发布时间】:2020-05-29 03:13:03 【问题描述】:

以下代码成功创建,在UITableView 中显示Cars 列表,您以后也可以在其中删除购物车。它还提供了一个UISearchController,您可以在其中成功执行搜索。

我的问题是在搜索/过滤后尝试删除汽车,例如,如果用户搜索位于数组中间的汽车,它将在第一行显示正确的汽车但是如果他/她决定删除它,它将删除cars 数组中的第一项,因为过滤后的项始终位于filteredCars 数组的顶部。在这里我没有收到错误,但它不会从cars 数组中删除正确的项目,它总是从cars 数组中删除第一项。

代码如下:

型号

class Car
    var make = ""
    var model = ""

视图控制器

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchResultsUpdating

    var cars =  Array<Car>()
    var filteredCars =  Array<Car>()
    let searchController = UISearchController(searchResultsController: nil)
    @IBOutlet weak var myTable: UITableView!

    override func viewDidLoad() 
        super.viewDidLoad()
        createCars()

        filteredCars = cars

        searchController.searchResultsUpdater = self
        searchController.obscuresBackgroundDuringPresentation = false
        searchController.searchBar.placeholder = "Search by model"
        navigationItem.searchController = searchController
        definesPresentationContext = true
    

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return filteredCars.count
    

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "TableCell")! as UITableViewCell
        cell.textLabel?.text = filteredCars[indexPath.row].model
        cell.detailTextLabel?.text = filteredCars[indexPath.row].make
        return cell
    
    func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? 

        let delete = UITableViewRowAction(style: .destructive, title: "Delete")  action, index in
            let alert = UIAlertController(title: "Delete selected car?", message: "This will permanently delete the selected car, do you want to continue?", preferredStyle: UIAlertController.Style.alert)

            alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertAction.Style.cancel, handler: nil))
            alert.addAction(UIAlertAction(title: "Yes", style: UIAlertAction.Style.destructive, handler:  action in
                self.filteredCars.remove(at: indexPath.row)
                self.cars.remove(at: indexPath.row)
                self.myTable.deleteRows(at: [indexPath], with: UITableView.RowAnimation.left)
            
            ))
            self.present(alert, animated: true, completion: nil)
        
        return [delete]
    

    func updateSearchResults(for searchController: UISearchController) 
        if let searchText = searchController.searchBar.text 
            filteredCars = searchText.isEmpty ? cars : cars.filter((dataString: Car) -> Bool in
                return dataString.model.lowercased().contains(searchText.lowercased())
            )
            myTable.reloadData()
        
    
    // create cars manually for demonstration only      
    func createCars()
        let car1 = Car()
        car1.make = "Ford"
        car1.model = "Explorer"

        //... more cars here

        cars.append(contentsOf: [car1, car2, car3, car4])
    

我尝试了以下操作,但一直收到 Index out of range 错误。

func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? 
    let delete = UITableViewRowAction(style: .destructive, title: "Delete")  action, index in
        let alert = UIAlertController(title: "Delete selected car?", message: "This will permanently delete the selected car, do you want to continue?", preferredStyle: UIAlertController.Style.alert)

        alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertAction.Style.cancel, handler: nil))
        alert.addAction(UIAlertAction(title: "Yes", style: UIAlertAction.Style.destructive, handler:  action in

            self.filteredCars.remove(at: indexPath.row)
            self.myTable.deleteRows(at: [indexPath], with: UITableView.RowAnimation.left)

            for i in 0..<self.cars.count 
                if self.cars[i].model == modelToDelete
                    self.cars.remove(at:i)
                
            
        
        ))
        self.present(alert, animated: true, completion: nil)
    
    return [delete]

搜索后删除项目的正确逻辑是什么?

【问题讨论】:

【参考方案1】:

您必须在给定汽车的主数组中获取索引

alert.addAction(UIAlertAction(title: "Yes", style: UIAlertAction.Style.destructive, handler:  action in
     let carToDelete = self.filteredCars.remove(at: indexPath.row)
     self.cars.remove(at: self.cars.index(of: carToDelete)!)
     tableView.deleteRows(at: [indexPath], with: .left)

这要求Car 采用Equatable,这很容易实现。如果您将class 更改为struct,您将免费获得Equatable

class Car : Equatable 
    var make = ""
    var model = ""

    static func == (lhs: Car, rhs: Car) -> Bool 
        return lhs.make == rhs.make && lhs.model == rhs.model
    

并始终使用作为参数传递的表格视图实例。

【讨论】:

在隐式提供 Equatable 的情况下,最好尽可能选择 struct 而不是 class 顺便说一句,将默认值分配给属性作为不在场证明来编写初始化程序是不好的做法。 使用默认值,您永远无法声明常量 (let),使用初始化程序 init(make: String, model:String),值也不能是 nil 静态函数== 确定两个对象是否相等。如果名称是唯一的,则这可以只是一个名称,或者如果makemodel 相同,则答案中两个对象相等。在您的特定情况下,有一个 UUID – 它是唯一的 – 因此,如果两个对象具有相同的 id,则将它们视为相等就足够了,只需 return lhs.id == rhs.id 是的,但这是另一个懒惰的坏习惯。 Swift 的轻量值类型(struct)比 Objective-C 的重引用类型(NSObject 类)更高效。

以上是关于使用 UISearchController 时如何删除排序/过滤的项目的主要内容,如果未能解决你的问题,请参考以下文章

如何在使用 popViewControllerAnimated(true) 导航出 UITableView 时修复 UISearchController 的警告

使用ios 8 UISearchController单击搜索栏时如何隐藏部分标题?

聚焦时如何调整 UISearchController.searchBar 的大小问题?

聚焦时如何修复 UISearchController 搜索栏调整大小?

如何使用 UISearchController 移除 UINavigationBar 下方的阴影

将 UISegmentedControl 与 UISearchController 一起使用