在 iOS 8.0 之前实现搜索功能我们用的是UISearchBar 和UISearchDisplayController,但是在 iOS 8.0 之后上述方法已经被弃用,所以我们可以使用UISearchController进行替代.UISearchController一般用于搜索功能,通常的交互场景是在视图控制器一上有个SearchBar,不可输入,点击searchBar,展示视图控制器二此时searchBar变成可编辑状态,输入搜索内容,在视图控制器二里展示搜索提示,searchBar上有Cancel按钮,点击后返回视图控制器一。
UISearchController的基本概念
UISearchController对象基于与search bar的交互来管理搜索结果的显示。当我们的一个视图控制器有可搜索的内容时,可以在该视图控制器界面中使用包含search bar的UISearchController对象。当用户与search bar进行交互是,UISearchController将自动显示一个新的视图控制显示搜索结果。
UISearchController与我们提供的两个自定义视图控制器进行工作,第一个视图控制器显示搜索的内容,第二个视图控制器显示搜索的结果。第一个视图控制器是我们APP主界面的部分,可以使用任意方式显示在APP中。我们可以使用
init(searchResultsController:)方法初始化
UISearchController并传递第二个视图控制器,那么
UISearchController将在合适的时候显示该视图控制器。
每一个UISearchController都提供了一个
UISearchBar对象,我们必须在初始化视图控制器的时候将
UISearchBar合并到用户界面。添加
s
earchBar
到包含我们需要被搜索内容的视图,当用户点击s
earchBar输入搜索内容,
UISearchController将自动显示搜索结果视图控制器并且通知APP搜索过程已经开始。
当用户与
s
earchBar交互,
UISearchController将通知searchResultsUpdater属性对象,所以我们需要提供搜索结果更新对象(
searchResultsUpdater
),并且必须遵守
UISearchResultsUpdating协议。我们可以使用协议中的方法来操作搜索的内容并传递结果到搜索结果视图控制。很显然,视图控制器拥有可搜索的内容,所以当前视图控制器可以充当搜索结果更新对象(
searchResultsUpdater
),简单一点理解就是代理。当然也可以使用其它对象。
为了自定义显示和去除搜索结果视图控制(search results controller),可以赋值对象到搜索控制器的
delegate
属性,delegate
对象必须遵守UISearchControllerDelegate协议,当
search controller变得活跃并且搜索结果视图控制被显示(present)或去除(dismiss),协议中的方法会接收通知。
注意:虽然UISearchController对象是视图控制器,但是我们不应该直接present到我们的页面上。如果我们想显示的present搜索结果,需要将UISearchController包裹到
UISearchContainerViewController对象,然后present该对象。
以上内容信息请看这里。
相关方法和属性内容
初始化搜索控制器
public init(searchResultsController: UIViewController?)
在初始化UISearchController它要求我们要先设计好的searchResultsController这个控制器,它可以是UITableViewController,也可以是UICollectionViewController,也可以是nil。如果设为nil的话,那数据源和搜索结果共用一个Controller。
管理搜索结果
openvar searchBar: UISearchBar { get } 显示在界面上的searchBar
weak open var searchResultsUpdater: UISearchResultsUpdating? 该对象负责更新搜索结果视图控制器的内容
openvar searchResultsController: UIViewController? { get } 该视图控制器显示搜索结果
open var isActive: Bool 搜索界面呈现的状态
配置搜索界面
openvar dimsBackgroundDuringPresentation: Bool// default is YES, and has the same behavior as obscuresBackgroundDuringPresentation. 设置开始搜索时背景颜色是否显示
@available(iOS9.1, *)
openvar obscuresBackgroundDuringPresentation: Bool// default is YES
openvar hidesNavigationBarDuringPresentation: Bool// default is YES 设置开始搜索时导航条是否隐藏
访问代理
weakopenvar delegate: UISearchControllerDelegate? 就向前面解释,该代理对象将接收通知,当search results controller是 presented或者dismissed.也可以通知来自定义搜索界面。
上面详情介绍了UISearchController,下面看一下基本使用步骤:
1:初始化UISearchController,指定搜索结果视图控制器,设置相应的属性
2:设置代理,遵守UISearchResultsUpdating 协议
3:实现协议方法,更新搜索结果页面内容
具体Demo,原文例子请看这里,整体功能效果图和解释如下:
运行程序可以看到整个页面是UITableView列表,该列表的头视图就是我们的searchController中的searchBar,当我们点击searchBar的时候,显示搜索结果视图控制页面,由于最开始我们在初始化UISearchController的时候,传入了nil作为参数,所以搜索结果视图控制器就是当前视图控制器。因为filteredArray数组中并没有存储搜索结果内容,所以界面无数据显示。当我们输入内容之后,搜索结果视图控制器会根据我们输入的内容匹配相应的内容进行显示。点击Search按钮去除键盘。点击Cancel按钮将回到之前视图控制器。
下面看一下具体的代码:
代码部分为了方便阅读进行了适当的调整,这里主要是实现创建UISearchController并设置相应的属性,然后从本地文件加载数据并初始化数据源更新列表显示内容。
class SearchViewController: UIViewController{
@IBOutlet weak var tableView: UITableView!
var dataArray = [String]() //存储文件内容
var filteredArray = [String]() //存储搜索结果数据
var shouldShowSearchResults = false //是否显示搜索结果
var searchController: UISearchController!
override func viewDidLoad() {
super.viewDidLoad()
configureSearchController()
loadListOfCountries()
}
func configureSearchController(){
//通过参数searchResultsController传nil来初始化UISearchController,意思是我们告诉search controller我们会用相同的视图控制器来展示我们的搜索结果,如果我们想要指定一个不同的view controller,那就会被替代为显示搜索结果。
searchController = UISearchController(searchResultsController: nil)
//设置代理,searchResultUpdater是UISearchController的一个属性,它的值必须实现UISearchResultsUpdating协议,这个协议让我们的类在UISearchBar文字改变时被通知到,我们之后会实现这个协议。
searchController.searchResultsUpdater = self
//默认情况下,UISearchController暗化前一个view,这在我们使用另一个view controller来显示结果时非常有用,但当前情况我们并不想暗化当前view,即设置开始搜索时背景是否显示
searchController.dimsBackgroundDuringPresentation = false
//设置默认显示内容
searchController.searchBar.placeholder = "Search here..."
//设置searchBar的代理
searchController.searchBar.delegate = self
//设置searchBar自适应大小
searchController.searchBar.sizeToFit()
//设置definesPresentationContext为true,我们保证在UISearchController在激活状态下用户push到下一个view controller之后search bar不会仍留在界面上。
searchController.definesPresentationContext = true
//将searchBar设置为tableview的头视图
tableView.tableHeaderView = searchController.searchBar
}
func loadListOfCountries(){
//获取文件的指定路径
let pathToFile = Bundle.main.path(forResource: "countries", ofType: "txt")
//判断路径是否存在
if let path = pathToFile{
do{
//加载文件变成字符串
let countriesString = try String(contentsOfFile: path, encoding: .utf8)
//将字符串变为数组
dataArray = countriesString.components(separatedBy: "\n")
//刷新列表
tableView.reloadData()
}catch{
print(error.localizedDescription)
}
}
}
}
扩展SearchViewController实现UITableViewDelegate,UITableViewDataSource两个协议,并根据shouldShowSearchResults属性控制对应类型数据的显示,如果为true,那么显示搜索结果,如果为false,显示原列表。
//扩展SearchViewController实现UITableViewDelegate,UITableViewDataSource两个协议,并控制数据的显示
extension SearchViewController:UITableViewDelegate,UITableViewDataSource{
//MARK: UITableView Delegate and Datasource functions
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if shouldShowSearchResults {//是否显示搜索结果
return filteredArray.count
}
return dataArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "idCell", for: indexPath)
if shouldShowSearchResults{
cell.textLabel?.text = filteredArray[indexPath.row]
}else{
cell.textLabel?.text = dataArray[indexPath.row]
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60.0
}
}
扩展SearchViewController实现UISearchBarDelegate和UISearchResultsUpdating两个协议,并实现相应的协议方法,实现了:
1:开始进行文本编辑,设置显示搜索结果,刷新列表;
2:点击Cancel按钮,设置不显示搜索结果并刷新列表,回到最初视图控制器页面;
3:点击搜索按钮,触发该代理方法,如果已经显示搜索结果,那么直接去除键盘,否则刷新列表;
4:最后就是在更新文本内容的同时过滤对应的数据,显示搜索结果内容。
//扩展SearchViewController实现UISearchBarDelegate和UISearchResultsUpdating两个协议
extension SearchViewController:UISearchBarDelegate,UISearchResultsUpdating{
//开始进行文本编辑,设置显示搜索结果,刷新列表
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
shouldShowSearchResults = true
tableView.reloadData()
}
//点击Cancel按钮,设置不显示搜索结果并刷新列表
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
shouldShowSearchResults = false
tableView.reloadData()
}
//点击搜索按钮,触发该代理方法,如果已经显示搜索结果,那么直接去除键盘,否则刷新列表
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if !shouldShowSearchResults{
shouldShowSearchResults = true
tableView.reloadData()
}
searchController.searchBar.resignFirstResponder()
}
//这个updateSearchResultsForSearchController(_:)方法是UISearchResultsUpdating中唯一一个我们必须实现的方法。当search bar 成为第一响应者,或者search bar中的内容被改变将触发该方法.不管用户输入还是删除search bar的text,UISearchController都会被通知到并执行上述方法。
func updateSearchResults(for searchController: UISearchController) {
let searchString = searchController.searchBar.text
//过滤数据源,存储匹配的数据
filteredArray = dataArray.filter({ (country) -> Bool in
let countryText: NSString = country as NSString
return (countryText.range(of: searchString!, options: .caseInsensitive).location) != NSNotFound
})
//刷新表格
tableView.reloadData()
}
}
自定义UISearchBar和UISearchController
下面自定义UISearchBar和UISearchController,实现的效果如下,功能跟之前一样:
首先看自定义UISearchBar,自定义UISearchBar,其实就是继承UISearchBar,然后根据需求实现相应的初始化方法,并获取UISearchBar中的UITextField,对UITextField进行相应的属性设置,也可以在Draw方法中进行相应的绘制操作。具体代码如下:
class CustomSearchBar: UISearchBar {
var preferredFont: UIFont!
var preferredTextColor: UIColor!
//实现自定义初始化构造器,设置大小,字体,文本颜色,样式等基本属性
init(frame: CGRect,font: UIFont, textColor: UIColor) {
super.init(frame: frame)
self.frame = frame
preferredFont = font
preferredTextColor = textColor
//设置为prominent样式并且需要设置isTranslucent为false,为了searchBar和earch field都不透明。
searchBarStyle = UISearchBarStyle.prominent
isTranslucent = false
//注意:search bar并不是一个单一的控件,它是textfield的一部分。相反,search bar有一个UIView类型的子视图,该子视图有两个非常重要的子视图,一个是search field是UITextField类型的子类,另一个是search field的背景view。我们可以使用如下操作,打印子视图内容:
print(subviews[0].subviews)
}
//自定义子视图必须实现该方法
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//建立一个辅助函数,获取search field在search bar子视图中的位置
func indexOfSearchFieldInSubviews() ->Int!{
var index: Int!
let searchBarView = subviews[0]
for i in 0..<searchBarView.subviews.count {
if searchBarView.subviews[i].isKind(of: UITextField.self){
index = i
break
}
}
return index
}
//实现draw方法
override func draw(_ rect: CGRect) {
//获取search field在search bar子视图中的位置
if let index = indexOfSearchFieldInSubviews(){
//获得search field
let searchField: UITextField = subviews[0].subviews[index] as! UITextField
//设置search field的大小
searchField.frame = CGRect(x: 5.0, y: 5.0, width: frame.size.width - 10.0, height: frame.size.height - 10.0)
//设置字体和文本颜色
searchField.font = preferredFont
searchField.textColor = preferredTextColor
//设置search field的背景颜色
searchField.backgroundColor = barTintColor
}
//在search bar的底部绘制一条细线,使用CAShapeLayer和UIBezierPath进行绘制
let startPoint = CGPoint(x: 0.0, y: frame.size.height)
let endPoint = CGPoint(x: frame.size.width, y: frame.size.height)
let path = UIBezierPath()
path.move(to: startPoint)
path.addLine(to: endPoint)
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = preferredTextColor.cgColor
shapeLayer.lineWidth = 2.5
layer.addSublayer(shapeLayer)
super.draw(rect)
}
}
接下来看一下CustomSearchController类,该类继承于UISearchController,构造了一个初始化方法设置CustomSearchBar需要的相关属性。并自定义协议用于与主页面视图控制器进行交互。
//自定义协议
protocol CustomSearchControllerDelegate{
func didStartSearching()
func didTapOnSearchButton()
func didTapOnCancelButton()
func didChangeSearchText(searchText: String)
}
class CustomSearchController: UISearchController {
//拥有自定义CustomSearchBar和代理对象
var customSearchBar: CustomSearchBar!
var customDelegate: CustomSearchControllerDelegate!
//1:构造初始化方法
init(searchResultsController: UIViewController?,searchBarFrame: CGRect,searchBarFont: UIFont,searchBarTextColor: UIColor,searchBarTintColor: UIColor) {
super.init(searchResultsController: searchResultsController)
configureSearchBar(frame: searchBarFrame, font: searchBarFont, textColor: searchBarTextColor, bgColor: searchBarTintColor)
}
//2:实现两个必须实现的方法
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
//3:自定义方法对SearchBar进行设置
func configureSearchBar(frame: CGRect, font: UIFont, textColor: UIColor, bgColor: UIColor) {
//创建自定义SearchBar
customSearchBar = CustomSearchBar(frame: frame, font: font , textColor: textColor)
//设置SearchBar相应的属性内容
customSearchBar.barTintColor = bgColor
customSearchBar.tintColor = textColor
customSearchBar.showsBookmarkButton = false
customSearchBar.showsCancelButton = true
customSearchBar.delegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
extension CustomSearchController: UISearchBarDelegate{
//当开始编辑search field,使用代理执行相应的协议方法
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
customDelegate.didStartSearching()
}
//当搜索按钮被点击,触发方法,取消键盘
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
customSearchBar.resignFirstResponder()
customDelegate.didTapOnSearchButton()
}
//点击取消按钮
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
customSearchBar.resignFirstResponder()
customDelegate.didTapOnCancelButton()
}
//文本发生改变
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
customDelegate.didChangeSearchText(searchText: searchText)
}
}
最后就是在SearchViewController中将使用原生SearchController替换成为自定义SearchController,并且实现代理,在协议方法中控制与自定义searchBar的交互。替换部分代码如下,具体可以看代码。
func configureCustomSearchController(){
//创建自定义SearchController并设置相应的属性
customSearchController = CustomSearchController(searchResultsController: self, searchBarFrame: CGRect(x: 0.0, y: 0.0, width: tableView.frame.size.width, height: 50.0), searchBarFont: UIFont(name: "Futura", size: 16.0)!, searchBarTextColor: UIColor.orange, searchBarTintColor: UIColor.black)
customSearchController.customSearchBar.placeholder = "Search in this awesome bar..."
tableView.tableHeaderView = customSearchController.customSearchBar
customSearchController.customDelegate = self
customSearchController.customSearchBar.sizeToFit()
}
//MARK:自定义UISearchBarController部分
extension SearchViewController:CustomSearchControllerDelegate{
func didStartSearching() {
shouldShowSearchResults = true
tableView.reloadData()
}
func didTapOnSearchButton() {
if !shouldShowSearchResults{
shouldShowSearchResults = true
tableView.reloadData()
}
}
func didTapOnCancelButton() {
shouldShowSearchResults = false
tableView.reloadData()
}
func didChangeSearchText(searchText: String) {
//过滤数据源,存储匹配的数据
filteredArray = dataArray.filter({ (country) -> Bool in
let countryText: NSString = country as NSString
return (countryText.range(of: searchText, options: .caseInsensitive).location) != NSNotFound
})
//刷新表格
tableView.reloadData()
}
}