UISearchController使用

当一个app要显示大量的数据,滑动列表并不会让人愉悦。所以允许用户搜索指定的内容变得刻不容缓。
好消息是,UIKit已经将UISearchBar和UITableView无缝结合在一起了。

在本教程中,你将用标准的table view创建一个可以搜索糖果的app。
使用iOS8的新特性UISearchController,赋予table view搜索的功能,包含动态过滤,
还要添加一个可供选择的scope bar。
最后,学会如何让app更加友好,满足用户的需求。


UISearchController-Square.png
开始

点击这里下载初始项目并打开它,
这个项目已经有了一个带样式的navigation controller。运行它,你将看到一个空的列表:


UISearchController-Starter-281x500.png

回到Xcode,文件Candy.swift中有一个类用来保存每一个糖果的信息,
这个类有两个属性,分别对应糖果的名称和种类。
当用户使用你的app搜索糖果时,你将根据用户输入的文字定位到对应的那一项。
在教程的最后你要实现一个Scope Bar,到时你就明白种类字符串有多重要。

创建Table View

打开 MasterViewController.swiftcandies属性用来管理所有不同的Candy对象.
说到这,是时候创建一些Candy了。

在本教程中,你只需要少量的数据来演示search bar是如何工作的;
在正式的项目中,你也许有几千个对象要被搜索。
不论是几千条还是几条数据,这个方法都同样适用。

创建candies数据,将下面的代码添加到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方法已经实现了,
你将看到一个有数据的table view:


UISearchController-Data-281x500.png

选择一行后会展示相应的糖果详细:


darkchocolate.png

糖果太多了,需要一些时间才能找到想要到!你需要一个 UISearchBar。

引入 UISearchController

如果你看过UISearchController的文档,你会发现它很懒。它没有做任何关于搜索的工作。
这个类简单地提供一个用户期望的标准接口。

UISearchController通过委托告诉app用户正在做什么。
你需要自己编写所有的字符串匹配函数。

虽然看起来有点吓人,编写自定义搜索函数对返回的数据进行严格的控制,
你的用户也会感到搜索非常智能和快速。

如果你使用过iOS的table view搜索,你也许很熟悉UISearchDisplayController
从iOS8开始,这个类被UISearchController替代了,并简化了搜索过程。

不幸的是,在撰写本文时,Interface Builder不支持UISearchController,所以要用代码来制作UI。

MasterViewController.swift中添加一个属性:

let searchController = UISearchController(searchResultsController: nil)

初始化UISearchController时并没有设置searchResultsController
你告诉search controller要使用默认的视图用来展示搜索结果。
如果你指定一个不同的viewController,那么它将被用来展示结果。

下一步,需要给searchController设置一些参数。
依然在MasterViewController.swift中,添加下面的代码到viewDidLoad()

searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true tableView.tableHeaderView = searchController.searchBar

下面是代码的说明:

  1. searchResultsUpdaterUISearchController中的一个属性,遵循了协议UISearchResultsUpdating
    这个协议允许类接收UISearchBar文本变化的通知。过一会就要使用这个协议。
  2. 默认情况下,UISearchController会将presented视图变暗。
    当你使用另一个viewController作为searchResultsController会非常有用,
    在现在的实例中,你已经设置了当前的view来展示结果,所以不需要让它变暗。
  3. 通过设置definesPresentationContexttrue,能够确保UISearchController被激活时,
    用户跳转到另一个viewController,而search bar依然保留在屏幕上。
  4. 最后,将searchBar添加到table view的tableHeaderView
    记住,Interface Builder还不兼容UISearchController,这一步是必须的。
UISearchResultsUpdating和Filtering

设置了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() }

这个方法会根据searchText过滤candies,并将结果添加到filteredCandies
不要担心scope这个参数,下一节就会用到它。

为了让MasterViewController响应search bar,必须实现UISearchResultsUpdating
打开MasterViewController.swift,添加下面的类扩展,在MasterViewController类外面:

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

updateSearchResultsForSearchController(_:)UISearchResultsUpdating协议中唯一一个而且是必须实现的方法。

现在,无论用户怎样修改search bar的文本,UISearchController都会通过这个方法告诉MasterViewController
这个方法简单的调用了助手方法,并将search bar当前的文本作为参数。

filter()用到了(candy: Candy) -> Bool类型的闭包。它会循环数组中的每一个元素,然后当前的元素发给闭包。

使用它来确定一个糖果是否作为搜索结果来呈现给用户。
返回true将当前的糖果添加到filtered数组中,否则返回false

为了判断结果,containsString(_:)用来检查candy的name是否包含了searchText
但在比较之前,使用lowercaseString方法将字符串转换成小写。

注意:大多数时候,用户不会去在乎输入的大小写问题,

如果大小写不匹配的话,依然不会返回结果。

现在,你输入"Chocolate"或者"chocolate"都会返回匹配的结果。这太有用了!!

再运行一次,你会发现table上面有一个search bar。

然而,输入任何文本都不会呈现过滤的结果。什么鬼?
这只是因为写的代码还没有告诉table何时使用过滤后的数据。

回到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 }

没有太多的修改,仅仅检查了一下用户是否正在输入,
并使用过滤后或者正常的数据给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 }

这两个方法都使用了searchControlleractive属性来决定呈现哪个数组。

当用户点击Search Bar的文本框,active会自动设置为true
如果search controller处在激活状态,会看到用户已经输入了一些文字。
如果已经存在了,便会返回filteredCandies的数据,否则返回完成列表的数据。

回想一下,search controller会自动显示或隐藏table,所有代码要做的就是根据用户的输入提供正确的数据。


ragecomic.png

编译运行一下,你有一个Search Bar来过滤数据。


UISearchController-Filter-281x500.png

试试这个app,已经可以搜索到各种糖果了。

这里还有一个问题,选中一个搜索结果,会发现详情界面显示了错误的糖果!来修复它。

发送数据给Detail View

在将数据发送给详细视图时,需要确保控制器需要知道哪个上下文正在使用:是完整的列表还是搜索结果。
还是在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]
}

这里执行了tableView(_:numberOfRowsInSection:)tableView(_:cellForRowAtIndexPath:)相同的检查,
但现在提供了正确的糖果对象给详细视图。

再次运行一遍,看看是不是正确的。


CandySearch-DetailView-281x500.png
使用Scope Bar来筛选数据

还有另一种方法过滤数据,添加一个Scope Bar根据糖果的类别来过滤。
类别就是创建Candy对象时添加的,如ChocolateHardOther

先在MasterViewController里面添加一个scope bar。
scope bar是一个分段控件,用来缩小搜索的范围。
范围就是最初定义的。在这个项目中,范围就是糖果的种类,但也可以是其他的。

先来实现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,
当它触发时,之行了搜索方法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() }

只有当参数scope等于“ALL”或者等于糖果对象的种类属性才将candy添加到filteredCandies数组。

已经快完成了,但范围过滤还没有生效。
需要修改方法updateSearchResultsForSearchController(_:)

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

现在唯一的问题就是还没有scope bar控件!
选择文件MasterViewController.swift,在viewDidLoad()中添加下面的代码:

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

这会给搜索栏添加一个分段控件,还有和candy的categories相对应的标题。
还包括一个名为“ALL”的分类,它将忽略种类的过滤。


UISearchController-Filter-with-Scope-281x500.png
何去何从

恭喜你,已经有了一个可以搜索的table view。
点击这里下载完整的项目代码。

越来越多的app都使用了表格视图,搜索功能成了标配。
没理由不使用UISearechBarUISearchController

转载于:https://www.cnblogs.com/ming1025/p/6136307.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值