本文翻譯自:http://www.raywenderlich.com/113772/uisearchcontroller-tutorial。
教程使用iOS 9 SDK和Swift 2進(jìn)行開發(fā)。
如果你的app展示了很多條數(shù)據(jù),并且需要在龐大的列表里滾動(dòng)來查閱,那么這種情況下敲街,比較友好的處理方式便是允許用戶進(jìn)行搜索。幸運(yùn)的是,UIKit包括UISearchBar
能很好地集成進(jìn)UITableView
并且能很快速地進(jìn)行過濾和響應(yīng)顶吮。
在這篇UISearchController
教程中,我們會(huì)編譯一個(gè)含有搜索功能的Candy app粪薛,它建立于一個(gè)標(biāo)準(zhǔn)的table view悴了。我們會(huì)在table view中增加搜索的功能,包括了動(dòng)態(tài)過濾以及增加一個(gè)可選的范圍選擇,這所有的都是iOS 8新加入的UISearchController
的優(yōu)勢(shì)湃交。
開始
可以從這兒下載初始項(xiàng)目熟空,這個(gè)demo已經(jīng)建立了一個(gè)navigation controller,啟動(dòng)它搞莺,我們會(huì)看到一個(gè)空的列表:
返回到Xcode息罗,Candy.swift這個(gè)文件包含了會(huì)被顯示的candy的所有信息,它實(shí)際上只有兩個(gè)屬性:
name
和category
才沧。當(dāng)用戶搜索candy的時(shí)候迈喉,我們可以根據(jù)
name
進(jìn)行搜索,但是在教程最后我們也會(huì)通過category
進(jìn)行范圍過濾温圆。
填充Table View
打開 MasterViewController.swift挨摸,candies
屬性就是我們用來展示的供搜索的數(shù)據(jù)源,說到這岁歉,我們先創(chuàng)建一些candy得运!
在本教程中,我們只需要?jiǎng)?chuàng)建有限個(gè)數(shù)據(jù)保證能完成搜索功能就好锅移,在真正的生產(chǎn)app當(dāng)中熔掺,我們可能會(huì)需要成千上萬(wàn)條數(shù)據(jù)來進(jìn)行搜索。即便數(shù)據(jù)有那么多時(shí)非剃,我們的搜索方式還是一樣的置逻。
在viewDidLoad()
中,在super.viewDidLoad()
后填充candies
數(shù)組:
candies = [
Candy(category:"Chocolate", name:"Chocolate Bar"),
Candy(category:"Chocolate", name:"Chocolate Chip"),
Candy(category:"Chocolate", name:"Dark Chocolate"),
Candy(category:"Hard", name:"Lollipop"),
Candy(category:"Hard", name:"Candy Cane"),
Candy(category:"Hard", name:"Jaw Breaker"),
Candy(category:"Other", name:"Caramel"),
Candy(category:"Other", name:"Sour Chew"),
Candy(category:"Other", name:"Gummi Bear")
]
再次編譯運(yùn)行app备绽,因?yàn)閠able view的代理和數(shù)據(jù)源方法已經(jīng)實(shí)現(xiàn)好诽偷,我們會(huì)看到數(shù)據(jù)正常顯示:
點(diǎn)擊一行cell并且能夠進(jìn)入到詳情頁(yè):
加入U(xiǎn)ISearchController
如果我們查閱UISearchController
文檔,會(huì)發(fā)現(xiàn)文檔精簡(jiǎn)得令人發(fā)指疯坤,沒有進(jìn)行任何的搜索例子报慕,這個(gè)類只是簡(jiǎn)單的提供了一些標(biāo)準(zhǔn)接口來供開發(fā)者自己實(shí)現(xiàn)。
UISearchController
用代理的模式來和app進(jìn)行溝通压怠,從而知道用戶在做些什么眠冈,我們必須自己寫一些函數(shù)來過濾字符串。
盡管這一開始顯得不是那么的友好菌瘫,但是自定義搜索方法給了我們對(duì)app更深的控制權(quán)蜗顽,我們的用戶會(huì)喜歡我們的搜索功能---因?yàn)閮?yōu)雅和快速。
如果大家曾經(jīng)開發(fā)過table view的搜索功能雨让,可能會(huì)發(fā)現(xiàn)這和UISearchDisplayController
很像雇盖,但是自從iOS 8之后,這個(gè)類就被UISearchController
取代了栖忠,因?yàn)槠浜?jiǎn)化了整個(gè)搜索流程崔挖。
不幸的是贸街,在寫這個(gè)教程的時(shí)候,Interface Builder并不支持UISearchController狸相,所以我們需要通過代碼來創(chuàng)建我們的UI薛匪。
在 MasterViewController.swift中,增加一個(gè)新的屬性:
let searchController = UISearchController(searchResultsController: nil)
通過參數(shù)searchResultsController
傳nil來初始化UISearchController
脓鹃,意思是我們告訴search controller我們會(huì)用相同的view來展示我們的搜索結(jié)果逸尖,如果我們想要指定一個(gè)不同的view controller,那就會(huì)被替代為顯示搜索結(jié)果瘸右。
下一步娇跟,我們需要為我們的searchController設(shè)置一些參數(shù)。仍然在MasterViewController.swift中太颤,在viewDidLoad()
中加入以下代碼:
searchController.searchResultUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
-
searchResultUpdater
是UISearchController
的一個(gè)屬性苞俘,它的值必須實(shí)現(xiàn)UISearchResultsUpdating
協(xié)議,這個(gè)協(xié)議讓我們的類在UISearchBar
文字改變時(shí)被通知到栋齿,我們之后會(huì)實(shí)現(xiàn)這個(gè)協(xié)議。 - 默認(rèn)情況下襟诸,
UISearchController
暗化前一個(gè)view瓦堵,這在我們使用另一個(gè)view controller來顯示結(jié)果時(shí)非常有用,但當(dāng)前情況我們并不想暗化當(dāng)前view歌亲。 - 設(shè)置
definesPresentationContext
為true
菇用,我們保證在UISearchController
在激活狀態(tài)下用戶push到下一個(gè)view controller之后search bar不會(huì)仍留在界面上。 - 最后陷揪,我們?cè)黾?code>searchBar到我們的
tableHeaderView
中惋鸥。
UISearchResultsUpdating and Filtering
在設(shè)置完search controller后,我們需要再加些代碼來讓它工作悍缠,首先卦绣,加入如下屬性到MasterViewController
:
var filterCandies = [Candy]()
這個(gè)屬性會(huì)保存用戶搜索后的結(jié)果,接著加入如下輔助方法:
func filterContentForSearchText(searchText: String, scope: String = "All) {
filteredCandies = candies.filter { candy in
return candy.name.lowercaseString.containsString(searchText.lowercaseString)
}
tableView.reloadData()
}
這個(gè)通過searchText
的來對(duì)candies
進(jìn)行過濾飞蚓,并且把結(jié)果記錄在filteredCandies
中滤港,別擔(dān)心scope
參數(shù),之后我們會(huì)用到的趴拧。
為了讓MasterViewController
響應(yīng)這個(gè)search bar溅漾,我們需要實(shí)現(xiàn)UISearchResultsUpdating
,打開** MasterViewController.swift**著榴,在主MasterViewController
類之外再加上如下的extension:
extension MasterViewController: UISearchResultsUpdating {
func updateSearchResultsForSearchController(searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
}
}
這個(gè)updateSearchResultsForSearchController(_:)
方法是UISearchResultsUpdating
中唯一一個(gè)我們必須實(shí)現(xiàn)的方法添履。
現(xiàn)在不管用戶輸入還是刪除search bar的text,UISearchController
都會(huì)被通知到并執(zhí)行上述方法脑又。
filter()
帶了一個(gè)(candy: Candy) -> Bool
類型的閉包暮胧,這個(gè)方法遍歷數(shù)組里的所有數(shù)據(jù)锐借,然后調(diào)用這個(gè)閉包,傳入當(dāng)前的數(shù)組值叔壤。
我們用這個(gè)方法來判斷數(shù)據(jù)是否需要被搜索到展示給用戶瞎饲,如果需要我們返回true
,否則返回false
炼绘。
我們達(dá)到比較友好的體驗(yàn)嗅战,我們把searchTet以及原數(shù)據(jù)都小寫之后在進(jìn)行過濾。
編譯運(yùn)行程序俺亮,我們發(fā)現(xiàn)現(xiàn)在已經(jīng)有一個(gè)搜索框在列表上方了驮捍。
但不管怎么輸入,我們的搜索功能似乎不起作用脚曾,這僅僅是因?yàn)槲覀儧]有對(duì)過濾后的數(shù)據(jù)進(jìn)行顯示东且。
回到** MasterViewController.swift**,替換tableView(_:numberOfRowsInSection:)
如下:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if sesarchController.active && searchController.searchBar.text != "" {
return filteredCandies.count
}
return candies.count
}
接著替換tableView(_:cellForRowAtIndexPath:)
方法:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let candy: Candy
if searchController.active && searchController.searchBar.text != "" {
candy = filteredCandies[indexPath.row]
} else {
candy = candies[indexPath.row]
}
cell.textLabel!.text = candy.name
cell.detailTextLabel!.text = candy.category
return cell
}
現(xiàn)在這些方法都會(huì)依賴searchController
的active
屬性來展示不同數(shù)據(jù)本讥,當(dāng)用戶點(diǎn)擊搜索區(qū)域內(nèi)的Search Bar時(shí)珊泳,active
會(huì)自動(dòng)被設(shè)成true
,如果search controller是active的拷沸,我們用看到用戶進(jìn)行搜索的結(jié)果色查。
編譯運(yùn)行程序,我們看到搜索成功了!
測(cè)試一段時(shí)間后撞芍,我們發(fā)現(xiàn)詳情頁(yè)有時(shí)會(huì)和點(diǎn)擊的不一致秧了,我們來修復(fù)它。
向詳情頁(yè)傳數(shù)據(jù)
在** MasterViewController.swift**文件中序无,找到perpareForSegue(_:sender:)
方法验毡,找到如下代碼:
let candy = candies[indexPath.row]
之后替換這如下代碼:
let candy: Candy
if searchController.active && searchController.searchBar.text != "" {
candy = filteredCandies[indexPath.row]
} else {
candy = candies[indexPath.row]
}
一切正常了:
創(chuàng)建一個(gè)Scope Bar來對(duì)結(jié)果進(jìn)行再次過濾
如果我們希望能給用戶另一種過濾選擇,我們可以增加一個(gè)Scope Bar來過濾category
字段帝嗡。我們用來過濾的categories包括Chocolate, Hard, Other晶通。
首先我們需要在MasterViewController
中創(chuàng)建一個(gè)Scope bar,這個(gè)scope bar實(shí)際上是一個(gè)segmented control
哟玷,來指定結(jié)果只能在某個(gè)范圍內(nèi)录择,在這個(gè)demo中我們的scope是category,但實(shí)際情況下碗降,scope可能是types, ranges或一些完全不同的東西隘竭。
要使用scope bar,我們需要再實(shí)現(xiàn)UISearchBarDelegate
代理中的一個(gè)方法讼渊,加入如下extension:
extension MasterViewController: UISearchBarDelegate {
func searchBar(searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
filterContentForSearchText(searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
}
}
每當(dāng)用戶切換scope bar時(shí)动看,這個(gè)代理方法就會(huì)被調(diào)用,所以我們應(yīng)該在此用新的scope進(jìn)行重新搜索爪幻。
現(xiàn)在我們修改```filterContentForSearchText(_:scope:)來實(shí)現(xiàn)scope過濾這個(gè)功能:
func filterContentForSearchText(searchText: String, scope: String = "All") {
filteredCandies = candies.filter { candy in
let categoryMatch = (scope == "All") || (candy.category == scope)
return categoryMatch && candy.name.lowercaseString.containsString(searchText.lowercaseString )
}
tableView.reloadData()
}
我們還需要修改之前實(shí)現(xiàn)的updateSearchResultsForSearchController(_:)
:
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchBar = searchController.searchBar
let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
filterContentForSearchText(searchController.searchBar.text!, scope: scope)
}
最后我們?cè)趕earch bar上加上scope bar菱皆,在** MasterViewController.swift**中须误,在viewDidLoad()
中,設(shè)置search controller后加入:
searchController.searchBar.scopeButtonTitles = ["All", "Chocolate", "Hard", "Other"]
searchController.searchBar.delegate = self
編譯運(yùn)行程序:
搞定仇轻!