如何使用Swift添加Table View搜索框


原文地址:http://www.raywenderlich.com/76519/add-table-view-search-swift
泰然翻译组:阳光。校对:glory。

Mou icon

关于此次更新的说明:这篇引导是Brad Johnson用原文使用iOS 8和Swift改写的,并在Xcode 6 beta 7上测试通过!原始文章是由Nicolas Martin引导教程团队撰写的。

在移动应用的世界,人们希望能够快捷的获取信息,甚至是立刻就能得到想要的内容!

iOS用户希望能够灵活的获取他们想要的信息,并且能以最快的速度将信息显示出来。此外,他们也希望能够以更简单和直观的方式来获取信息。当然,这是一个非常高的目标!

自从滑动屏幕变得自然和快速,许多使用UIKit为基础的应用使用UITableView来浏览数据。但是对于大量,巨量的数据如何进行筛选呢?对于大数据集,滚动有大量数据的列表十分缓慢,这也会使人厌烦-所以允许用户使用特定的分类是十分重要的。幸运的是,UIKit有一个UISearchBar 类,可以无缝的集成在table view中,可以让我们快速的过滤信息。

在这篇引导教程中,你将在一个基本的table view上创建一个可以搜索糖果的应用。你将给table view增加搜索的能力,包括动态过滤和预设范围。最后,你将学会如何使你的应用界面更友好,并且满足你用户急切的需求!

想查找一些糖果的搜索结果!继续读下去吧!

提示:在写这篇引导时,iOS 8还是个测试项目,所以我们不能截图。所有的截图均出自iOS 7而且我们在却定可以使用之前尽量不使用Xcode 6的截图。

让我们开始吧

在Xcode中,选择 File\New\Project… 创建一个新项目。选择 Single View ApplicationNext 。将项目命名为 CandySearch 确保 Language 设置为 Swift 并且 Devices 设置为 iPhone 。点击完成,选择你想储存的位置,然后点击 Create

首先将默认的文件清理掉,这样你就可以真正的从头开始。在 Project Navigator中,选择 ViewController.swift,右键点击,选择删除,然后选择 Move to Trash。然后打开 Main.storyboard,选择唯一的一个view controller然后删掉他。现在你有一个空的storyboard,可以在你的应用中添加主屏幕了。

Object Browser (边框控制条的右下部分)拖出 Navigation Controller 以将iOS内置的导航逻辑添加到项目中。这样可以在storyboard中创建两个view controllers - navigation controller和table view controller,这些将作为应用的初始视图。

当用户选择列表中的某一项时,你需要一个视图控件来显示详细的内容。从Table View Controller中拖拽,一直到那个新的view controller释放,并且在弹出的菜单中选择 show 来作为manual segue的选项。

设置糖果类

下一步你将创建一个用来显示糖果的数据结构,保存像策略和名称类的信息。在 CandySearch 文件夹上点击右键,然后选择 New File… 。选择 iOS \ Source \ Swift File 然后点击下一步。将此文件命名为 Candy.swift 。打开文件,然后添加如下内容:

struct Candy {

  let category : String

  let name : String

}

这个结构有两个属性:糖果的策略和名称。当你的用户在应用中搜索糖果时,会引用名称属性对用户搜索的字符串进行比较。你文章稍后将会发现策略字符串在你实现分类条时有多么重要。

你不需要在这里添加自己的初始化,你能得到一个自动生成的机制。默认的,初始化参数将对属性进行配置。在下一个部分中你将看到如何创建 candy 实例。

现在你已经准备好设置 UITableView 以使你的 UISearchBar 能够过滤信息!

连接Table View

下一步你将设置 UITableView 让他和 UISearchBar 一起工作。右键点击 CandySearch 文件夹并且选择 New File… 。选择 iOS \ Source \ Cocoa Touch Class 点击下一步。类命名为 CandyTableViewController ,父类设置为 UITableViewController 并且设置语言为 Swift

开始的时候我们需要添加一个数组。打开 CandyTableViewController.swift 并且增加如下代码:

var candies = [Candy]()

candies 数组将会管理所有不同的 Candy 对象,以便用户搜索。说到这里,是时候创建你的糖果了!在这篇引导中,你只需要创建一个数量区间,用来说明搜索条如何工作;在制作应用的过程中,你会发现你有成千上万的条目需要检索。但是不论你的应用程序有多少条目,搜索的方法都是相同的。具有可伸缩性是最好的!为了能够布置好你的 candies 数组,重写 viewDidLoad 如下:

override func viewDidLoad() {

  super.viewDidLoad()



  // 向candyArray中添加简单的数据

  self.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

  self.tableView.reloadData()

}

这段代码并没做太多的设置,但他做了一些重要设置。首先它填充了9个有不同名称和类别的糖果。你将在填充数组后使用它。然后你让tableView重新载入数据。你必须这么做,以确保所有的糖果信息都被显示。

下一步,你将添加控制表视图本身的功能。实现 tableView(_:numberOfRowsInSection:) 如下:

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

  return self.candies.count

}

这里简单的告诉 tableView ,应该包含许多行,而这个数量是你在 candies 数组中确定的。

现在 tableView 知道了需要准备多少行,你需要告诉他每行需要放什么内容。实现 tableView(_:cellForRowAtIndexPath:):

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

  // 在tableview中查询一个条目,如果没有创建一个。

  let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell



  // 从我们的糖果数组中获得相应的内容

  let candy = self.candies[indexPath.row]



  // 设置条目

  cell.textLabel!.text = candy.name

  cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator



  return cell

}

这个方法主要分三个部分。首先,你使用 indexPath 选出条目。然后检索 candies 数组,根据 indexPath 决定哪个 Candy 需要提取,然后使用 Candy 对象实现 UITableViewCell 实例。

注意:如果你在之前已经使用过Table Views,你也许会怀疑为什么没有实现 heightForRowAtIndexPath 。在iOS8中,这个方法已经不再支持;操作系统在运行时自动决定元素的大小。这岂不是更好吗?:]当然,如果你打算让你的应用支持早期iOS版本,你还是需要实现这个方法。

现在你做的差不多了!还需要一个步骤你就可以看到糖果列表了。你需要实现故事板中的所有代码。首先,打开 Main.storyboard 并且选择 Root View Controller 。在 Identity Inspector 中(右边栏中顶部第三个选项卡),设置 ClassCandyTableViewController 。这将会告诉操作系统当你的控件呈现在屏幕时加载你指定的关联类。好了,现在,双击 CandySearch 根视图控件的标题,然后修改他,这可以给你的用户一些关于他们能在软件中做什么的提示。

最后,你需要让你的操作系统知道你的代码是用于做什么的。你需要注意当你修改故事板的之前,一个默认的元素已经存在于你的table view中。选择这个元素(通常被命名为"Cell")并且打开Attributes Inspector(位置在Identity Inspector的右边)。修改 IdentifierCell 。这将重新匹配你早期代码中元素的标示符。

保存你的修改并运行。现在你有了一个正在工作的列表视图!许多的糖果。。。呈现这些只花了极短的时间!接下来我们需要一个UISearchBar!

Mou icon

设置UISearchBar

如果你查看过 UISearchBar 的文档,你将发现作者非常的懒惰。作者没有提供任何具体的搜索接口!这个类只是提供了一些用户所期望的接口。他更像是一个中级的管理类;擅长将任务委托给他人。

UISearchBar 这个类使用一个代理接口进行通讯,使你的应用知道用户做了些什么。所有的字符串匹配和其他的操作都需要你自己编写完成。尽管这个看起来有点可怕(有点不公平!),但写自定义的搜索方法给了你的应用完善的控制返回结果的体验。你的用户将会欣赏你提供的搜索结果 - 并且也会更快速。

打开 Main.storyboard 并且拖放一个 Search Bar and Search Display Controller 对象到视图控制器。注意 - 这里和 Search Bar 对象有一些不同,他也是有效的。调整搜索条的位置。

不能明白搜索显示控制器的意思?根据苹果自己的文档,一个搜索显示控制器是“管理搜索条的显示,具体来说是一个显示另一个视图控制器搜索结果的table view”。

这意味着区别于你刚才设置的, 搜索控件将有他自己的table view用来显示结果。简单的来说,先在显示控件中增加上层操控,然后把一个搜索中生成的过滤数据放入一个单独的视图控制器,这一点你丝毫不用自己操心。:]

UISearchBar中Attributes inspector的选项

在故事板中,花些时间重新查看搜索条对象的可用属性。你也许不会全部使用到,但使用一个新的UIKit组件之前了解Attributes Inspector是值得的。

  • Text:这将改变呈现在搜索条上的字符串。如果你的应用需要使用默认值,你是不需要修改这一属性的。

  • Placeholder: 这正是你所期望的 - 他允许你放置一个浅灰色的文本以高速你的用户搜索条可以做什么。在这个糖果应用中,我们使用 "搜索你的糖果"。

  • Prompt: 这个文本将会直接显示在搜索条的上方。这有利于对复杂的搜索机制进行说明。(但在这个应用中,Placeholder应该更显而易懂!)

  • Search Style, Bar Style, Translucent, Tint, Background and Scope Bar Images: 这些选项允许你自定义搜索条的呈现。这些选项是为了是你的UISearchBar和UINavigationBar看起来更和谐。

  • Search Text and Background Positions: 这些选项允许你添加文本与搜索框的偏移量。

  • Show Search Results Button: 在搜索条的右边提供一个按钮为了执行像显示最近搜索或者显示最后搜索的方法。搜索栏与这个按钮的交互是通过代理方法来实现的。

  • Show Bookmarks Button: 在搜索栏右边显示标准的蓝色椭圆形书签。这将有一个用户希望保存的搜索书签。像结果按钮一样,也是通过代理来实现的。

  • Show Cancel Button: 这个按钮允许用户关闭搜索栏单独生成的视图控制器。如果这个选项没有选中,搜索栏会在隐藏取消按钮并在搜索模式中自动显示。

  • Shows Scope Bar & Scope Titles: 分类条允许用户用特定的策略进行搜索。比如在音乐应用中,这个控件条也许会显示艺术家,专辑或者流派。对于当前项目,保持这个选项没有被选中;你将实现自己的分类标签。

  • Capitalize, Correction, Keyboard, etc.: 这些选项都借用了UITextField的内容,可以让你改变搜索条的行为。例如,如果用户想搜索专业名词或者是人名,这是就可以关闭自动修正,以避免不必要的麻烦。在本教程中,糖果的名字都是普通的名称,所以保持默认选项就可以了。

注意:了解一些有用的选项可以为你的开发节省许多时间。所以对于一个iOS未来的开发者,是要经常花时间搜索可用的资源。

UISearchBarDelegate和过滤器

设置了故事板以后,你需要写一些代码来让搜索条工作。设置 CandyTableViewController 类响应搜索条,这需要实现一些接口。打开 CandyTableViewController.swift 并且将类的声明替换为下列代码:

class CandyTableViewController : UITableViewController, UISearchBarDelegate, UISearchDisplayDelegate {

UISearchBarDelegate 定义了搜索的行为和响应方式,然后 UISearchDisplayDelegate 定义了搜索条的外观。

下一步,在类中添加如下属性:

var filteredCandies = [Candy]()

这个数组将保存过滤后的数据。

下一步,添加如下辅助方法到类里面:

func filterContentForSearchText(searchText: String) {

  // 使用过滤方法过滤数组

  self.filteredCandies = self.candies.filter({( candy: Candy) -> Bool in

    let categoryMatch = (scope == "All") || (candy.category == scope)

    let stringMatch = candy.name.rangeOfString(searchText)

    return categoryMatch && (stringMatch != nil)

  })

}

这个方法将使用 searchText (也就是你的搜索字符串) 过滤 candies ,然后将结果放入 filteredCandies 。Swift的数组有一个叫做filter()的方法,他使用了一个闭合表达式作为他唯一的参数。

如果你有一些Objective-C的经验,仔细看一下闭合表达式在Swift中的表现形式。闭包是一个自包含的功能块。他们被称作闭包,是因为他们可以使用一段上下文捕获或储存任意变量或常量的引用,这被称为关闭。下面是一个闭合表达式的语法:

{(parameters) -> (return type) in expression statements}

在这个示例中,过滤方法使用闭合表达式来区分数组中的每一个元素。闭包表达式的参数用于对个别的元素进行排序。闭包表达式返回一个bool值,如果元素存在于过滤的数组中则返回true,如果返回false则说明没有包含在数组内。如果你仔细查看文档中的方法,你会注意到他使用了一个像 的类型。这是一个通用类型,这以为这他可以是任何类型。直到你使用闭包表达式定义自己的过滤规则,这个过滤方法可以使用在任何类型的过滤上。闭包表达式的参数也是一个 T 类型,其中的元素就是等待被过滤的。在你的代码中我们使用了 candy: Candy ,然后你了解了这个数组是被 Candy 装满的:

rangeOfString() 用于检查是否字符串包含索要查找的字符串。如果是,则返回true,表明当前的糖果包含在过滤的数组中;如果返回false则不包含。

下一步,在类中添加如下代码:

func searchDisplayController(controller: UISearchDisplayController!, shouldReloadTableForSearchString searchString: String!) -> Bool {

  self.filterContentForSearchText(searchString)

  return true

}



func searchDisplayController(controller: UISearchDisplayController!, shouldReloadTableForSearchScope searchOption: Int) -> Bool {

  self.filterContentForSearchText(self.searchDisplayController!.searchBar.text)

  return true

}

这两个方法是 UISearchDisplayControllerDelegate 的一部分。当用户输入一个搜索队列时中他们会调用过滤方法。当用户更改搜索的字符串时,第一种方法将被调用。第二种方法将用于操作分类条的输入。你还没有添加分类条在这篇引导中,但是你最好添加 UISearchBarDelegate 方法,稍后会使用到。

运行应用;你会注意到,使用搜索条还是没有任何的过滤效果!这到底是怎么回事?这很简单,因为你还没有添加代码,让 tableView 知道什么时候使用过滤过的数据。你需要修改 numberOfRowsInSectioncellForRowAtIndexPath 方法。使用如下方法替换方法中的内容:

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

  if tableView == self.searchDisplayController!.searchResultsTableView {

    return self.filteredCandies.count

  } else {

    return self.candies.count

  }

}



override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

  // 从table view中查找一个可修改的元素,如果没有就创建一个

  let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell



  var candy : Candy

  // 检查标准table和搜索结果table是否正常显示,然后从candy数据集查找需要的对象

  if tableView == self.searchDisplayController!.searchResultsTableView {

    candy = filteredCandies[indexPath.row]

  } else {

    candy = candies[indexPath.row]

  }



  // 设置元素

  cell.textLabel!.text = candy.name

  cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator



  return cell

}

代码测试了当前的 tableView 是否为搜索搜索table或者标准table。如果他确实是搜索table,则数据从 filteredCandies 数组中获取。否则,数据从全部的项目中获取。回想一下之前的内容,搜索显示控制器自动处理结果table的显示和隐藏,所以你所有的代码不得不提供当前的数据(过滤或者没有过滤),这依赖于当前的视图。

运行应用。你现在已经得到了搜索条的方法,过滤主table的行!哇哈!使用以下,看看用户如何搜索各种糖果。

Mou icon

你可能已经注意到 tableView(numberOfRowsInSection:) 中的if/else逻辑重用了很多次。这在处理显示控制器时是很重要的,如果缺少可能会造成错误,这种错误很难被排查。记住过滤的结果不会出现在主table的相同table中。他们实际上是完全独立的表视图,但苹果公司使用了无缝方式设计了他们 - 这样做会让开发者们很困惑!

向详细内容视图发送数据

当向详细视图控件中发送消息时,你需要确定view controller知道哪个用户正在使用的是哪一个table view:完整的列表,或者搜索过的列表。这里的代码和 tableView(_:numberOfRowsInSection:)tableView(_:cellForRowAtIndexPath:) 的地方类似。还是在CandyTableViewController.swift,添加如下代码:

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

  self.performSegueWithIdentifier("candyDetail", sender: tableView)

}



override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {

  if segue.identifier == "candyDetail" {

    let candyDetailViewController = segue.destinationViewController as UIViewController

    if sender as UITableView == self.searchDisplayController!.searchResultsTableView {

      let indexPath = self.searchDisplayController!.searchResultsTableView.indexPathForSelectedRow()!

      let destinationTitle = self.filteredCandies[indexPath.row].name

      candyDetailViewController.title = destinationTitle

    } else {

      let indexPath = self.tableView.indexPathForSelectedRow()!

      let destinationTitle = self.candies[indexPath.row].name

      candyDetailViewController.title = destinationTitle

    }

  }

}

打开故事板并且确保从 Candy Table View ControllerDetail View 的segue拥有candyDetail标示符。运行代码,查看应用如何导航从主table或者搜索tabel过度到详细视图的。

创建一个过滤条件选项卡

如果你想给你的用户搜索的另一种方式,你可以添加一个分类条,以过按物品类别进行过滤。过滤的内容是你在 candyArray 创建之前在 Candy 中注册的:巧克力,硬巧克力,或其他。

首先,在故事板中设置分类条。切换到 CandySearch View Controller 然后选择搜索条。在In the attributes inspector,选择 Shows Scope Bar 。修改标题为:“全部”, “巧克力”, “硬巧克力” ,and “其他”。(你可以使用 + 按钮同时添加多个分类,双击可以修改分类)

下一步,修改CandyTableViewController.swift中的filterContentForSearchText,以为账户添加新的分类。用如下内容替换当前方法:

func filterContentForSearchText(searchText: String, scope: String = "All") {

  self.filteredCandies = self.candies.filter({( candy : Candy) -> Bool in

  var categoryMatch = (scope == "All") || (candy.category == scope)

  var stringMatch = candy.name.rangeOfString(searchText)

  return categoryMatch && (stringMatch != nil)

  })

}

这个方法现在获取一个 scope 变量(默认值为全部)。这个方法默认选取所有的分类,并且如果有策略和字符串都匹配的则返回true(或者手动设置分类为全部)。

现在你可以改变这个方法,你需要改变两个 searchDisplayController 方法以改变范围:

func searchDisplayController(controller: UISearchDisplayController!, shouldReloadTableForSearchString searchString: String!) -> Bool {

  let scopes = self.searchDisplayController.searchBar.scopeButtonTitles as [String]

  let selectedScope = scopes[self.searchDisplayController.searchBar.selectedScopeButtonIndex] as String

  self.filterContentForSearchText(searchString, scope: selectedScope)

  return true

}



func searchDisplayController(controller: UISearchDisplayController!, shouldReloadTableForSearchScope searchOption: Int) -> Bool {

  let scope = self.searchDisplayController.searchBar.scopeButtonTitles as [String]

  self.filterContentForSearchText(self.searchDisplayController.searchBar.text, scope: scope[searchOption])

  return true

}

所有的方法将分类从搜索条发送到过滤方法。运行。你将会看到如下内容:

Mou icon

接下来你应该关注哪些东西?

恭喜你 - 你现在已经完成了一个可以在主table view中直接搜索内容的应用了!这是一个实例,其中包含引导中所有的代码。

Table views在应用中被广泛使用,因为他为触摸控制搜索提供了良好的支持。对于UISearchBar和UISearchDisplayController,iOS提供了大量的创造性方法,所以没有理由不去使用。当人们发现控件没有搜索选项是,他们肯定不会乐于使用他们!

Mou icon

不要让这样的事情发生在你的用户身上。始终保持提供搜索方法。


我们希望你能在你的table view应用中添加搜索的能力。如果你有什么问题,可以尽情的在下面的讨论区留言!

标签: Swift

?>