本文譯自:UISearchController Tutorial: Getting Started
當(dāng)一個app要顯示大量的數(shù)據(jù),滑動列表并不會讓人愉悅拴签。所以允許用戶搜索指定的內(nèi)容變得刻不容緩。
好消息是前塔,UIKit已經(jīng)將UISearchBar和UITableView無縫結(jié)合在一起了勺鸦。
在本教程中,你將用標(biāo)準(zhǔn)的table view創(chuàng)建一個可以搜索糖果的app舶胀。
使用iOS8的新特性UISearchController,賦予table view搜索的功能碧注,包含動態(tài)過濾嚣伐,
還要添加一個可供選擇的scope bar。
最后萍丐,學(xué)會如何讓app更加友好轩端,滿足用戶的需求。
開始
點擊這里下載初始項目并打開它逝变,
這個項目已經(jīng)有了一個帶樣式的navigation controller基茵。運行它,你將看到一個空的列表:
回到Xcode壳影,文件Candy.swift中有一個類用來保存每一個糖果的信息拱层,
這個類有兩個屬性,分別對應(yīng)糖果的名稱和種類宴咧。
當(dāng)用戶使用你的app搜索糖果時根灯,你將根據(jù)用戶輸入的文字定位到對應(yīng)的那一項。
在教程的最后你要實現(xiàn)一個Scope Bar掺栅,到時你就明白種類字符串有多重要烙肺。
創(chuàng)建Table View
打開 MasterViewController.swift,candies屬性用來管理所有不同的Candy對象.
說到這氧卧,是時候創(chuàng)建一些Candy了桃笙。
在本教程中,你只需要少量的數(shù)據(jù)來演示search bar是如何工作的沙绝;
在正式的項目中搏明,你也許有幾千個對象要被搜索鼠锈。
不論是幾千條還是幾條數(shù)據(jù),這個方法都同樣適用熏瞄。
創(chuàng)建candies數(shù)據(jù)脚祟,將下面的代碼添加到viewDidLoad()方法中,然后call super.viewDidLoad()
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")
]
再運行一次你的項目强饮,table view的delegate和datasource方法已經(jīng)實現(xiàn)了由桌,
你將看到一個有數(shù)據(jù)的table view:
選擇一行后會展示相應(yīng)的糖果詳細:
糖果太多了,需要一些時間才能找到想要到邮丰!你需要一個 UISearchBar行您。
引入 UISearchController
如果你看過UISearchController的文檔,你會發(fā)現(xiàn)它很懶剪廉。它沒有做任何關(guān)于搜索的工作娃循。
這個類簡單地提供一個用戶期望的標(biāo)準(zhǔn)接口。
UISearchController通過委托告訴app用戶正在做什么斗蒋。
你需要自己編寫所有的字符串匹配函數(shù)捌斧。
雖然看起來有點嚇人,編寫自定義搜索函數(shù)對返回的數(shù)據(jù)進行嚴(yán)格的控制泉沾,
你的用戶也會感到搜索非常智能和快速捞蚂。
如果你使用過iOS的table view搜索,你也許很熟悉UISearchDisplayController跷究。
從iOS8開始姓迅,這個類被UISearchController替代了,并簡化了搜索過程俊马。
不幸的是丁存,在撰寫本文時,Interface Builder不支持UISearchController柴我,所以要用代碼來制作UI解寝。
在MasterViewController.swift中添加一個屬性:
let searchController = UISearchController(searchResultsController: nil)
初始化UISearchController時并沒有設(shè)置searchResultsController打月,
你告訴search controller要使用默認的視圖用來展示搜索結(jié)果绑谣。
如果你指定一個不同的viewController茅撞,那么它將被用來展示結(jié)果尼摹。
下一步,需要給searchController設(shè)置一些參數(shù)薯定。
依然在MasterViewController.swift中,添加下面的代碼到viewDidLoad():
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
下面是代碼的說明:
-
searchResultsUpdater是UISearchController中的一個屬性,遵循了協(xié)議UISearchResultsUpdating晕窑。
這個協(xié)議允許類接收UISearchBar文本變化的通知。過一會就要使用這個協(xié)議卵佛。 - 默認情況下杨赤,UISearchController會將presented視圖變暗敞斋。
當(dāng)你使用另一個viewController作為searchResultsController會非常有用,
在現(xiàn)在的實例中疾牲,你已經(jīng)設(shè)置了當(dāng)前的view來展示結(jié)果植捎,所以不需要讓它變暗。 - 通過設(shè)置definesPresentationContext為true阳柔,能夠確保UISearchController被激活時焰枢,
用戶跳轉(zhuǎn)到另一個viewController,而search bar依然保留在屏幕上舌剂。 - 最后济锄,將searchBar添加到table view的tableHeaderView。
記住霍转,Interface Builder還不兼容UISearchController荐绝,這一步是必須的。
UISearchResultsUpdating和Filtering
設(shè)置了search controller后避消,還需要寫一些代碼讓它工作起來低滩。
首先,將下面的屬性添加到MasterViewController的頂部:
var filteredCandies = [Candy]()
這個屬性將持有用戶正在搜索的糖果對象岩喷。
下一步恕沫,將這個方法添加到MasterViewController:
func filterContentForSearchText(searchText: String, scope: String = "All") {
filteredCandies = candies.filter { candy in
return candy.name.lowercaseString.containsString(searchText.lowercaseString)
}
tableView.reloadData()
}
這個方法會根據(jù)searchText過濾candies,并將結(jié)果添加到filteredCandies均驶。
不要擔(dān)心scope這個參數(shù)昏兆,下一節(jié)就會用到它。
為了讓MasterViewController響應(yīng)search bar妇穴,必須實現(xiàn)UISearchResultsUpdating爬虱。
打開MasterViewController.swift,添加下面的類擴展腾它,在MasterViewController類外面:
extension MasterViewController: UISearchResultsUpdating {
func updateSearchResultsForSearchController(searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
}
}
updateSearchResultsForSearchController(_:)是UISearchResultsUpdating協(xié)議中唯一一個而且是必須實現(xiàn)的方法跑筝。
現(xiàn)在,無論用戶怎樣修改search bar的文本瞒滴,UISearchController都會通過這個方法告訴MasterViewController曲梗。
這個方法簡單的調(diào)用了助手方法,并將search bar當(dāng)前的文本作為參數(shù)妓忍。
filter()用到了(candy: Candy) -> Bool類型的閉包虏两。它會循環(huán)數(shù)組中的每一個元素,然后當(dāng)前的元素發(fā)給閉包世剖。
使用它來確定一個糖果是否作為搜索結(jié)果來呈現(xiàn)給用戶定罢。
返回true將當(dāng)前的糖果添加到filtered數(shù)組中,否則返回false旁瘫。
為了判斷結(jié)果祖凫,containsString(_:)用來檢查candy的name是否包含了searchText琼蚯。
但在比較之前,使用lowercaseString方法將字符串轉(zhuǎn)換成小寫惠况。
注意:大多數(shù)時候遭庶,用戶不會去在乎輸入的大小寫問題,
如果大小寫不匹配的話稠屠,依然不會返回結(jié)果峦睡。
現(xiàn)在,你輸入"Chocolate"或者"chocolate"都會返回匹配的結(jié)果权埠。這太有用了!!
再運行一次赐俗,你會發(fā)現(xiàn)table上面有一個search bar。
然而弊知,輸入任何文本都不會呈現(xiàn)過濾的結(jié)果阻逮。什么鬼?
這只是因為寫的代碼還沒有告訴table何時使用過濾后的數(shù)據(jù)秩彤。
回到MasterViewController.swift叔扼,將tableView(_:numberOfRowsInSection:)替換成下面的代碼:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.active && searchController.searchBar.text != "" {
return filteredCandies.count
}
return candies.count
}
沒有太多的修改,僅僅檢查了一下用戶是否正在輸入漫雷,
并使用過濾后或者正常的數(shù)據(jù)給table瓜富。
接下來,將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
}
這兩個方法都使用了searchController的active屬性來決定呈現(xiàn)哪個數(shù)組降盹。
當(dāng)用戶點擊Search Bar的文本框与柑,active會自動設(shè)置為true。
如果search controller處在激活狀態(tài)蓄坏,會看到用戶已經(jīng)輸入了一些文字价捧。
如果已經(jīng)存在了,便會返回filteredCandies的數(shù)據(jù)涡戳,否則返回完成列表的數(shù)據(jù)结蟋。
回想一下,search controller會自動顯示或隱藏table渔彰,所有代碼要做的就是根據(jù)用戶的輸入提供正確的數(shù)據(jù)嵌屎。
編譯運行一下,你有一個Search Bar來過濾數(shù)據(jù)恍涂。
試試這個app宝惰,已經(jīng)可以搜索到各種糖果了。
這里還有一個問題再沧,選中一個搜索結(jié)果尼夺,會發(fā)現(xiàn)詳情界面顯示了錯誤的糖果!來修復(fù)它。
發(fā)送數(shù)據(jù)給Detail View
在將數(shù)據(jù)發(fā)送給詳細視圖時汞斧,需要確保控制器需要知道哪個上下文正在使用:是完整的列表還是搜索結(jié)果什燕。
還是在MasterViewController.swift粘勒,在prepareForSegue(_:sender:)中找到下面的代碼:
let candy = candies[indexPath.row]
替換成:
let candy: Candy
if searchController.active && searchController.searchBar.text != "" {
candy = filteredCandies[indexPath.row]
} else {
candy = candies[indexPath.row]
}
這里執(zhí)行了tableView(_:numberOfRowsInSection:)和tableView(_:cellForRowAtIndexPath:)相同的檢查,
但現(xiàn)在提供了正確的糖果對象給詳細視圖屎即。
再次運行一遍庙睡,看看是不是正確的。
使用Scope Bar來篩選數(shù)據(jù)
還有另一種方法過濾數(shù)據(jù)技俐,添加一個Scope Bar根據(jù)糖果的類別來過濾乘陪。
類別就是創(chuàng)建Candy對象時添加的,如Chocolate雕擂,Hard啡邑,Other。
先在MasterViewController里面添加一個scope bar井赌。
scope bar是一個分段控件谤逼,用來縮小搜索的范圍。
范圍就是最初定義的仇穗。在這個項目中流部,范圍就是糖果的種類,但也可以是其他的纹坐。
先來實現(xiàn)scope bar的代理方法枝冀。
在MasterViewController.swift中,添加另一個擴展UISearchBarDelegate耘子,
將下面的代碼添加到UISearchResultsUpdating的后面:
extension MasterViewController: UISearchBarDelegate {
func searchBar(searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
filterContentForSearchText(searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope])
}
}
這個代理方法會在用戶切換scope bar的時候通知viewController果漾,
當(dāng)它觸發(fā)時,之行了搜索方法filterContentForSearchText(_:scope:)谷誓。
修改filterContentForSearchText(_:scope:)方法支持范圍的選擇:
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()
}
只有當(dāng)參數(shù)scope等于“ALL”或者等于糖果對象的種類屬性才將candy添加到filteredCandies數(shù)組跨晴。
已經(jīng)快完成了,但范圍過濾還沒有生效片林。
需要修改方法updateSearchResultsForSearchController(_:):
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchBar = searchController.searchBar
let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex]
filterContentForSearchText(searchController.searchBar.text!, scope: scope)
}
現(xiàn)在唯一的問題就是還沒有scope bar控件端盆!
選擇文件MasterViewController.swift,在viewDidLoad()中添加下面的代碼:
searchController.searchBar.scopeButtonTitles = ["All", "Chocolate", "Hard", "Other"]
searchController.searchBar.delegate = self
這會給搜索欄添加一個分段控件费封,還有和candy的categories相對應(yīng)的標(biāo)題焕妙。
還包括一個名為“ALL”的分類,它將忽略種類的過濾弓摘。
何去何從
恭喜你焚鹊,已經(jīng)有了一個可以搜索的table view。
點擊這里下載完整的項目代碼韧献。
越來越多的app都使用了表格視圖末患,搜索功能成了標(biāo)配研叫。
沒理由不使用UISearechBar和UISearchController。