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,启动它,我们会看到一个空的列表:

UISearchController教程_第1张图片

返回到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的代理和数据源方法已经实现好,我们会看到数据正常显示:


UISearchController教程_第2张图片

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


UISearchController教程_第3张图片
加入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的,我们用看到用户进行搜索的结果。
编译运行程序,我们看到搜索成功了!

UISearchController教程_第4张图片

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

向详情页传数据

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

一切正常了:


UISearchController教程_第5张图片
创建一个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

编译运行程序:


UISearchController教程_第6张图片

搞定!

你可能感兴趣的:(UISearchController教程)