UISearchController教程

本文翻译自:http://www.raywenderlich.com/113772/uisearchcontroller-tutorial
教程使用iOS 9 SDKSwift 2进行开发。


如果你的app展示了很多条数据,并且需要在庞大的列表里滚动来查阅,那么这种情况下,比较友好的处理方式便是允许用户进行搜索。幸运的是,UIKit包括UISearchBar能很好地集成进UITableView并且能很快速地进行过滤和响应。
在这篇UISearchController教程中,我们会编译一个含有搜索功能的Candy app,它建立于一个标准的table view。我们会在table view中增加搜索的功能,包括了动态过滤以及增加一个可选的范围选择,这所有的都是iOS 8新加入的UISearchController的优势。

开始

可以从这儿下载初始项目,这个demo已经建立了一个navigation controller,启动它,我们会看到一个空的列表:


返回到Xcode,Candy.swift这个文件包含了会被显示的candy的所有信息,它实际上只有两个属性: namecategory
当用户搜索candy的时候,我们可以根据name进行搜索,但是在教程最后我们也会通过category进行范围过滤。

填充Table View

打开 MasterViewController.swiftcandies属性就是我们用来展示的供搜索的数据源,说到这,我们先创建一些candy!
在本教程中,我们只需要创建有限个数据保证能完成搜索功能就好,在真正的生产app当中,我们可能会需要成千上万条数据来进行搜索。即便数据有那么多时,我们的搜索方式还是一样的。
viewDidLoad()中,在super.viewDidLoad()后填充candies数组:

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")
]

再次编译运行app,因为table view的代理和数据源方法已经实现好,我们会看到数据正常显示:



点击一行cell并且能够进入到详情页:


加入UISearchController

如果我们查阅UISearchController文档,会发现文档精简得令人发指,没有进行任何的搜索例子,这个类只是简单的提供了一些标准接口来供开发者自己实现。
UISearchController用代理的模式来和app进行沟通,从而知道用户在做些什么,我们必须自己写一些函数来过滤字符串。
尽管这一开始显得不是那么的友好,但是自定义搜索方法给了我们对app更深的控制权,我们的用户会喜欢我们的搜索功能---因为优雅和快速。
如果大家曾经开发过table view的搜索功能,可能会发现这和UISearchDisplayController很像,但是自从iOS 8之后,这个类就被UISearchController取代了,因为其简化了整个搜索流程。
不幸的是,在写这个教程的时候,Interface Builder并不支持UISearchController,所以我们需要通过代码来创建我们的UI。
MasterViewController.swift中,增加一个新的属性:

let searchController = UISearchController(searchResultsController: nil)

通过参数searchResultsController传nil来初始化UISearchController,意思是我们告诉search controller我们会用相同的view来展示我们的搜索结果,如果我们想要指定一个不同的view controller,那就会被替代为显示搜索结果。
下一步,我们需要为我们的searchController设置一些参数。仍然在MasterViewController.swift中,在viewDidLoad()中加入以下代码:

searchController.searchResultUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
  1. searchResultUpdaterUISearchController的一个属性,它的值必须实现UISearchResultsUpdating协议,这个协议让我们的类在UISearchBar文字改变时被通知到,我们之后会实现这个协议。
  2. 默认情况下,UISearchController暗化前一个view,这在我们使用另一个view controller来显示结果时非常有用,但当前情况我们并不想暗化当前view。
  3. 设置definesPresentationContexttrue,我们保证在UISearchController在激活状态下用户push到下一个view controller之后search bar不会仍留在界面上。
  4. 最后,我们增加searchBar到我们的tableHeaderView中。
UISearchResultsUpdating and Filtering

在设置完search controller后,我们需要再加些代码来让它工作,首先,加入如下属性到MasterViewController:

var filterCandies = [Candy]()

这个属性会保存用户搜索后的结果,接着加入如下辅助方法:

func filterContentForSearchText(searchText: String, scope: String = "All) {
    filteredCandies = candies.filter { candy in
        return candy.name.lowercaseString.containsString(searchText.lowercaseString)
    }
    tableView.reloadData()
}

这个通过searchText的来对candies进行过滤,并且把结果记录在filteredCandies中,别担心scope参数,之后我们会用到的。
为了让MasterViewController响应这个search bar,我们需要实现UISearchResultsUpdating,打开** MasterViewController.swift**,在主MasterViewController类之外再加上如下的extension:

extension MasterViewController: UISearchResultsUpdating {
    func updateSearchResultsForSearchController(searchController: UISearchController) {
        filterContentForSearchText(searchController.searchBar.text!)
    }
}

这个updateSearchResultsForSearchController(_:)方法是UISearchResultsUpdating中唯一一个我们必须实现的方法。
现在不管用户输入还是删除search bar的text,UISearchController都会被通知到并执行上述方法。
filter()带了一个(candy: Candy) -> Bool类型的闭包,这个方法遍历数组里的所有数据,然后调用这个闭包,传入当前的数组值。
我们用这个方法来判断数据是否需要被搜索到展示给用户,如果需要我们返回true,否则返回false
我们达到比较友好的体验,我们把searchTet以及原数据都小写之后在进行过滤。
编译运行程序,我们发现现在已经有一个搜索框在列表上方了。
但不管怎么输入,我们的搜索功能似乎不起作用,这仅仅是因为我们没有对过滤后的数据进行显示。
回到** 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
}

现在这些方法都会依赖searchControlleractive属性来展示不同数据,当用户点击搜索区域内的Search Bar时,active会自动被设成true,如果search controller是active的,我们用看到用户进行搜索的结果。
编译运行程序,我们看到搜索成功了!


测试一段时间后,我们发现详情页有时会和点击的不一致,我们来修复它。

向详情页传数据

在** 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]
}

一切正常了:


创建一个Scope Bar来对结果进行再次过滤

如果我们希望能给用户另一种过滤选择,我们可以增加一个Scope Bar来过滤category字段。我们用来过滤的categories包括Chocolate, Hard, Other
首先我们需要在MasterViewController中创建一个Scope bar,这个scope bar实际上是一个segmented control,来指定结果只能在某个范围内,在这个demo中我们的scope是category,但实际情况下,scope可能是types, ranges或一些完全不同的东西。
要使用scope bar,我们需要再实现UISearchBarDelegate代理中的一个方法,加入如下extension:

extension MasterViewController: UISearchBarDelegate { 
    func searchBar(searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) { 
        filterContentForSearchText(searchBar.text!, scope: searchBar.scopeButtonTitles![selectedScope]) 
    }
}

每当用户切换scope bar时,这个代理方法就会被调用,所以我们应该在此用新的scope进行重新搜索。
现在我们修改```filterContentForSearchText(_:scope:)来实现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()
}

我们还需要修改之前实现的updateSearchResultsForSearchController(_:):

func updateSearchResultsForSearchController(searchController: UISearchController) { 
    let searchBar = searchController.searchBar 
    let scope = searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex] 
    filterContentForSearchText(searchController.searchBar.text!, scope: scope)
}

最后我们在search bar上加上scope bar,在** MasterViewController.swift**中,在viewDidLoad()中,设置search controller后加入:

searchController.searchBar.scopeButtonTitles = ["All", "Chocolate", "Hard", "Other"]
searchController.searchBar.delegate = self

编译运行程序:



搞定!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,373评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,732评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,163评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,700评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,036评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,425评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,737评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,421评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,141评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,398评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,908评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,276评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,907评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,018评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,772评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,448评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,325评论 2 261

推荐阅读更多精彩内容