Hi, 大家好,我是姜友华。
CoreData不仅与TableView的Data Source相匹配,它同时与SearchController的Predicate相匹配,这一特征能让我们非常便捷的将搜索功能加入其中。好,我们继续上一节,为App添加搜索功能。
内容概要
- 首先,添加搜索功能;
- 其次,了解搜索设置;
- 最后,了解Predicate语法。
添加SearchController
我们打开ViewController.swift文件,通过下面代码为本例添加搜索框。
/// viewController
override func viewDidLoad() {
super.viewDidLoad()
......
let searchController = UISearchController(searchResultsController: nil)
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchResultsUpdater = self
self.navigationItem.searchController = searchController
}
代码的内容是:
- 我们使用了一个SearchController;
- 设置它在输入搜索内容时不加深列表内容;
- 指定它的结果更新器的委托为当前类;
- 将searchController作为导航项的searchController。
searchController呈现的效果,后面会图示。
实现搜索功能
我们需要实现searchResultsUpdater的委托方法。
- 首先,我们拓展ViewController类,并实现updateSearchResults方法;
- 然后,我们建立一个Predicate(断言),使用Predicate语法,告诉这个断言的判断条件;
- 最后将Predicate给予上一节创建的fetchedResultsController,让它基于该条件重新获取数据。
extension ViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
let predicate: NSPredicate
if let userInput = searchController.searchBar.text, !userInput.isEmpty {
predicate = NSPredicate(format: "title CONTAINS[c] %@", userInput)
} else {
predicate = NSPredicate(value: true)
}
fetchedResultsController.fetchRequest.predicate = predicate
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("\(error)")
}
tableView.reloadData()
}
}
运行得到的效果如下:
Screen Shot 2021-08-16 at 10.25.23.png
不止于搜索标题内容。
一开始我们实现对Title的搜索。现在,我们需要前进一步,来实现基Title、Info、ID甚至全部的内容进行搜索。UISearchController为这种需要求提供了一个叫Scope Button的控件,我们可通过设置它要实现。
- 首先,设置scopeButtonTitles。使它的值为一个包含Title和Info的数组,表示为搜索提供两个选项。至于对ID和全部内容的搜索,我们将在Predicate语法内再添加。
/// ViewController
override func viewDidLoad() {
......
searchController.searchBar.scopeButtonTitles = ["Title", "Info"]
......
}
- 其次,更改断言的判断条件。我们将PrediCate字符串中title,改为从Scope Bar获取内容,同时将断言部分独立出来,作为formatPredicate方法。
/// ViewController
extension ViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
let predicate = formatPredicate(searchController: searchController)
fetchedResultsController.fetchRequest.predicate = predicate
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("\(error)")
}
tableView.reloadData()
}
private func formatPredicate(searchController: UISearchController) -> NSPredicate {
guard let userInput = searchController.searchBar.text, !userInput.isEmpty else {
return NSPredicate(value: true)
}
let index = searchController.searchBar.selectedScopeButtonIndex
guard let scopeTitle = searchController.searchBar.scopeButtonTitles?[index] else {
fatalError("Error getting title of Scope Button.")
}
return NSPredicate(format: "\(scopeTitle.lowercased()) CONTAINS[c] %@", userInput)
}
}
我们搜索一个title不包含的字符串试试,结果达到了我们的需求:
Screen Shot 2021-08-16 at 12.21.39.png
我们再来深入了解一下。
SearchController的设置
- SearchController提供了一些Bool值供我们来设置,我们来看看。
由于我们在初始UISearchController(searchResultsController: nil),结果控制器设置为空,所以最后两项你在本例中无法呈现。
// 搜索时,列表区是否变暗,默认:是。
searchController.obscuresBackgroundDuringPresentation = true
// 搜索时,导航栏其它元素是否隐藏,默认:是。
searchController.hidesNavigationBarDuringPresentation = true
// 搜索框有内容时,是否显示框后的取消的文字按钮,默认:是。
searchController.automaticallyShowsCancelButton = true
// 搜索时,是否显示搜索栏范围栏,默认:是。
searchController.automaticallyShowsScopeBar = true
// 是否管理结果控制器的可见性,默认:否。
searchController.automaticallyShowsSearchResultsController = false
// 搜索时,是否显示搜索结果控制器,默认:否。
searchController.showsSearchResultsController = false
Predicate的语法
折腾了半才发现这个,你可以收藏一下是Objective-C的:
官网地址,到这里后面可以不看了。
- 如果你还想看的话,我们需要拓展一下ViewTable显示数据的类型。
- 我们为StudyEntity添加两个字段:index: Integer32, state: Boolean。
- Build一下,应用上这两个字段。
/// AppDelegate
func initializationData() {
......
for i in 0...20 {
let studyEntity = StudyEntity(context: context)
......
studyEntity.index = Int32(i)
studyEntity.state = i % 2 == 0
}
......
}
- 将这新加的属性值,添加到TableViewCell的info里。
/// ViewController
extension ViewController: UITableViewDataSource {
......
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
......
cell.info.text = "\(studyEntity.index). \(studyEntity.state), " + (studyEntity.info ?? "")
......
}
}
- 修改搜索范围,将全部All, index, state加入。
searchController.searchBar.scopeButtonTitles = ["All", "Title", "Info", "Index", "State"]
- 删除现有数,重新初始化数据,原数据里index, state没有赋值。
- 首先,我们在Appdelegate定义了一个方法。
- 然后,我们在removeContext方法里调用,运行一次后删除。
/// Appdelegate
func removeContext() {
let context = persistentContainer.viewContext
if let result = try? context.fetch(StudyEntity.fetchRequest()) {
for object in result {
context.delete(object as! NSManagedObject)
}
}
}
func removeContext() {
removeContext()
......
}
最后,运行后显示如下图:
Screen Shot 2021-08-16 at 14.43.26.png
- 逻辑运算符
注意搜索框输入1为Bool类型的true。
/// logic. ==, !=, >=, <=, >, < &&, ||
NSPredicate(format: "index >= 5 || state == 1")
- BETWEEN
两者之间。
/// key BETWEEN {lower, upper}
NSPredicate(format: "index BETWEEN {1, 5})
- IN
被列举里的。
/// key IN {item, .... item_n}
NSPredicate(format: "index IN {1, 5})
- LIKE
精确匹配。
/// key LIKE 'Job'
NSPredicate(format: "title LIKE 'Job'",)
- MATCHES
正则匹配。
请留意,使用MATCHES,在本例未测试成功。
/// title MATCHES 'a+'
NSPredicate(format: "title MATCHES 'A*'")
- BEGINSWITH
匹配开头。
// key BEGINSWITH 'prefix'
NSPredicate(format: "title BEGINSWITH 'A'")
- ENDSWITH
匹配结尾。
/// key ENDSWITH 'suffix'
NSPredicate(format: "title ENDSWITH 'A'")
-
CONTAINS
匹配包含的字符,我们一开始用的就是这种方式。 -
参数c、d,
- CONTAINS[c],表示忽略大小写。
- CONTAINS[c],查询同读音的字符。
在这里,就讲这几个常用的吧。
我是姜友华,下次再见。
网友评论