当前位置: > > > Swift - 使用UISearchController实现带搜索栏的表格

Swift - 使用UISearchController实现带搜索栏的表格

(本文代码已升级至Swift3)

    我原来写过一篇文章“Swift - 带结果列表的搜索条(UISearchDisplayController)的用法”,当时是使用UISearchDisplayController来实现带有搜索功能的列表,由于UISearchDisplayController本身就整合了搜索条和表格,所有用起来很方便。
 
    到了iOS8,苹果废除UISearchDisplayController,建议我们使用UISearchController配合UITableView来实现。我们可以把搜索条放在表格头部,或者放在页面顶部,还是很灵活的。下面通过代码演示如何使用UISearchController实现具有搜索功能的表格。

效果图如下:
   

样例代码说明:
(1)这里对ViewController做了类扩展ViewControllerExtensions.swift,把UITableView和UISearchController的代理方法都写在扩展类里,使代码更加简洁。
(2)同时这里提供两个样例。分别对应两种搜索方式,一个是实时搜索(即每输入一个字符就搜索过滤一次结果)。一个是输入时不进行搜索,只有点击键盘上的搜索按钮后才开始搜索。

一、实时搜索

--- ViewController.swift ---
import UIKit

class ViewController: UIViewController {
    
    //展示列表
    var tableView: UITableView!
    
    //搜索控制器
    var countrySearchController = UISearchController()
    
    //原始数据集
    let schoolArray = ["清华大学","北京大学","中国人民大学","北京交通大学","北京工业大学",
                       "北京航空航天大学","北京理工大学","北京科技大学","中国政法大学",
                       "中央财经大学","华北电力大学","北京体育大学","上海外国语大学","复旦大学",
                       "华东师范大学","上海大学","河北工业大学"]
    
    //搜索过滤后的结果集
    var searchArray:[String] = [String](){
        didSet  {self.tableView.reloadData()}
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //创建表视图
        let tableViewFrame = CGRect(x: 0, y: 20, width: self.view.frame.width,
                                    height: self.view.frame.height-20)
        self.tableView = UITableView(frame: tableViewFrame, style:.plain)
        self.tableView!.delegate = self
        self.tableView!.dataSource = self
        //创建一个重用的单元格
        self.tableView!.register(UITableViewCell.self,
                                      forCellReuseIdentifier: "MyCell")
        self.view.addSubview(self.tableView!)
        
        //配置搜索控制器
        self.countrySearchController = ({
            let controller = UISearchController(searchResultsController: nil)
            controller.searchResultsUpdater = self   //两个样例使用不同的代理
            controller.hidesNavigationBarDuringPresentation = false
            controller.dimsBackgroundDuringPresentation = false
            controller.searchBar.searchBarStyle = .minimal
            controller.searchBar.sizeToFit()
            self.tableView.tableHeaderView = controller.searchBar
            
            return controller
        })()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(true)
        self.tableView.reloadData()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

--- ViewControllerExtensions.swift ---
import Foundation
import UIKit

extension ViewController: UITableViewDataSource
{
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if self.countrySearchController.isActive {
            return self.searchArray.count
        } else {
            return self.schoolArray.count
        }
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
        -> UITableViewCell {
        //为了提供表格显示性能,已创建完成的单元需重复使用
        let identify:String = "MyCell"
        //同一形式的单元格重复使用,在声明时已注册
        let cell = tableView.dequeueReusableCell(withIdentifier: identify,
                                                 for: indexPath)
        
        if self.countrySearchController.isActive {
            cell.textLabel?.text = self.searchArray[indexPath.row]
            return cell
        } else {
            cell.textLabel?.text = self.schoolArray[indexPath.row]
            return cell
        }
    }
}

extension ViewController: UITableViewDelegate
{
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }
}

extension ViewController: UISearchResultsUpdating
{
    //实时进行搜索
    func updateSearchResults(for searchController: UISearchController) {
        self.searchArray = self.schoolArray.filter { (school) -> Bool in
            return school.contains(searchController.searchBar.text!)
        }
    }
}

二、点击搜索按钮后才进行搜索

--- ViewController.swift ---
import UIKit

class ViewController: UIViewController {
    
    //展示列表
    var tableView: UITableView!
    
    //搜索控制器
    var countrySearchController = UISearchController()
    
    //原始数据集
    let schoolArray = ["清华大学","北京大学","中国人民大学","北京交通大学","北京工业大学",
                       "北京航空航天大学","北京理工大学","北京科技大学","中国政法大学",
                       "中央财经大学","华北电力大学","北京体育大学","上海外国语大学","复旦大学",
                       "华东师范大学","上海大学","河北工业大学"]
    
    //搜索过滤后的结果集
    var searchArray:[String] = [String](){
        didSet  {self.tableView.reloadData()}
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //创建表视图
        let tableViewFrame = CGRect(x: 0, y: 20, width: self.view.frame.width,
                                    height: self.view.frame.height-20)
        self.tableView = UITableView(frame: tableViewFrame, style:.plain)
        self.tableView!.delegate = self
        self.tableView!.dataSource = self
        //创建一个重用的单元格
        self.tableView!.register(UITableViewCell.self,
                                 forCellReuseIdentifier: "MyCell")
        self.view.addSubview(self.tableView!)
        
        //配置搜索控制器
        self.countrySearchController = ({
            let controller = UISearchController(searchResultsController: nil)
            controller.searchBar.delegate = self  //两个样例使用不同的代理
            controller.hidesNavigationBarDuringPresentation = false
            controller.dimsBackgroundDuringPresentation = false
            controller.searchBar.searchBarStyle = .minimal
            controller.searchBar.sizeToFit()
            self.tableView.tableHeaderView = controller.searchBar
            
            return controller
        })()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(true)
        self.tableView.reloadData()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

--- ViewControllerExtensions.swift ---
import Foundation
import UIKit

extension ViewController: UITableViewDataSource
{
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if self.countrySearchController.isActive {
            return self.searchArray.count
        } else {
            return self.schoolArray.count
        }
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
        -> UITableViewCell {
            //为了提供表格显示性能,已创建完成的单元需重复使用
            let identify:String = "MyCell"
            //同一形式的单元格重复使用,在声明时已注册
            let cell = tableView.dequeueReusableCell(withIdentifier: identify,
                                                     for: indexPath)
            
            if self.countrySearchController.isActive {
                cell.textLabel?.text = self.searchArray[indexPath.row]
                return cell
            } else {
                cell.textLabel?.text = self.schoolArray[indexPath.row]
                return cell
            }
    }
}

extension ViewController: UITableViewDelegate
{
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }
}

extension ViewController: UISearchBarDelegate {
    //点击搜索按钮
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        self.searchArray = self.schoolArray.filter { (school) -> Bool in
            return school.contains(searchBar.text!)
        }
    }
    
    //点击取消按钮
    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        self.searchArray = self.schoolArray
    }
}
评论10
  • 10楼
    2017-09-21 10:11
    Eval

    你好,抱歉,我是新手入门。请问类拓展ViewControllerExtensions.swift是添加的那个文件。

    站长回复

    直接添加个“Swift File”就可以了。

  • 9楼
    2017-05-10 22:47
    nothor

    你好,我想知道原始数据如果不是schoolArray 而是一个表的话应该怎么设置呢?

    站长回复

    原始数据要么是数组,要么是集合,都是通过遍历过滤来实现搜索的。不知你说的一个表是指什么?

  • 8楼
    2017-04-08 15:33
    翀鹰女孩

    你好 可以把这个demo发我一份吗 2807338860@qq.com

    站长回复

    文章里的代码都是全的,你新建个工程复制进去就可以了。

  • 7楼
    2016-11-03 18:09
    young

    你好 我添加这个的时候,报错
    controller.searchResultsUpdater = self //两个样例使用不同的代理


    运行时,编译到这里就闪退了
    if self.countrySearchController.isActive
    怎么解决

    站长回复

    我又测试了下代码,是没问题的啊。我也不知道你那边是什么情况了。

  • 6楼
    2016-08-31 14:43
    落花流水

    请问怎么取消实时搜索,改成通过点击键盘上的搜索按钮进行搜索。

    站长回复

    我文章更新了,现增加了个点击搜索按钮进行搜索的样例,你可以看下。

  • 5楼
    2016-07-14 00:01
    您好

    您好
    如果开启showsScopeBar,特别是将searchbar放到导航栏位置的话,显示会有问题,showsScopeBar整个被压在searchbar下面
    再就是开启cancel按钮的话也会出现问题 最开始会显示 但如果点击搜索栏进入搜索模式后(搜索栏会自己弹出一个返回作用的cancel) 再返回去的话之前的cancel按钮就没有了
    请问这两个问题有没有办法解决呢 谢谢

    站长回复

    你指的是把searchbar放到导航栏里并开启scopeBar吗?这个样做就直接超过导航栏高度,自然会重叠起来,不建议导航栏里的searchBar开启scopeBar。 

    不过把searchBar放到tableview的tableHeaderView里,并开启scopeBar倒是没问题的。

  • 4楼
    2015-12-29 21:47
    ysfshine

    这样还不行,报如下错误
    Connot invoke 'filter' with an argument lits of type '(@noescape(Element) throws -> Bool)'

    语句为
    self.requestSearch = self.requestData.filter(){$0.name.rangeOfString(searchController.searchBar.text!) != nil }
    我的数据是这样的
    let requestData = NSMutableArray()
    requestData.addObject(TradeItem(name: "名称1", model: "款式", color: "颜色", size: "尺码"))
    requestData.addObject(TradeItem(name: "名称2", model: "款式", color: "颜色", size: "尺码"))
    requestData.addObject(TradeItem(name: "名称3", model: "款式", color: "颜色", size: "尺码"))
    requestData.addObject(TradeItem(name: "名称4", model: "款式", color: "颜色", size: "尺码"))
    requestData.addObject(TradeItem(name: "名称11", model: "款式", color: "颜色", size: "尺码"))
    是定义的对象

    站长回复

    建议直接使用Array放置数据(不要用NSMutableArray),比如:
    var requestData = [School]()
    requestData.append(School(name: "1清华大学3", address: "北京"))
    requestData.append(School(name: "2北京大学3", address: "北京"))
    requestData.append(School(name: "3圣地亚哥大学", address: "未知"))
    就没问题了

  • 3楼
    2015-12-24 09:09
    ysfshine

    搜索的内容 是一个对象 应该如何搜索?
    let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %@",
    searchController.searchBar.text!)
    let array = (self.schoolArray as NSArray)
    .filteredArrayUsingPredicate(searchPredicate)
    这段应该如何写》?

    站长回复

    假设schoolArray里是对象,根据对象中的name属性过滤:
    self.searchArray = self.schoolArray.filter() { $0.name.rangeOfString(searchController.searchBar.text!) != nil }

  • 2楼
    2015-12-24 08:09
    Yue

    你理解错了
    我要搜索框放在导航栏上

    站长回复

    设成导航栏的titleView即可。
    self.navigationItem.titleView = controller.searchBar

  • 1楼
    2015-12-23 15:10
    yue

    我的 ViewController 导航上加了UINavigationController 搜索框 没有在上面, 请问如何放上去?

    站长回复

    是不是tableView头部被导航栏盖住了,你把tableView的y坐标移到导航栏下方即可。