1. 1. Demo App Overview下面的两张图是Demo的最终效果: 第一张图,是一个UISearchController的默认搜索栏。第二张图,是自定义的搜索栏。接下来将会介绍到两种搜索栏的实现,但在此之前,还需要介绍下这个项目。 和往常一样,可以先下载一个初始项目。下载下来后,使用Xcode打开,可以先看一下工程目录。介于最终效果比较简单,所以文件并不是太多。在StroryBoard中有一个tableView,它将用于展示数据、搜索栏和搜索结果。除此之外,在ViewController类中有少量的tableView delegate与dataSource方法的实现。 在这个案例中,我们的数据源是全世界所有的国家名称。在工程中,可以找到一个名为countries.txt的文件,我们需要做的就是将这个文件加载到程序中,存入数组并使用。国家列表文件来源于这个网站。 接下来的所有操作,都可以分为以下三个步骤: 首先,我们需要加载数据,这样可以在之后方面调用; 我们将使用UISearchController来实现默认的搜索功能; 我们将继承并自定义search controller与search bar,从而改变搜索栏的默认界面。 接下来,我们一起来细化每一个步骤吧。 加载并显示样本数据我们将从加载countries.txt文件并将它放入数组开始,这样tableView便有了数据源。接着,需要将整个国家列表展示出来。当完成这一步以后,我们就可以将注意力放在实现两种搜索方法设计上了。 在加载内容到数组里之前,先要在ViewController类中声明一些属性,接着便可以直接使用这些属性了。在ViewController.swift文件中添加以下三行: var dataArray = [String]()var filteredArray = [String]()var shouldShowSearchResults = false 让我解释一下上面的代码。dataArray数组包含的内容就是要显示在tableView上的国家列表。要记住,这个数组只在没有搜索操作的时候作为dataSource。当开始搜索时,filteredArray将会作为dataSource,里面只包含了符合搜索条件的国家名称。具体由哪个数组来作为tableView的dataSource,将由shouldShowSearchResults这个属性来指定。 现在,让我们把countries.txt文件中的数据加载到dataArray数组中,这样就能在tableView中使用了。在初始项目中,有一个对tableView简单的实现,接下来要继续对它进行完善。需要添加一个新的方法loadListOfCountries()。具体做法是:首先需要将countries.txt文件中的内容加载到一个字符串中,然后按照换行符将字符串拆分为数组。最终得到的数组将作为tableView的原始数据。来看一下代码: func loadListOfCountries() { // Specify the path to the countries list file. let pathToFile = NSBundle.mainBundle().pathForResource("countries", ofType: "txt") if let path = pathToFile { // Load the file contents as a string. let countriesString = String(contentsOfFile: path, encoding: NSUTF8StringEncoding, error: nil)! // Append the countries from the string to the dataArray array by breaking them using the line change character. dataArray = countriesString.componentsSeparatedByString("\n") // Reload the tableview. tblSearchResults.reloadData() }} 然后,在viewDidLoad()方法中调用。 override func viewDidLoad() { ? ... ? loadListOfCountries()} 我们现在需要更新几个与tableView相关的方法,tableView的显示行数,应该取决于当前使用的是哪一个数组: func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { ? if shouldShowSearchResults { ? ? ? return filteredArray.count ? } ? else { ? ? ? return dataArray.count ? }} 接着,指定单元行的内容: func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { var cell = tableView.dequeueReusableCellWithIdentifier("idCell", forIndexPath: indexPath) as! UITableViewCell if shouldShowSearchResults { cell.textLabel?.text = filteredArray[indexPath.row] } else { cell.textLabel?.text = dataArray[indexPath.row] } return cell} 现在可以运行程序就可以看到国家列表显示在了tableView上。目前为止,我们还没有做一些比较新、比较难的操作,接下来让我们来配置搜索控制器并显示默认的搜索栏。 配置UISearchController为了能使用UISearchController在tableView中显示搜索结果,还有必要添加一个属性。所以,在ViewController类的顶部添加一下代码: var searchController: UISearchController! 为实现需要的效果,需要添加一个名为configureSearchController()的自定义方法。在这个方法中,要初始化刚才定义的属性并给它配置一些属性。事实上,搜索控制就是有一些特殊属性的ViewController。 其中一个属性就是searchBar,它相当于显示在tableView顶部的搜索栏。还有一些属性会在接下来的方法中进行设置。搜索栏并不会自动显示在tableView上,所以我们需要手动进行设置,接下来也会讲到。 除此之外,搜索控制器和搜索栏还有很多属性可以配置。详细信息可以查看在这里和这里。 接下来,让我们从一个新方法开始,一步一步的进行配置。首先,初始化searchController: func configureSearchController() { searchController = UISearchController(searchResultsController: nil)} 这里有个重要的细节: Demo中用来显示搜索结果的tableView和搜索控制器在同一个视图控制器中。但是在一些情况下,用于展示搜索结果的视图控制器和正在执行搜索的控制器是不同的,并且搜索控制器必须以某种方式知道这个情况。唯一的方式就是通过上面说的初始化方法。当传参是nil时,搜索控制器知道存在另一个视图控制器来处理和显示搜索的结果。任何其他情况下,展示搜索结果的控制器和用于搜索的控制器是不同的。 我们有两种方式来实现在键入搜索词时,将搜索结果显示到tableView上。第一种方式是利用search bar的代理,第二种方式是将ViewController类作为searchResultsUpdater的代理。我们将会在自定义搜索栏的时候使用到第一种方法,所以现在先介绍第二种方法。既然如此,我们需要让ViewController类遵循UIResultsUpdating协议。这个协议是iOS8更新的,具体描述为:基于键入的搜索词,更新搜索结果。这个协议只有一个代理方法需要实现,但我们等会在弄。现在先遵循一下这个协议: class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating 再次回到configureSearchController()方法,添加一句代码,这样就离成功又近了一步: func configureSearchController() { ... searchController.searchResultsUpdater = self} 除此之外,你也可以看看根据需要,设置一些其他的属性。比如,下面这个属性设为true,那么开始输入时,背景就会变暗。 func configureSearchController() { ... searchController.dimsBackgroundDuringPresentation = true} 通常情况下,当搜索控制器与显示结果的tableView在同一个视图控制器中时,是不会将背景变暗的,所以,还是吧上面的dimsBackgroundDuringPresentation属性设为false吧。 现在来配置一下搜索栏,给它添加一个placeholder: func configureSearchController() { ... searchController.searchBar.placeholder = "Search here..."} 除此之外,再设置一下搜索栏的代理,等下就可以使用代理方法了: func configureSearchController() { ... searchController.searchBar.delegate = self} 然后,这里有一个比较取巧的方式来设置搜索栏的大小: func configureSearchController() { ... searchController.searchBar.sizeToFit()} 想要让搜索栏显示出来,还必须要加上下面这句代码: func configureSearchController() { ... tblSearchResults.tableHeaderView = searchController.searchBar} 到此,整个configureSearchController()方法中的代码如下: func configureSearchController() { // Initialize and perform a minimum configuration to the search controller. searchController = UISearchController(searchResultsController: nil) searchController.searchResultsUpdater = self searchController.dimsBackgroundDuringPresentation = false searchController.searchBar.placeholder = "Search here..." searchController.searchBar.delegate = self searchController.searchBar.sizeToFit() // Place the search bar view to the tableview headerview. tblSearchResults.tableHeaderView = searchController.searchBar} 现在,到ViewController类的顶部,遵循UISearchBarDelegate协议: class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating, UISearchBarDelegate 接着,在viewDidLoad()中调用configureSearchController()方法: override func viewDidLoad() { ... configureSearchController()} 以上就是想要使用UISearchController将搜索栏显示在tableView上的全部代码。现在我们可以处理搜索结果了。现在不要去管Xcode报的错,当我们将缺失的代理方法实现以后,错误就会消失。 处理搜索操作现在来处理搜索和显示结果操作吧。这一步分为两个部分。首先,当在搜索栏键入关键字或者键盘上的搜索按钮被点击时,能够将搜索结果返回。对于边输入边搜索这个操作,并不强制实现。如果你允许,也可以在输入时,跳过返回结果这个步骤。但是,点击搜索按钮时,显示搜索结果这个是必须要实现的,否则搜索栏就变得毫无意义了。在这两种情况下,filteredArray都将是tableView的dataSource,在没有进行搜索时,filteredArray应该是上一次的搜索结果。 我们已经使用shouldShowSearchResults属性来判断应该用哪一个数组作为dataSource了,但目前为止,还没有代码修改过shouldShowSearchResults的值。那么现在来完成它,它的值取决于是否在进行搜索操作。 根据这个逻辑,我们先来实现UISearchBarDelegate的两个代理方法。正如你所看到的,在这两个方法中,我们对shouldShowSearchResults的值进行了修改,并且刷新了tableView: func searchBarTextDidBeginEditing(searchBar: UISearchBar) { ? shouldShowSearchResults = true ? tblSearchResults.reloadData()}func searchBarCancelButtonClicked(searchBar: UISearchBar) { ? shouldShowSearchResults = false ? tblSearchResults.reloadData()} 第一个方法会在搜索开始时,将filteredArray作为dataSource。同理,第二个方法会在取消搜索按钮按下时,将dataArray作为dataSource。 接下来,实现另一个代理方法,它将会显示搜索结果,并且会在搜索按钮按下时,取消搜索框的第一响应。注意,下面的这个if条件在不想要边输入边搜索的时候很有用。 func searchBarSearchButtonClicked(searchBar: UISearchBar) { if !shouldShowSearchResults { shouldShowSearchResults = true tblSearchResults.reloadData() } searchController.searchBar.resignFirstResponder()} 正如写在代理方法中的条件判断一样,给shouldShowSearchResults正确的值并且刷新tableView,就能每次都在视图控制器中显示正确的数据了。 上面的每一步都很完美,但还是缺少一个重要的步骤。目前为止,我们都还没有和filteredArray打过交道,所以上面的代码并不能得到我们想要的结果。在上一节中,我们遵循了UISearchResultsUpdating协议,那时我说,这个协议中只有一个方法需要实现。那好,现在我们来看一下这个方法。我们将在这个方法中,根据输入的关键字,来把原始数据过滤一下,把符合条件的数据,放入filteredArray数组: func updateSearchResultsForSearchController(searchController: UISearchController) { let searchString = searchController.searchBar.text // Filter the data array and get only those countries that match the search text. filteredArray = dataArray.filter({ (country) -> Bool in let countryText: NSString = country return (countryText.rangeOfString(searchString, options: NSStringCompareOptions.CaseInsensitiveSearch).location) != NSNotFound }) // Reload the tableview. tblSearchResults.reloadData()} 让我们深入了解下这个方法都干了啥。首先,我们将输入的关键字赋给了一个字符串常量searchString。其实可以不用专门拿中间量来接收的,但是这样更方面。 上面这个方法的核心,就是使用filter方法来过滤dataArray的操作。它的功能就像filter这个名字一样,它根据在闭包中写的过滤条件来过滤原始数据,然后将满足条件的数据添加到filteredArray中。每个国家字符串都作为闭包的参数传入,参数名为country。接着,将country转为了类型是NSString的常量countryText。这个操作是为了保证接下来调用NSString的rangeOfString()方法时不会出现类型错误。rangeOfString()方法能查找关键字(searchString)是否在当前国家字符串中(译者注:如,”斯”是否在”俄罗斯”中),如果能找到,则返回关键字所在的范围(NSRange)。如果关键字不在当前国家字符串中,那么会返回NSNotFound 。因为闭包的返回值是Bool类型,所以我们需要做的就是将rangeOfString()的返回值和NSNotFound 做对比。 在最后,刷新tableView,这样就能显示过滤或的国家列表了。我所提供的只是万千解决方案中的一种,你也可以对上面的代码进行精炼和修改。 现在运行程序来测试下功能。键入一个国家名称,可以看到tableView及时的进行了刷新。测试下搜索和取消按钮,观察App的响应。最后,尝试修改一下之前写的代码,这样更有助于理解。 到此,教程的第一部分就结束了。在接下来这个部分中,你将看到如何重写搜索控制器和搜索栏的样式,如何通过自定义来让他们更加符合App的UI风格。 自定义搜索栏其实要自定义搜索控制器和搜索栏很简单,你只需要继承UISearchController和UISearchBar,然后写入想要的自定义方法和逻辑就可以了。其实,这个步骤在每次需要重写 iOS SDK 时都要经历,所以接下来介绍的步骤以前都做过。 我们的自定义操作将从继承UISearchBar开始。接下来,会把这个自定义的搜索栏用到搜索控制器的子类上。最后,要在ViewController类中使用这两个自定义类。 首先,在 Xcode 创建新文件,步骤是File >New>File…,在左侧的菜单中,选择iOS >Source分类的Cocoa Touch Class。接着,将Subclass of内容设置为UISearchBar,类名设置为CustomSearchBar: 结束创建,并选中打开这个文件。我们开始写自定义初始化方法。在初始化方法中,定义接下来自定义搜索栏和搜索输入框所需的frame,font和text color。在此之前,先定义以下两个属性: var preferredFont: UIFont!var preferredTextColor: UIColor! 下面是自定义初始化方法: init(frame: CGRect, font: UIFont, textColor: UIColor) { super.init(frame: frame) self.frame = frame preferredFont = font preferredTextColor = textColor} 正如你所看到的,上面的代码先给搜索栏设置了frame,拿中间变量接收了font与textColor,这两个属性会在之后使用到。 接下来,用下面一行代码来设置搜索栏的风格: searchBarStyle = UISearchBarStyle.Prominent 这句话使搜索栏有半透明效果,而搜索输入框不透明。但这还不够,我们需要将搜索栏和输入框都设置为不透明的,所以可能需要设置颜色让配色看起来更协调。因此,先添加以下代码: translucent = false 添加上面两句代码后,现在初始化方法变成了: init(frame: CGRect, font: UIFont, textColor: UIColor) { ? super.init(frame: frame) ? self.frame = frame ? preferredFont = font ? preferredTextColor = textColor ? searchBarStyle = UISearchBarStyle.Prominent ? translucent = false} 注意:搜索栏并不只由一个控件组成,相反,它实际上它有一个UIView类型的子视图,在这个视图中,有两个相当重要的子视图:一个是搜索输入框(是UITextField的子类),还有一个是搜索输入框的背景视图。为了更清晰的了解它,我们打印一下它的子视图: println(subviews[0].subviews) 控制台上会显示: 除了刚才的自定义初始方法以外,还需要添加一个初始化方法: required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)} 根据刚才的注意事项,我们需要拿到真正的搜索输入框(就是搜索框中的textfield)。先添加下面这个方法,它将会返回输入框在搜索框所以子视图中的下标: func indexOfSearchFieldInSubviews() -> Int! { var index: Int! let searchBarView = subviews[0] as! UIView for var i=0; i<searchBarView.subviews.count; ++i { if searchBarView.subviews[i].isKindOfClass(UITextField) { index = i break } } return index}</code></pre><p class="">有了上面这个方法,我们就可以通过重写本类中的<strong><span style="color: #008000;" spellcheck="false"><code>drawRect()</code></span></strong>方法来自定义了。在这里,我们有两个非常明确的任务:第一个是获取到输入框并把它改成需要的样子,第二个是给搜索栏画一条自定义的底部线条。</p>具体需要对输入框进行如下修改:<ol> <li>修改一下<span spellcheck="false"><code>frame</code></span>,让它比搜索栏小一点;</li> <li>设置自定义的字体;</li> <li>设置字体颜色;</li> <li>修改背景颜色。这是为了让他更契合搜索栏的主题色,这会在下一部分中介绍到。通过这样的手段,可以让搜索栏和输入框的风格更统一(可以在项目概述的地方看到最终效果)。</li></ol><p class="md-focus">把这些内容转换成代码:</p><pre><code class="swift">override func drawRect(rect: CGRect) { // 获取搜索栏子视图中搜索输入框的下标 if let index = indexOfSearchFieldInSubviews() { // 获取搜索输入框 let searchField: UITextField = (subviews[0] as! UIView).subviews[index] as! UITextField // 设置 frame searchField.frame = CGRectMake(5.0, 5.0, frame.size.width - 10.0, frame.size.height - 10.0) // 设置字体和文字颜色 searchField.font = preferredFont searchField.textColor = preferredTextColor // 设置背景颜色 searchField.backgroundColor = barTintColor } super.drawRect(rect)} 这里也用到了我们之前实现的自定义方法indexOfSearchFieldInSubviews()。 最后,给搜索栏底部画一条线。现在,有两件事需要做:第一就是要根据绘出的线创建一条贝塞尔路径,第二就是要根据贝塞尔路径创建一个CAShapeLayer的实例,并给他设置颜色与线宽。最终,这个layer将作为搜索栏的子layer。实现如下: override func drawRect(rect: CGRect) { ... var startPoint = CGPointMake(0.0, frame.size.height) var endPoint = CGPointMake(frame.size.width, frame.size.height) var path = UIBezierPath() path.moveToPoint(startPoint) path.addLineToPoint(endPoint) var shapeLayer = CAShapeLayer() shapeLayer.path = path.CGPath shapeLayer.strokeColor = preferredTextColor.CGColor shapeLayer.lineWidth = 2.5 layer.addSublayer(shapeLayer) super.drawRect(rect)} 当然,你也可以根据喜好或需求来修改线的颜色和线宽。在任何情况下,你都能方便的修改这些属性。 现在,自定义的搜索栏就搞定了。因为还缺少搜索控制器,所以暂时还不能看到效果,不过在下一节就会实现了。在这一节中,重要就是如何正确的去自定义搜索栏,让它和你的App更加和谐。 自定义搜索控制器我们的自定义搜索控制器将会是UISearchController的子类,操作步骤也和上一节差不多。创建文件的步骤都一样,只是要注意Subclass要改为UISearchController,类名是CustomSearchController: 创建完以后,在工程目录里面选中这个类,在开头添加自定义搜索栏的属性: var customSearchBar: CustomSearchBar! 接下来,依然需要添加自定义初始化方法。这个方法有五个参数: 用于显示搜索结果的视图控制器; 搜索栏的frame; 搜索输入框的字体; 搜索输入框的字体颜色; 搜索栏的主题色 代码如下: init(searchResultsController: UIViewController!, searchBarFrame: CGRect, searchBarFont: UIFont, searchBarTextColor: UIColor, searchBarTintColor: UIColor) { super.init(searchResultsController: searchResultsController) configureSearchBar(searchBarFrame, font: searchBarFont, textColor: searchBarTextColor, bgColor: searchBarTintColor)} 这里的configureSearchBar()方法,会在后面进行实现。从方法名可以看出,我们会在这个方法中,对搜索栏进行一些配置。 在实现这个方法之前,还需要添加两个必要的初始化方法: override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)}required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)} 现在可以实现配置搜索栏的方法了,方法很简单: func configureSearchBar(frame: CGRect, font: UIFont, textColor: UIColor, bgColor: UIColor) { customSearchBar = CustomSearchBar(frame: frame, font: font , textColor: textColor) customSearchBar.barTintColor = bgColor customSearchBar.tintColor = textColor customSearchBar.showsBookmarkButton = false customSearchBar.showsCancelButton = true} 上面的第一行代码,利用了搜索栏的自定义初始化方法实例化了一个自定义搜索栏。接下来的事情就简单多了:给自定义搜索栏设置主题色(barTintColor)和它元素的主题色(tintColor)。接着,我们不显示bookmark按钮,而显示取消按钮。当然,这些属性都可以根据自己需求来设置。 现在,自定义搜索控制器就搞定了,即使还没有遵守搜索栏的UISearchBarDelegate协议。这个会在下一个部分中实现。然而,现在已经可以使用这两个自定义类了。 回到ViewController类,在顶部添加以下属性: var customSearchController: CustomSearchController! 接下来,用一个超级简单额方法来初始化自定义的搜索控制器,并且给它设置frame,font和color,代码如下: func configureCustomSearchController() { ? customSearchController = CustomSearchController(searchResultsController: self, searchBarFrame: CGRectMake(0.0, 0.0, tblSearchResults.frame.size.width, 50.0), searchBarFont: UIFont(name: "Futura", size: 16.0)!, searchBarTextColor: UIColor.orangeColor(), searchBarTintColor: UIColor.blackColor()) ? customSearchController.customSearchBar.placeholder = "Search in this awesome bar..." ? tblSearchResults.tableHeaderView = customSearchController.customSearchBar} 通过上面的方法,就可以给自定义搜索栏设置响应的参数。接着,再给搜索框添加placeholder。最后,把搜索栏显示到tableView的header上。 现在,在viewDidLoad()方法中,还要做两件事:调用刚刚实现的configureCustomSearchController(),并注释configureSearchController()方法,防止系统默认搜索栏再显示。 override func viewDidLoad() { ... // configureSearchController() configureCustomSearchController()} 在这个时候,自定义控制器还达不到想要的要求,因为搜索操作还没加上去。这个问题会在下一节中解决。不过现在已经可以看到自定义搜索控制器的效果了。 给自定义搜索控制器添加搜索操作把搜索控制器设置为搜索栏的代理,这就意味着,和搜索相关的方法实现都会放在CustomSearchController类中。然后,自定义一个协议,让ViewController类遵循并实现代理方法。这样就在ViewController中处理搜索结果了,就像上一个部分中,使用系统默认的搜索逻辑一样。 接下来,按照上面逻辑进行实现。首先, 打开CustomSearchController.swift文件,找到configureSearchBar()方法,添加以下代码: func configureSearchBar(frame: CGRect, font: UIFont, textColor: UIColor, bgColor: UIColor) { ... customSearchBar.delegate = self} 将CustomSearchController设置为搜索栏的代理,先在CustomSearchController顶部遵循UISearchBarDelegate, class CustomSearchController: UISearchController, UISearchBarDelegate 接着,创建自定义协议,并添加几个代理方法(下面这段代码要添加在CustomSearchController类之前): protocol CustomSearchControllerDelegate { func didStartSearching() func didTapOnSearchButton() func didTapOnCancelButton() func didChangeSearchText(searchText: String)} 代理方法的功能就不多做解释了,完全的见名知意。在CustomSearchController类中,定义delegate属性: var customDelegate: CustomSearchControllerDelegate! 这时,我们就可以添加搜索栏协议剩下的代理方法了,然后这些方法中,调用响应的CustomSearchControllerDelegate代理方法。 首先,当搜索框开始编辑时: func searchBarTextDidBeginEditing(searchBar: UISearchBar) { customDelegate.didStartSearching()} 当键盘上的搜索按钮点击时: func searchBarSearchButtonClicked(searchBar: UISearchBar) { ? customSearchBar.resignFirstResponder() ? customDelegate.didTapOnSearchButton()} 当搜索栏上的取消按钮点击时: func searchBarSearchButtonClicked(searchBar: UISearchBar) { customSearchBar.resignFirstResponder() customDelegate.didTapOnSearchButton()} 最后,当搜索关键字变化时: func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { customDelegate.didChangeSearchText(searchText)} 以上的这些方法,会在搜索开始、结束和关键字改变时告诉ViewController。现在,打开ViewController.swift文件,在configureCustomSearchController()方法中添加下面这行代码: func configureCustomSearchController() { ... customSearchController.customDelegate = self} 别忘了在类头部遵循CustomSearchControllerDelegate协议,这样实现的代理方法才有效: class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating, UISearchBarDelegate, CustomSearchControllerDelegate 最后,让我们来实现CustomSearchControllerDelegate的代理方法,这和普通的搜索控制器的处理差不多,代码如下: 开始搜索时: func didStartSearching() { shouldShowSearchResults = true tblSearchResults.reloadData()} 点击搜索按钮时: func didTapOnSearchButton() { if !shouldShowSearchResults { shouldShowSearchResults = true tblSearchResults.reloadData() }} 点击取消按钮时: func didTapOnCancelButton() { shouldShowSearchResults = false tblSearchResults.reloadData()} 搜索关键字改变时: func didChangeSearchText(searchText: String) { // Filter the data array and get only those countries that match the search text. filteredArray = dataArray.filter({ (country) -> Bool in let countryText: NSString = country return (countryText.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch).location) != NSNotFound }) // Reload the tableview. tblSearchResults.reloadData()} 到此,整个 App 的功能就已经完全 ok 了,并且使用的是自定义的搜索控制器。运行看下效果: 总结再开速浏览一遍上面的实现流程,不管是使用 iOS 8 提供的UISearchController,还是自定义搜索栏和搜索控制器,都还比较容易理解。得到的结论都是一样的:我们可以很完美的在自己的 App 使用搜索功能。如果你的 App 对 UI 的要求不是很严格,那么可以选用默认的搜索栏样式,如果需要自定义的样式,那也可以搞定,反正已经知道该怎么做了。是时候结束了,希望这篇教程能在需要的时候帮到你。 完整的 Xcode 代码可以在这里下载。
  2. 2. Demo App Overview
  3. 3. 加载并显示样本数据
  4. 4. 配置UISearchController为了能使用UISearchController在tableView中显示搜索结果,还有必要添加一个属性。所以,在ViewController类的顶部添加一下代码: var searchController: UISearchController! 为实现需要的效果,需要添加一个名为configureSearchController()的自定义方法。在这个方法中,要初始化刚才定义的属性并给它配置一些属性。事实上,搜索控制就是有一些特殊属性的ViewController。 其中一个属性就是searchBar,它相当于显示在tableView顶部的搜索栏。还有一些属性会在接下来的方法中进行设置。搜索栏并不会自动显示在tableView上,所以我们需要手动进行设置,接下来也会讲到。 除此之外,搜索控制器和搜索栏还有很多属性可以配置。详细信息可以查看在这里和这里。 接下来,让我们从一个新方法开始,一步一步的进行配置。首先,初始化searchController: func configureSearchController() { searchController = UISearchController(searchResultsController: nil)} 这里有个重要的细节: Demo中用来显示搜索结果的tableView和搜索控制器在同一个视图控制器中。但是在一些情况下,用于展示搜索结果的视图控制器和正在执行搜索的控制器是不同的,并且搜索控制器必须以某种方式知道这个情况。唯一的方式就是通过上面说的初始化方法。当传参是nil时,搜索控制器知道存在另一个视图控制器来处理和显示搜索的结果。任何其他情况下,展示搜索结果的控制器和用于搜索的控制器是不同的。 我们有两种方式来实现在键入搜索词时,将搜索结果显示到tableView上。第一种方式是利用search bar的代理,第二种方式是将ViewController类作为searchResultsUpdater的代理。我们将会在自定义搜索栏的时候使用到第一种方法,所以现在先介绍第二种方法。既然如此,我们需要让ViewController类遵循UIResultsUpdating协议。这个协议是iOS8更新的,具体描述为:基于键入的搜索词,更新搜索结果。这个协议只有一个代理方法需要实现,但我们等会在弄。现在先遵循一下这个协议: class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating 再次回到configureSearchController()方法,添加一句代码,这样就离成功又近了一步: func configureSearchController() { ... searchController.searchResultsUpdater = self} 除此之外,你也可以看看根据需要,设置一些其他的属性。比如,下面这个属性设为true,那么开始输入时,背景就会变暗。 func configureSearchController() { ... searchController.dimsBackgroundDuringPresentation = true} 通常情况下,当搜索控制器与显示结果的tableView在同一个视图控制器中时,是不会将背景变暗的,所以,还是吧上面的dimsBackgroundDuringPresentation属性设为false吧。 现在来配置一下搜索栏,给它添加一个placeholder: func configureSearchController() { ... searchController.searchBar.placeholder = "Search here..."} 除此之外,再设置一下搜索栏的代理,等下就可以使用代理方法了: func configureSearchController() { ... searchController.searchBar.delegate = self} 然后,这里有一个比较取巧的方式来设置搜索栏的大小: func configureSearchController() { ... searchController.searchBar.sizeToFit()} 想要让搜索栏显示出来,还必须要加上下面这句代码: func configureSearchController() { ... tblSearchResults.tableHeaderView = searchController.searchBar} 到此,整个configureSearchController()方法中的代码如下: func configureSearchController() { // Initialize and perform a minimum configuration to the search controller. searchController = UISearchController(searchResultsController: nil) searchController.searchResultsUpdater = self searchController.dimsBackgroundDuringPresentation = false searchController.searchBar.placeholder = "Search here..." searchController.searchBar.delegate = self searchController.searchBar.sizeToFit() // Place the search bar view to the tableview headerview. tblSearchResults.tableHeaderView = searchController.searchBar} 现在,到ViewController类的顶部,遵循UISearchBarDelegate协议: class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating, UISearchBarDelegate 接着,在viewDidLoad()中调用configureSearchController()方法: override func viewDidLoad() { ... configureSearchController()} 以上就是想要使用UISearchController将搜索栏显示在tableView上的全部代码。现在我们可以处理搜索结果了。现在不要去管Xcode报的错,当我们将缺失的代理方法实现以后,错误就会消失。 处理搜索操作现在来处理搜索和显示结果操作吧。这一步分为两个部分。首先,当在搜索栏键入关键字或者键盘上的搜索按钮被点击时,能够将搜索结果返回。对于边输入边搜索这个操作,并不强制实现。如果你允许,也可以在输入时,跳过返回结果这个步骤。但是,点击搜索按钮时,显示搜索结果这个是必须要实现的,否则搜索栏就变得毫无意义了。在这两种情况下,filteredArray都将是tableView的dataSource,在没有进行搜索时,filteredArray应该是上一次的搜索结果。 我们已经使用shouldShowSearchResults属性来判断应该用哪一个数组作为dataSource了,但目前为止,还没有代码修改过shouldShowSearchResults的值。那么现在来完成它,它的值取决于是否在进行搜索操作。 根据这个逻辑,我们先来实现UISearchBarDelegate的两个代理方法。正如你所看到的,在这两个方法中,我们对shouldShowSearchResults的值进行了修改,并且刷新了tableView: func searchBarTextDidBeginEditing(searchBar: UISearchBar) { ? shouldShowSearchResults = true ? tblSearchResults.reloadData()}func searchBarCancelButtonClicked(searchBar: UISearchBar) { ? shouldShowSearchResults = false ? tblSearchResults.reloadData()} 第一个方法会在搜索开始时,将filteredArray作为dataSource。同理,第二个方法会在取消搜索按钮按下时,将dataArray作为dataSource。 接下来,实现另一个代理方法,它将会显示搜索结果,并且会在搜索按钮按下时,取消搜索框的第一响应。注意,下面的这个if条件在不想要边输入边搜索的时候很有用。 func searchBarSearchButtonClicked(searchBar: UISearchBar) { if !shouldShowSearchResults { shouldShowSearchResults = true tblSearchResults.reloadData() } searchController.searchBar.resignFirstResponder()} 正如写在代理方法中的条件判断一样,给shouldShowSearchResults正确的值并且刷新tableView,就能每次都在视图控制器中显示正确的数据了。 上面的每一步都很完美,但还是缺少一个重要的步骤。目前为止,我们都还没有和filteredArray打过交道,所以上面的代码并不能得到我们想要的结果。在上一节中,我们遵循了UISearchResultsUpdating协议,那时我说,这个协议中只有一个方法需要实现。那好,现在我们来看一下这个方法。我们将在这个方法中,根据输入的关键字,来把原始数据过滤一下,把符合条件的数据,放入filteredArray数组: func updateSearchResultsForSearchController(searchController: UISearchController) { let searchString = searchController.searchBar.text // Filter the data array and get only those countries that match the search text. filteredArray = dataArray.filter({ (country) -> Bool in let countryText: NSString = country return (countryText.rangeOfString(searchString, options: NSStringCompareOptions.CaseInsensitiveSearch).location) != NSNotFound }) // Reload the tableview. tblSearchResults.reloadData()} 让我们深入了解下这个方法都干了啥。首先,我们将输入的关键字赋给了一个字符串常量searchString。其实可以不用专门拿中间量来接收的,但是这样更方面。 上面这个方法的核心,就是使用filter方法来过滤dataArray的操作。它的功能就像filter这个名字一样,它根据在闭包中写的过滤条件来过滤原始数据,然后将满足条件的数据添加到filteredArray中。每个国家字符串都作为闭包的参数传入,参数名为country。接着,将country转为了类型是NSString的常量countryText。这个操作是为了保证接下来调用NSString的rangeOfString()方法时不会出现类型错误。rangeOfString()方法能查找关键字(searchString)是否在当前国家字符串中(译者注:如,”斯”是否在”俄罗斯”中),如果能找到,则返回关键字所在的范围(NSRange)。如果关键字不在当前国家字符串中,那么会返回NSNotFound 。因为闭包的返回值是Bool类型,所以我们需要做的就是将rangeOfString()的返回值和NSNotFound 做对比。 在最后,刷新tableView,这样就能显示过滤或的国家列表了。我所提供的只是万千解决方案中的一种,你也可以对上面的代码进行精炼和修改。 现在运行程序来测试下功能。键入一个国家名称,可以看到tableView及时的进行了刷新。测试下搜索和取消按钮,观察App的响应。最后,尝试修改一下之前写的代码,这样更有助于理解。 到此,教程的第一部分就结束了。在接下来这个部分中,你将看到如何重写搜索控制器和搜索栏的样式,如何通过自定义来让他们更加符合App的UI风格。 自定义搜索栏其实要自定义搜索控制器和搜索栏很简单,你只需要继承UISearchController和UISearchBar,然后写入想要的自定义方法和逻辑就可以了。其实,这个步骤在每次需要重写 iOS SDK 时都要经历,所以接下来介绍的步骤以前都做过。 我们的自定义操作将从继承UISearchBar开始。接下来,会把这个自定义的搜索栏用到搜索控制器的子类上。最后,要在ViewController类中使用这两个自定义类。 首先,在 Xcode 创建新文件,步骤是File >New>File…,在左侧的菜单中,选择iOS >Source分类的Cocoa Touch Class。接着,将Subclass of内容设置为UISearchBar,类名设置为CustomSearchBar: 结束创建,并选中打开这个文件。我们开始写自定义初始化方法。在初始化方法中,定义接下来自定义搜索栏和搜索输入框所需的frame,font和text color。在此之前,先定义以下两个属性: var preferredFont: UIFont!var preferredTextColor: UIColor! 下面是自定义初始化方法: init(frame: CGRect, font: UIFont, textColor: UIColor) { super.init(frame: frame) self.frame = frame preferredFont = font preferredTextColor = textColor} 正如你所看到的,上面的代码先给搜索栏设置了frame,拿中间变量接收了font与textColor,这两个属性会在之后使用到。 接下来,用下面一行代码来设置搜索栏的风格: searchBarStyle = UISearchBarStyle.Prominent 这句话使搜索栏有半透明效果,而搜索输入框不透明。但这还不够,我们需要将搜索栏和输入框都设置为不透明的,所以可能需要设置颜色让配色看起来更协调。因此,先添加以下代码: translucent = false 添加上面两句代码后,现在初始化方法变成了: init(frame: CGRect, font: UIFont, textColor: UIColor) { ? super.init(frame: frame) ? self.frame = frame ? preferredFont = font ? preferredTextColor = textColor ? searchBarStyle = UISearchBarStyle.Prominent ? translucent = false} 注意:搜索栏并不只由一个控件组成,相反,它实际上它有一个UIView类型的子视图,在这个视图中,有两个相当重要的子视图:一个是搜索输入框(是UITextField的子类),还有一个是搜索输入框的背景视图。为了更清晰的了解它,我们打印一下它的子视图: println(subviews[0].subviews) 控制台上会显示: 除了刚才的自定义初始方法以外,还需要添加一个初始化方法: required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)} 根据刚才的注意事项,我们需要拿到真正的搜索输入框(就是搜索框中的textfield)。先添加下面这个方法,它将会返回输入框在搜索框所以子视图中的下标: func indexOfSearchFieldInSubviews() -> Int! { var index: Int! let searchBarView = subviews[0] as! UIView for var i=0; i<searchBarView.subviews.count; ++i { if searchBarView.subviews[i].isKindOfClass(UITextField) { index = i break } } return index}</code></pre><p class="">有了上面这个方法,我们就可以通过重写本类中的<strong><span style="color: #008000;" spellcheck="false"><code>drawRect()</code></span></strong>方法来自定义了。在这里,我们有两个非常明确的任务:第一个是获取到输入框并把它改成需要的样子,第二个是给搜索栏画一条自定义的底部线条。</p>具体需要对输入框进行如下修改:<ol> <li>修改一下<span spellcheck="false"><code>frame</code></span>,让它比搜索栏小一点;</li> <li>设置自定义的字体;</li> <li>设置字体颜色;</li> <li>修改背景颜色。这是为了让他更契合搜索栏的主题色,这会在下一部分中介绍到。通过这样的手段,可以让搜索栏和输入框的风格更统一(可以在项目概述的地方看到最终效果)。</li></ol><p class="md-focus">把这些内容转换成代码:</p><pre><code class="swift">override func drawRect(rect: CGRect) { // 获取搜索栏子视图中搜索输入框的下标 if let index = indexOfSearchFieldInSubviews() { // 获取搜索输入框 let searchField: UITextField = (subviews[0] as! UIView).subviews[index] as! UITextField // 设置 frame searchField.frame = CGRectMake(5.0, 5.0, frame.size.width - 10.0, frame.size.height - 10.0) // 设置字体和文字颜色 searchField.font = preferredFont searchField.textColor = preferredTextColor // 设置背景颜色 searchField.backgroundColor = barTintColor } super.drawRect(rect)} 这里也用到了我们之前实现的自定义方法indexOfSearchFieldInSubviews()。 最后,给搜索栏底部画一条线。现在,有两件事需要做:第一就是要根据绘出的线创建一条贝塞尔路径,第二就是要根据贝塞尔路径创建一个CAShapeLayer的实例,并给他设置颜色与线宽。最终,这个layer将作为搜索栏的子layer。实现如下: override func drawRect(rect: CGRect) { ... var startPoint = CGPointMake(0.0, frame.size.height) var endPoint = CGPointMake(frame.size.width, frame.size.height) var path = UIBezierPath() path.moveToPoint(startPoint) path.addLineToPoint(endPoint) var shapeLayer = CAShapeLayer() shapeLayer.path = path.CGPath shapeLayer.strokeColor = preferredTextColor.CGColor shapeLayer.lineWidth = 2.5 layer.addSublayer(shapeLayer) super.drawRect(rect)} 当然,你也可以根据喜好或需求来修改线的颜色和线宽。在任何情况下,你都能方便的修改这些属性。 现在,自定义的搜索栏就搞定了。因为还缺少搜索控制器,所以暂时还不能看到效果,不过在下一节就会实现了。在这一节中,重要就是如何正确的去自定义搜索栏,让它和你的App更加和谐。 自定义搜索控制器我们的自定义搜索控制器将会是UISearchController的子类,操作步骤也和上一节差不多。创建文件的步骤都一样,只是要注意Subclass要改为UISearchController,类名是CustomSearchController: 创建完以后,在工程目录里面选中这个类,在开头添加自定义搜索栏的属性: var customSearchBar: CustomSearchBar! 接下来,依然需要添加自定义初始化方法。这个方法有五个参数: 用于显示搜索结果的视图控制器; 搜索栏的frame; 搜索输入框的字体; 搜索输入框的字体颜色; 搜索栏的主题色 代码如下: init(searchResultsController: UIViewController!, searchBarFrame: CGRect, searchBarFont: UIFont, searchBarTextColor: UIColor, searchBarTintColor: UIColor) { super.init(searchResultsController: searchResultsController) configureSearchBar(searchBarFrame, font: searchBarFont, textColor: searchBarTextColor, bgColor: searchBarTintColor)} 这里的configureSearchBar()方法,会在后面进行实现。从方法名可以看出,我们会在这个方法中,对搜索栏进行一些配置。 在实现这个方法之前,还需要添加两个必要的初始化方法: override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)}required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)} 现在可以实现配置搜索栏的方法了,方法很简单: func configureSearchBar(frame: CGRect, font: UIFont, textColor: UIColor, bgColor: UIColor) { customSearchBar = CustomSearchBar(frame: frame, font: font , textColor: textColor) customSearchBar.barTintColor = bgColor customSearchBar.tintColor = textColor customSearchBar.showsBookmarkButton = false customSearchBar.showsCancelButton = true} 上面的第一行代码,利用了搜索栏的自定义初始化方法实例化了一个自定义搜索栏。接下来的事情就简单多了:给自定义搜索栏设置主题色(barTintColor)和它元素的主题色(tintColor)。接着,我们不显示bookmark按钮,而显示取消按钮。当然,这些属性都可以根据自己需求来设置。 现在,自定义搜索控制器就搞定了,即使还没有遵守搜索栏的UISearchBarDelegate协议。这个会在下一个部分中实现。然而,现在已经可以使用这两个自定义类了。 回到ViewController类,在顶部添加以下属性: var customSearchController: CustomSearchController! 接下来,用一个超级简单额方法来初始化自定义的搜索控制器,并且给它设置frame,font和color,代码如下: func configureCustomSearchController() { ? customSearchController = CustomSearchController(searchResultsController: self, searchBarFrame: CGRectMake(0.0, 0.0, tblSearchResults.frame.size.width, 50.0), searchBarFont: UIFont(name: "Futura", size: 16.0)!, searchBarTextColor: UIColor.orangeColor(), searchBarTintColor: UIColor.blackColor()) ? customSearchController.customSearchBar.placeholder = "Search in this awesome bar..." ? tblSearchResults.tableHeaderView = customSearchController.customSearchBar} 通过上面的方法,就可以给自定义搜索栏设置响应的参数。接着,再给搜索框添加placeholder。最后,把搜索栏显示到tableView的header上。 现在,在viewDidLoad()方法中,还要做两件事:调用刚刚实现的configureCustomSearchController(),并注释configureSearchController()方法,防止系统默认搜索栏再显示。 override func viewDidLoad() { ... // configureSearchController() configureCustomSearchController()} 在这个时候,自定义控制器还达不到想要的要求,因为搜索操作还没加上去。这个问题会在下一节中解决。不过现在已经可以看到自定义搜索控制器的效果了。 给自定义搜索控制器添加搜索操作把搜索控制器设置为搜索栏的代理,这就意味着,和搜索相关的方法实现都会放在CustomSearchController类中。然后,自定义一个协议,让ViewController类遵循并实现代理方法。这样就在ViewController中处理搜索结果了,就像上一个部分中,使用系统默认的搜索逻辑一样。 接下来,按照上面逻辑进行实现。首先, 打开CustomSearchController.swift文件,找到configureSearchBar()方法,添加以下代码: func configureSearchBar(frame: CGRect, font: UIFont, textColor: UIColor, bgColor: UIColor) { ... customSearchBar.delegate = self} 将CustomSearchController设置为搜索栏的代理,先在CustomSearchController顶部遵循UISearchBarDelegate, class CustomSearchController: UISearchController, UISearchBarDelegate 接着,创建自定义协议,并添加几个代理方法(下面这段代码要添加在CustomSearchController类之前): protocol CustomSearchControllerDelegate { func didStartSearching() func didTapOnSearchButton() func didTapOnCancelButton() func didChangeSearchText(searchText: String)} 代理方法的功能就不多做解释了,完全的见名知意。在CustomSearchController类中,定义delegate属性: var customDelegate: CustomSearchControllerDelegate! 这时,我们就可以添加搜索栏协议剩下的代理方法了,然后这些方法中,调用响应的CustomSearchControllerDelegate代理方法。 首先,当搜索框开始编辑时: func searchBarTextDidBeginEditing(searchBar: UISearchBar) { customDelegate.didStartSearching()} 当键盘上的搜索按钮点击时: func searchBarSearchButtonClicked(searchBar: UISearchBar) { ? customSearchBar.resignFirstResponder() ? customDelegate.didTapOnSearchButton()} 当搜索栏上的取消按钮点击时: func searchBarSearchButtonClicked(searchBar: UISearchBar) { customSearchBar.resignFirstResponder() customDelegate.didTapOnSearchButton()} 最后,当搜索关键字变化时: func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { customDelegate.didChangeSearchText(searchText)} 以上的这些方法,会在搜索开始、结束和关键字改变时告诉ViewController。现在,打开ViewController.swift文件,在configureCustomSearchController()方法中添加下面这行代码: func configureCustomSearchController() { ... customSearchController.customDelegate = self} 别忘了在类头部遵循CustomSearchControllerDelegate协议,这样实现的代理方法才有效: class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating, UISearchBarDelegate, CustomSearchControllerDelegate 最后,让我们来实现CustomSearchControllerDelegate的代理方法,这和普通的搜索控制器的处理差不多,代码如下: 开始搜索时: func didStartSearching() { shouldShowSearchResults = true tblSearchResults.reloadData()} 点击搜索按钮时: func didTapOnSearchButton() { if !shouldShowSearchResults { shouldShowSearchResults = true tblSearchResults.reloadData() }} 点击取消按钮时: func didTapOnCancelButton() { shouldShowSearchResults = false tblSearchResults.reloadData()} 搜索关键字改变时: func didChangeSearchText(searchText: String) { // Filter the data array and get only those countries that match the search text. filteredArray = dataArray.filter({ (country) -> Bool in let countryText: NSString = country return (countryText.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch).location) != NSNotFound }) // Reload the tableview. tblSearchResults.reloadData()} 到此,整个 App 的功能就已经完全 ok 了,并且使用的是自定义的搜索控制器。运行看下效果: 总结再开速浏览一遍上面的实现流程,不管是使用 iOS 8 提供的UISearchController,还是自定义搜索栏和搜索控制器,都还比较容易理解。得到的结论都是一样的:我们可以很完美的在自己的 App 使用搜索功能。如果你的 App 对 UI 的要求不是很严格,那么可以选用默认的搜索栏样式,如果需要自定义的样式,那也可以搞定,反正已经知道该怎么做了。是时候结束了,希望这篇教程能在需要的时候帮到你。 完整的 Xcode 代码可以在这里下载。
  5. 5. 配置UISearchController
  6. 6. 处理搜索操作
  7. 7. 自定义搜索栏其实要自定义搜索控制器和搜索栏很简单,你只需要继承UISearchController和UISearchBar,然后写入想要的自定义方法和逻辑就可以了。其实,这个步骤在每次需要重写 iOS SDK 时都要经历,所以接下来介绍的步骤以前都做过。 我们的自定义操作将从继承UISearchBar开始。接下来,会把这个自定义的搜索栏用到搜索控制器的子类上。最后,要在ViewController类中使用这两个自定义类。 首先,在 Xcode 创建新文件,步骤是File >New>File…,在左侧的菜单中,选择iOS >Source分类的Cocoa Touch Class。接着,将Subclass of内容设置为UISearchBar,类名设置为CustomSearchBar: 结束创建,并选中打开这个文件。我们开始写自定义初始化方法。在初始化方法中,定义接下来自定义搜索栏和搜索输入框所需的frame,font和text color。在此之前,先定义以下两个属性: var preferredFont: UIFont!var preferredTextColor: UIColor! 下面是自定义初始化方法: init(frame: CGRect, font: UIFont, textColor: UIColor) { super.init(frame: frame) self.frame = frame preferredFont = font preferredTextColor = textColor} 正如你所看到的,上面的代码先给搜索栏设置了frame,拿中间变量接收了font与textColor,这两个属性会在之后使用到。 接下来,用下面一行代码来设置搜索栏的风格: searchBarStyle = UISearchBarStyle.Prominent 这句话使搜索栏有半透明效果,而搜索输入框不透明。但这还不够,我们需要将搜索栏和输入框都设置为不透明的,所以可能需要设置颜色让配色看起来更协调。因此,先添加以下代码: translucent = false 添加上面两句代码后,现在初始化方法变成了: init(frame: CGRect, font: UIFont, textColor: UIColor) { ? super.init(frame: frame) ? self.frame = frame ? preferredFont = font ? preferredTextColor = textColor ? searchBarStyle = UISearchBarStyle.Prominent ? translucent = false} 注意:搜索栏并不只由一个控件组成,相反,它实际上它有一个UIView类型的子视图,在这个视图中,有两个相当重要的子视图:一个是搜索输入框(是UITextField的子类),还有一个是搜索输入框的背景视图。为了更清晰的了解它,我们打印一下它的子视图: println(subviews[0].subviews) 控制台上会显示: 除了刚才的自定义初始方法以外,还需要添加一个初始化方法: required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)} 根据刚才的注意事项,我们需要拿到真正的搜索输入框(就是搜索框中的textfield)。先添加下面这个方法,它将会返回输入框在搜索框所以子视图中的下标: func indexOfSearchFieldInSubviews() -> Int! { var index: Int! let searchBarView = subviews[0] as! UIView for var i=0; i<searchBarView.subviews.count; ++i { if searchBarView.subviews[i].isKindOfClass(UITextField) { index = i break } } return index}</code></pre><p class="">有了上面这个方法,我们就可以通过重写本类中的<strong><span style="color: #008000;" spellcheck="false"><code>drawRect()</code></span></strong>方法来自定义了。在这里,我们有两个非常明确的任务:第一个是获取到输入框并把它改成需要的样子,第二个是给搜索栏画一条自定义的底部线条。</p>具体需要对输入框进行如下修改:<ol> <li>修改一下<span spellcheck="false"><code>frame</code></span>,让它比搜索栏小一点;</li> <li>设置自定义的字体;</li> <li>设置字体颜色;</li> <li>修改背景颜色。这是为了让他更契合搜索栏的主题色,这会在下一部分中介绍到。通过这样的手段,可以让搜索栏和输入框的风格更统一(可以在项目概述的地方看到最终效果)。</li></ol><p class="md-focus">把这些内容转换成代码:</p><pre><code class="swift">override func drawRect(rect: CGRect) { // 获取搜索栏子视图中搜索输入框的下标 if let index = indexOfSearchFieldInSubviews() { // 获取搜索输入框 let searchField: UITextField = (subviews[0] as! UIView).subviews[index] as! UITextField // 设置 frame searchField.frame = CGRectMake(5.0, 5.0, frame.size.width - 10.0, frame.size.height - 10.0) // 设置字体和文字颜色 searchField.font = preferredFont searchField.textColor = preferredTextColor // 设置背景颜色 searchField.backgroundColor = barTintColor } super.drawRect(rect)} 这里也用到了我们之前实现的自定义方法indexOfSearchFieldInSubviews()。 最后,给搜索栏底部画一条线。现在,有两件事需要做:第一就是要根据绘出的线创建一条贝塞尔路径,第二就是要根据贝塞尔路径创建一个CAShapeLayer的实例,并给他设置颜色与线宽。最终,这个layer将作为搜索栏的子layer。实现如下: override func drawRect(rect: CGRect) { ... var startPoint = CGPointMake(0.0, frame.size.height) var endPoint = CGPointMake(frame.size.width, frame.size.height) var path = UIBezierPath() path.moveToPoint(startPoint) path.addLineToPoint(endPoint) var shapeLayer = CAShapeLayer() shapeLayer.path = path.CGPath shapeLayer.strokeColor = preferredTextColor.CGColor shapeLayer.lineWidth = 2.5 layer.addSublayer(shapeLayer) super.drawRect(rect)} 当然,你也可以根据喜好或需求来修改线的颜色和线宽。在任何情况下,你都能方便的修改这些属性。 现在,自定义的搜索栏就搞定了。因为还缺少搜索控制器,所以暂时还不能看到效果,不过在下一节就会实现了。在这一节中,重要就是如何正确的去自定义搜索栏,让它和你的App更加和谐。 自定义搜索控制器我们的自定义搜索控制器将会是UISearchController的子类,操作步骤也和上一节差不多。创建文件的步骤都一样,只是要注意Subclass要改为UISearchController,类名是CustomSearchController: 创建完以后,在工程目录里面选中这个类,在开头添加自定义搜索栏的属性: var customSearchBar: CustomSearchBar! 接下来,依然需要添加自定义初始化方法。这个方法有五个参数: 用于显示搜索结果的视图控制器; 搜索栏的frame; 搜索输入框的字体; 搜索输入框的字体颜色; 搜索栏的主题色 代码如下: init(searchResultsController: UIViewController!, searchBarFrame: CGRect, searchBarFont: UIFont, searchBarTextColor: UIColor, searchBarTintColor: UIColor) { super.init(searchResultsController: searchResultsController) configureSearchBar(searchBarFrame, font: searchBarFont, textColor: searchBarTextColor, bgColor: searchBarTintColor)} 这里的configureSearchBar()方法,会在后面进行实现。从方法名可以看出,我们会在这个方法中,对搜索栏进行一些配置。 在实现这个方法之前,还需要添加两个必要的初始化方法: override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)}required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)} 现在可以实现配置搜索栏的方法了,方法很简单: func configureSearchBar(frame: CGRect, font: UIFont, textColor: UIColor, bgColor: UIColor) { customSearchBar = CustomSearchBar(frame: frame, font: font , textColor: textColor) customSearchBar.barTintColor = bgColor customSearchBar.tintColor = textColor customSearchBar.showsBookmarkButton = false customSearchBar.showsCancelButton = true} 上面的第一行代码,利用了搜索栏的自定义初始化方法实例化了一个自定义搜索栏。接下来的事情就简单多了:给自定义搜索栏设置主题色(barTintColor)和它元素的主题色(tintColor)。接着,我们不显示bookmark按钮,而显示取消按钮。当然,这些属性都可以根据自己需求来设置。 现在,自定义搜索控制器就搞定了,即使还没有遵守搜索栏的UISearchBarDelegate协议。这个会在下一个部分中实现。然而,现在已经可以使用这两个自定义类了。 回到ViewController类,在顶部添加以下属性: var customSearchController: CustomSearchController! 接下来,用一个超级简单额方法来初始化自定义的搜索控制器,并且给它设置frame,font和color,代码如下: func configureCustomSearchController() { ? customSearchController = CustomSearchController(searchResultsController: self, searchBarFrame: CGRectMake(0.0, 0.0, tblSearchResults.frame.size.width, 50.0), searchBarFont: UIFont(name: "Futura", size: 16.0)!, searchBarTextColor: UIColor.orangeColor(), searchBarTintColor: UIColor.blackColor()) ? customSearchController.customSearchBar.placeholder = "Search in this awesome bar..." ? tblSearchResults.tableHeaderView = customSearchController.customSearchBar} 通过上面的方法,就可以给自定义搜索栏设置响应的参数。接着,再给搜索框添加placeholder。最后,把搜索栏显示到tableView的header上。 现在,在viewDidLoad()方法中,还要做两件事:调用刚刚实现的configureCustomSearchController(),并注释configureSearchController()方法,防止系统默认搜索栏再显示。 override func viewDidLoad() { ... // configureSearchController() configureCustomSearchController()} 在这个时候,自定义控制器还达不到想要的要求,因为搜索操作还没加上去。这个问题会在下一节中解决。不过现在已经可以看到自定义搜索控制器的效果了。 给自定义搜索控制器添加搜索操作把搜索控制器设置为搜索栏的代理,这就意味着,和搜索相关的方法实现都会放在CustomSearchController类中。然后,自定义一个协议,让ViewController类遵循并实现代理方法。这样就在ViewController中处理搜索结果了,就像上一个部分中,使用系统默认的搜索逻辑一样。 接下来,按照上面逻辑进行实现。首先, 打开CustomSearchController.swift文件,找到configureSearchBar()方法,添加以下代码: func configureSearchBar(frame: CGRect, font: UIFont, textColor: UIColor, bgColor: UIColor) { ... customSearchBar.delegate = self} 将CustomSearchController设置为搜索栏的代理,先在CustomSearchController顶部遵循UISearchBarDelegate, class CustomSearchController: UISearchController, UISearchBarDelegate 接着,创建自定义协议,并添加几个代理方法(下面这段代码要添加在CustomSearchController类之前): protocol CustomSearchControllerDelegate { func didStartSearching() func didTapOnSearchButton() func didTapOnCancelButton() func didChangeSearchText(searchText: String)} 代理方法的功能就不多做解释了,完全的见名知意。在CustomSearchController类中,定义delegate属性: var customDelegate: CustomSearchControllerDelegate! 这时,我们就可以添加搜索栏协议剩下的代理方法了,然后这些方法中,调用响应的CustomSearchControllerDelegate代理方法。 首先,当搜索框开始编辑时: func searchBarTextDidBeginEditing(searchBar: UISearchBar) { customDelegate.didStartSearching()} 当键盘上的搜索按钮点击时: func searchBarSearchButtonClicked(searchBar: UISearchBar) { ? customSearchBar.resignFirstResponder() ? customDelegate.didTapOnSearchButton()} 当搜索栏上的取消按钮点击时: func searchBarSearchButtonClicked(searchBar: UISearchBar) { customSearchBar.resignFirstResponder() customDelegate.didTapOnSearchButton()} 最后,当搜索关键字变化时: func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { customDelegate.didChangeSearchText(searchText)} 以上的这些方法,会在搜索开始、结束和关键字改变时告诉ViewController。现在,打开ViewController.swift文件,在configureCustomSearchController()方法中添加下面这行代码: func configureCustomSearchController() { ... customSearchController.customDelegate = self} 别忘了在类头部遵循CustomSearchControllerDelegate协议,这样实现的代理方法才有效: class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating, UISearchBarDelegate, CustomSearchControllerDelegate 最后,让我们来实现CustomSearchControllerDelegate的代理方法,这和普通的搜索控制器的处理差不多,代码如下: 开始搜索时: func didStartSearching() { shouldShowSearchResults = true tblSearchResults.reloadData()} 点击搜索按钮时: func didTapOnSearchButton() { if !shouldShowSearchResults { shouldShowSearchResults = true tblSearchResults.reloadData() }} 点击取消按钮时: func didTapOnCancelButton() { shouldShowSearchResults = false tblSearchResults.reloadData()} 搜索关键字改变时: func didChangeSearchText(searchText: String) { // Filter the data array and get only those countries that match the search text. filteredArray = dataArray.filter({ (country) -> Bool in let countryText: NSString = country return (countryText.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch).location) != NSNotFound }) // Reload the tableview. tblSearchResults.reloadData()} 到此,整个 App 的功能就已经完全 ok 了,并且使用的是自定义的搜索控制器。运行看下效果: 总结再开速浏览一遍上面的实现流程,不管是使用 iOS 8 提供的UISearchController,还是自定义搜索栏和搜索控制器,都还比较容易理解。得到的结论都是一样的:我们可以很完美的在自己的 App 使用搜索功能。如果你的 App 对 UI 的要求不是很严格,那么可以选用默认的搜索栏样式,如果需要自定义的样式,那也可以搞定,反正已经知道该怎么做了。是时候结束了,希望这篇教程能在需要的时候帮到你。 完整的 Xcode 代码可以在这里下载。
  8. 8. 自定义搜索栏
  9. 9. 自定义搜索控制器我们的自定义搜索控制器将会是UISearchController的子类,操作步骤也和上一节差不多。创建文件的步骤都一样,只是要注意Subclass要改为UISearchController,类名是CustomSearchController: 创建完以后,在工程目录里面选中这个类,在开头添加自定义搜索栏的属性: var customSearchBar: CustomSearchBar! 接下来,依然需要添加自定义初始化方法。这个方法有五个参数: 用于显示搜索结果的视图控制器; 搜索栏的frame; 搜索输入框的字体; 搜索输入框的字体颜色; 搜索栏的主题色 代码如下: init(searchResultsController: UIViewController!, searchBarFrame: CGRect, searchBarFont: UIFont, searchBarTextColor: UIColor, searchBarTintColor: UIColor) { super.init(searchResultsController: searchResultsController) configureSearchBar(searchBarFrame, font: searchBarFont, textColor: searchBarTextColor, bgColor: searchBarTintColor)} 这里的configureSearchBar()方法,会在后面进行实现。从方法名可以看出,我们会在这个方法中,对搜索栏进行一些配置。 在实现这个方法之前,还需要添加两个必要的初始化方法: override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)}required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)} 现在可以实现配置搜索栏的方法了,方法很简单: func configureSearchBar(frame: CGRect, font: UIFont, textColor: UIColor, bgColor: UIColor) { customSearchBar = CustomSearchBar(frame: frame, font: font , textColor: textColor) customSearchBar.barTintColor = bgColor customSearchBar.tintColor = textColor customSearchBar.showsBookmarkButton = false customSearchBar.showsCancelButton = true} 上面的第一行代码,利用了搜索栏的自定义初始化方法实例化了一个自定义搜索栏。接下来的事情就简单多了:给自定义搜索栏设置主题色(barTintColor)和它元素的主题色(tintColor)。接着,我们不显示bookmark按钮,而显示取消按钮。当然,这些属性都可以根据自己需求来设置。 现在,自定义搜索控制器就搞定了,即使还没有遵守搜索栏的UISearchBarDelegate协议。这个会在下一个部分中实现。然而,现在已经可以使用这两个自定义类了。 回到ViewController类,在顶部添加以下属性: var customSearchController: CustomSearchController! 接下来,用一个超级简单额方法来初始化自定义的搜索控制器,并且给它设置frame,font和color,代码如下: func configureCustomSearchController() { ? customSearchController = CustomSearchController(searchResultsController: self, searchBarFrame: CGRectMake(0.0, 0.0, tblSearchResults.frame.size.width, 50.0), searchBarFont: UIFont(name: "Futura", size: 16.0)!, searchBarTextColor: UIColor.orangeColor(), searchBarTintColor: UIColor.blackColor()) ? customSearchController.customSearchBar.placeholder = "Search in this awesome bar..." ? tblSearchResults.tableHeaderView = customSearchController.customSearchBar} 通过上面的方法,就可以给自定义搜索栏设置响应的参数。接着,再给搜索框添加placeholder。最后,把搜索栏显示到tableView的header上。 现在,在viewDidLoad()方法中,还要做两件事:调用刚刚实现的configureCustomSearchController(),并注释configureSearchController()方法,防止系统默认搜索栏再显示。 override func viewDidLoad() { ... // configureSearchController() configureCustomSearchController()} 在这个时候,自定义控制器还达不到想要的要求,因为搜索操作还没加上去。这个问题会在下一节中解决。不过现在已经可以看到自定义搜索控制器的效果了。 给自定义搜索控制器添加搜索操作把搜索控制器设置为搜索栏的代理,这就意味着,和搜索相关的方法实现都会放在CustomSearchController类中。然后,自定义一个协议,让ViewController类遵循并实现代理方法。这样就在ViewController中处理搜索结果了,就像上一个部分中,使用系统默认的搜索逻辑一样。 接下来,按照上面逻辑进行实现。首先, 打开CustomSearchController.swift文件,找到configureSearchBar()方法,添加以下代码: func configureSearchBar(frame: CGRect, font: UIFont, textColor: UIColor, bgColor: UIColor) { ... customSearchBar.delegate = self} 将CustomSearchController设置为搜索栏的代理,先在CustomSearchController顶部遵循UISearchBarDelegate, class CustomSearchController: UISearchController, UISearchBarDelegate 接着,创建自定义协议,并添加几个代理方法(下面这段代码要添加在CustomSearchController类之前): protocol CustomSearchControllerDelegate { func didStartSearching() func didTapOnSearchButton() func didTapOnCancelButton() func didChangeSearchText(searchText: String)} 代理方法的功能就不多做解释了,完全的见名知意。在CustomSearchController类中,定义delegate属性: var customDelegate: CustomSearchControllerDelegate! 这时,我们就可以添加搜索栏协议剩下的代理方法了,然后这些方法中,调用响应的CustomSearchControllerDelegate代理方法。 首先,当搜索框开始编辑时: func searchBarTextDidBeginEditing(searchBar: UISearchBar) { customDelegate.didStartSearching()} 当键盘上的搜索按钮点击时: func searchBarSearchButtonClicked(searchBar: UISearchBar) { ? customSearchBar.resignFirstResponder() ? customDelegate.didTapOnSearchButton()} 当搜索栏上的取消按钮点击时: func searchBarSearchButtonClicked(searchBar: UISearchBar) { customSearchBar.resignFirstResponder() customDelegate.didTapOnSearchButton()} 最后,当搜索关键字变化时: func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { customDelegate.didChangeSearchText(searchText)} 以上的这些方法,会在搜索开始、结束和关键字改变时告诉ViewController。现在,打开ViewController.swift文件,在configureCustomSearchController()方法中添加下面这行代码: func configureCustomSearchController() { ... customSearchController.customDelegate = self} 别忘了在类头部遵循CustomSearchControllerDelegate协议,这样实现的代理方法才有效: class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating, UISearchBarDelegate, CustomSearchControllerDelegate 最后,让我们来实现CustomSearchControllerDelegate的代理方法,这和普通的搜索控制器的处理差不多,代码如下: 开始搜索时: func didStartSearching() { shouldShowSearchResults = true tblSearchResults.reloadData()} 点击搜索按钮时: func didTapOnSearchButton() { if !shouldShowSearchResults { shouldShowSearchResults = true tblSearchResults.reloadData() }} 点击取消按钮时: func didTapOnCancelButton() { shouldShowSearchResults = false tblSearchResults.reloadData()} 搜索关键字改变时: func didChangeSearchText(searchText: String) { // Filter the data array and get only those countries that match the search text. filteredArray = dataArray.filter({ (country) -> Bool in let countryText: NSString = country return (countryText.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch).location) != NSNotFound }) // Reload the tableview. tblSearchResults.reloadData()} 到此,整个 App 的功能就已经完全 ok 了,并且使用的是自定义的搜索控制器。运行看下效果: 总结再开速浏览一遍上面的实现流程,不管是使用 iOS 8 提供的UISearchController,还是自定义搜索栏和搜索控制器,都还比较容易理解。得到的结论都是一样的:我们可以很完美的在自己的 App 使用搜索功能。如果你的 App 对 UI 的要求不是很严格,那么可以选用默认的搜索栏样式,如果需要自定义的样式,那也可以搞定,反正已经知道该怎么做了。是时候结束了,希望这篇教程能在需要的时候帮到你。 完整的 Xcode 代码可以在这里下载。
  10. 10. 自定义搜索控制器
  11. 11. 给自定义搜索控制器添加搜索操作把搜索控制器设置为搜索栏的代理,这就意味着,和搜索相关的方法实现都会放在CustomSearchController类中。然后,自定义一个协议,让ViewController类遵循并实现代理方法。这样就在ViewController中处理搜索结果了,就像上一个部分中,使用系统默认的搜索逻辑一样。 接下来,按照上面逻辑进行实现。首先, 打开CustomSearchController.swift文件,找到configureSearchBar()方法,添加以下代码: func configureSearchBar(frame: CGRect, font: UIFont, textColor: UIColor, bgColor: UIColor) { ... customSearchBar.delegate = self} 将CustomSearchController设置为搜索栏的代理,先在CustomSearchController顶部遵循UISearchBarDelegate, class CustomSearchController: UISearchController, UISearchBarDelegate 接着,创建自定义协议,并添加几个代理方法(下面这段代码要添加在CustomSearchController类之前): protocol CustomSearchControllerDelegate { func didStartSearching() func didTapOnSearchButton() func didTapOnCancelButton() func didChangeSearchText(searchText: String)} 代理方法的功能就不多做解释了,完全的见名知意。在CustomSearchController类中,定义delegate属性: var customDelegate: CustomSearchControllerDelegate! 这时,我们就可以添加搜索栏协议剩下的代理方法了,然后这些方法中,调用响应的CustomSearchControllerDelegate代理方法。 首先,当搜索框开始编辑时: func searchBarTextDidBeginEditing(searchBar: UISearchBar) { customDelegate.didStartSearching()} 当键盘上的搜索按钮点击时: func searchBarSearchButtonClicked(searchBar: UISearchBar) { ? customSearchBar.resignFirstResponder() ? customDelegate.didTapOnSearchButton()} 当搜索栏上的取消按钮点击时: func searchBarSearchButtonClicked(searchBar: UISearchBar) { customSearchBar.resignFirstResponder() customDelegate.didTapOnSearchButton()} 最后,当搜索关键字变化时: func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { customDelegate.didChangeSearchText(searchText)} 以上的这些方法,会在搜索开始、结束和关键字改变时告诉ViewController。现在,打开ViewController.swift文件,在configureCustomSearchController()方法中添加下面这行代码: func configureCustomSearchController() { ... customSearchController.customDelegate = self} 别忘了在类头部遵循CustomSearchControllerDelegate协议,这样实现的代理方法才有效: class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating, UISearchBarDelegate, CustomSearchControllerDelegate 最后,让我们来实现CustomSearchControllerDelegate的代理方法,这和普通的搜索控制器的处理差不多,代码如下: 开始搜索时: func didStartSearching() { shouldShowSearchResults = true tblSearchResults.reloadData()} 点击搜索按钮时: func didTapOnSearchButton() { if !shouldShowSearchResults { shouldShowSearchResults = true tblSearchResults.reloadData() }} 点击取消按钮时: func didTapOnCancelButton() { shouldShowSearchResults = false tblSearchResults.reloadData()} 搜索关键字改变时: func didChangeSearchText(searchText: String) { // Filter the data array and get only those countries that match the search text. filteredArray = dataArray.filter({ (country) -> Bool in let countryText: NSString = country return (countryText.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch).location) != NSNotFound }) // Reload the tableview. tblSearchResults.reloadData()} 到此,整个 App 的功能就已经完全 ok 了,并且使用的是自定义的搜索控制器。运行看下效果: 总结再开速浏览一遍上面的实现流程,不管是使用 iOS 8 提供的UISearchController,还是自定义搜索栏和搜索控制器,都还比较容易理解。得到的结论都是一样的:我们可以很完美的在自己的 App 使用搜索功能。如果你的 App 对 UI 的要求不是很严格,那么可以选用默认的搜索栏样式,如果需要自定义的样式,那也可以搞定,反正已经知道该怎么做了。是时候结束了,希望这篇教程能在需要的时候帮到你。 完整的 Xcode 代码可以在这里下载。
  12. 12. 给自定义搜索控制器添加搜索操作
  13. 13. 总结

用 UISearchController 自定义 Search Bar

本文翻译自 Gabriel Theodoropoulos 发布在 AppCoda 上的文章 文章版权由 AppCoda 授给 SwiftGG翻译组

文章太过久远,可能已不具备参考价值。没删,只因为留作纪念。

本文翻译自 Gabriel Theodoropoulos 发布在 AppCoda 上的文章

文章版权由AppCoda授给SwiftGG翻译组

很多 App 应用都需要在 UITableView 中显示数据的搜索结果。毋庸置疑,很多开发者都遇到过这种情况,通常的做法都是直接使用 iOS SDK 中自带的控件。iOS8以前,苹果提供了一个用于呈现搜索结果的控件 UISearchDisplayController。这个控制器,结合 UISearchBar,能更方便的在应用中添加搜索特性。然而,这些都已成为历史。

iOS8 到来以后,这种实现方式发生了改变。首先,UISearchDisplayController 被弃用,即使他可以在 IB 界面中使用。而现在,出现了一个新的控制器 UISearchController。尽管有这样的变化,但它却没有在 IB 中显示。相反,他需要使用程序来进行初始化并配置,其实这很简单,接下来的文章中将会介绍到。

除了上面提到的内容,UITableView的数据源还有一点比较有趣。iOS SDK给搜索栏提供了预定义外观,并且搜索栏能适合绝大多数的界面样式。然而,当UI界面高度自定义时,自带的搜索栏样式可能就不太能符合整个App的界面风格了。这种情况下,为避免搜索栏特别扎眼,就必须要自定义了。

所以,上面说了那么多,是时候开始教程的正文了。我不得不说,这篇教程我主要有两个目的:第一个目的是演示在iOS8中,如何使用默认搜索栏风格的UISearchController来搜索、过滤数据。即使在IB中没有UISearchController,我们也能使用很简单的代码来对它进行配置。第二个目的是展示如何重写搜索栏的样式,从来使它更加契合App设计风格。正如你接下来所看到的,我们需要继承UISearchControllerUISearchBar类,但需要添加的代码却很简单。我们将会使用非常普通的方法来对子类进行实现,这样就可以在你的项目中重用了。

到此,介绍部分就要结束了,接下来先看看Demo的细节,然后实现它。

Demo App Overview

下面的两张图是Demo的最终效果:

第一张图,是一个UISearchController的默认搜索栏。第二张图,是自定义的搜索栏。接下来将会介绍到两种搜索栏的实现,但在此之前,还需要介绍下这个项目。

和往常一样,可以先下载一个初始项目。下载下来后,使用Xcode打开,可以先看一下工程目录。介于最终效果比较简单,所以文件并不是太多。在StroryBoard中有一个tableView,它将用于展示数据、搜索栏和搜索结果。除此之外,在ViewController类中有少量的tableView delegatedataSource方法的实现。

在这个案例中,我们的数据源是全世界所有的国家名称。在工程中,可以找到一个名为countries.txt的文件,我们需要做的就是将这个文件加载到程序中,存入数组并使用。国家列表文件来源于这个网站

接下来的所有操作,都可以分为以下三个步骤:

  1. 首先,我们需要加载数据,这样可以在之后方面调用;
  2. 我们将使用UISearchController来实现默认的搜索功能;
  3. 我们将继承并自定义search controller与search bar,从而改变搜索栏的默认界面。

接下来,我们一起来细化每一个步骤吧。

加载并显示样本数据

我们将从加载countries.txt文件并将它放入数组开始,这样tableView便有了数据源。接着,需要将整个国家列表展示出来。当完成这一步以后,我们就可以将注意力放在实现两种搜索方法设计上了。

在加载内容到数组里之前,先要在ViewController类中声明一些属性,接着便可以直接使用这些属性了。在ViewController.swift文件中添加以下三行:

var dataArray = [String]()
var filteredArray = [String]()
var shouldShowSearchResults = false

让我解释一下上面的代码。dataArray数组包含的内容就是要显示在tableView上的国家列表。要记住,这个数组只在没有搜索操作的时候作为dataSource。当开始搜索时,filteredArray将会作为dataSource,里面只包含了符合搜索条件的国家名称。具体由哪个数组来作为tableView的dataSource,将由shouldShowSearchResults这个属性来指定。

现在,让我们把countries.txt文件中的数据加载到dataArray数组中,这样就能在tableView中使用了。在初始项目中,有一个对tableView简单的实现,接下来要继续对它进行完善。需要添加一个新的方法loadListOfCountries()。具体做法是:首先需要将countries.txt文件中的内容加载到一个字符串中,然后按照换行符将字符串拆分为数组。最终得到的数组将作为tableView的原始数据。来看一下代码:

func loadListOfCountries() {
// Specify the path to the countries list file.
let pathToFile = NSBundle.mainBundle().pathForResource("countries", ofType: "txt")

if let path = pathToFile {
// Load the file contents as a string.
let countriesString = String(contentsOfFile: path, encoding: NSUTF8StringEncoding, error: nil)!

// Append the countries from the string to the dataArray array by breaking them using the line change character.
dataArray = countriesString.componentsSeparatedByString("\n")

// Reload the tableview.
tblSearchResults.reloadData()
}
}

然后,在viewDidLoad()方法中调用。

override func viewDidLoad() {
? ...

? loadListOfCountries()
}

我们现在需要更新几个与tableView相关的方法,tableView的显示行数,应该取决于当前使用的是哪一个数组:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
? if shouldShowSearchResults {
? ? ? return filteredArray.count
? }
? else {
? ? ? return dataArray.count
? }
}

接着,指定单元行的内容:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("idCell", forIndexPath: indexPath) as! UITableViewCell

if shouldShowSearchResults {
cell.textLabel?.text = filteredArray[indexPath.row]
}
else {
cell.textLabel?.text = dataArray[indexPath.row]
}

return cell
}

现在可以运行程序就可以看到国家列表显示在了tableView上。目前为止,我们还没有做一些比较新、比较难的操作,接下来让我们来配置搜索控制器并显示默认的搜索栏。

配置UISearchController

为了能使用UISearchController在tableView中显示搜索结果,还有必要添加一个属性。所以,在ViewController类的顶部添加一下代码:

var searchController: UISearchController!

为实现需要的效果,需要添加一个名为configureSearchController()的自定义方法。在这个方法中,要初始化刚才定义的属性并给它配置一些属性。事实上,搜索控制就是有一些特殊属性的ViewController。

其中一个属性就是searchBar,它相当于显示在tableView顶部的搜索栏。还有一些属性会在接下来的方法中进行设置。搜索栏并不会自动显示在tableView上,所以我们需要手动进行设置,接下来也会讲到。 除此之外,搜索控制器和搜索栏还有很多属性可以配置。详细信息可以查看在这里这里

接下来,让我们从一个新方法开始,一步一步的进行配置。首先,初始化searchController

func configureSearchController() {
searchController = UISearchController(searchResultsController: nil)
}

这里有个重要的细节: Demo中用来显示搜索结果的tableView和搜索控制器在同一个视图控制器中。但是在一些情况下,用于展示搜索结果的视图控制器和正在执行搜索的控制器是不同的,并且搜索控制器必须以某种方式知道这个情况。唯一的方式就是通过上面说的初始化方法。当传参是nil时,搜索控制器知道存在另一个视图控制器来处理和显示搜索的结果。任何其他情况下,展示搜索结果的控制器和用于搜索的控制器是不同的。

我们有两种方式来实现在键入搜索词时,将搜索结果显示到tableView上。第一种方式是利用search bar的代理,第二种方式是将ViewController类作为searchResultsUpdater的代理。我们将会在自定义搜索栏的时候使用到第一种方法,所以现在先介绍第二种方法。既然如此,我们需要让ViewController类遵循UIResultsUpdating协议。这个协议是iOS8更新的,具体描述为:基于键入的搜索词,更新搜索结果。这个协议只有一个代理方法需要实现,但我们等会在弄。现在先遵循一下这个协议:

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating

再次回到configureSearchController()方法,添加一句代码,这样就离成功又近了一步:

func configureSearchController() {
...

searchController.searchResultsUpdater = self
}

除此之外,你也可以看看根据需要,设置一些其他的属性。比如,下面这个属性设为true,那么开始输入时,背景就会变暗。

func configureSearchController() {
...

searchController.dimsBackgroundDuringPresentation = true
}

通常情况下,当搜索控制器与显示结果的tableView在同一个视图控制器中时,是不会将背景变暗的,所以,还是吧上面的dimsBackgroundDuringPresentation属性设为false吧。

现在来配置一下搜索栏,给它添加一个placeholder

func configureSearchController() {
...

searchController.searchBar.placeholder = "Search here..."
}

除此之外,再设置一下搜索栏的代理,等下就可以使用代理方法了:

func configureSearchController() {
...

searchController.searchBar.delegate = self
}

然后,这里有一个比较取巧的方式来设置搜索栏的大小:

func configureSearchController() {
...

searchController.searchBar.sizeToFit()
}

想要让搜索栏显示出来,还必须要加上下面这句代码:

func configureSearchController() {
...

tblSearchResults.tableHeaderView = searchController.searchBar
}

到此,整个configureSearchController()方法中的代码如下:

func configureSearchController() {
// Initialize and perform a minimum configuration to the search controller.
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search here..."
searchController.searchBar.delegate = self
searchController.searchBar.sizeToFit()

// Place the search bar view to the tableview headerview.
tblSearchResults.tableHeaderView = searchController.searchBar
}

现在,到ViewController类的顶部,遵循UISearchBarDelegate协议:

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating, UISearchBarDelegate

接着,在viewDidLoad()中调用configureSearchController()方法:

override func viewDidLoad() {
...

configureSearchController()
}

以上就是想要使用UISearchController将搜索栏显示在tableView上的全部代码。现在我们可以处理搜索结果了。现在不要去管Xcode报的错,当我们将缺失的代理方法实现以后,错误就会消失。

处理搜索操作

现在来处理搜索和显示结果操作吧。这一步分为两个部分。首先,当在搜索栏键入关键字或者键盘上的搜索按钮被点击时,能够将搜索结果返回。对于边输入边搜索这个操作,并不强制实现。如果你允许,也可以在输入时,跳过返回结果这个步骤。但是,点击搜索按钮时,显示搜索结果这个是必须要实现的,否则搜索栏就变得毫无意义了。在这两种情况下,filteredArray都将是tableView的dataSource,在没有进行搜索时,filteredArray应该是上一次的搜索结果。

我们已经使用shouldShowSearchResults属性来判断应该用哪一个数组作为dataSource了,但目前为止,还没有代码修改过shouldShowSearchResults的值。那么现在来完成它,它的值取决于是否在进行搜索操作。

根据这个逻辑,我们先来实现UISearchBarDelegate的两个代理方法。正如你所看到的,在这两个方法中,我们对shouldShowSearchResults的值进行了修改,并且刷新了tableView:

 func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
? shouldShowSearchResults = true
? tblSearchResults.reloadData()
}


func searchBarCancelButtonClicked(searchBar: UISearchBar) {
? shouldShowSearchResults = false
? tblSearchResults.reloadData()
}

第一个方法会在搜索开始时,将filteredArray作为dataSource。同理,第二个方法会在取消搜索按钮按下时,将dataArray作为dataSource

接下来,实现另一个代理方法,它将会显示搜索结果,并且会在搜索按钮按下时,取消搜索框的第一响应。注意,下面的这个if条件在不想要边输入边搜索的时候很有用。

func searchBarSearchButtonClicked(searchBar: UISearchBar) {
if !shouldShowSearchResults {
shouldShowSearchResults = true
tblSearchResults.reloadData()
}

searchController.searchBar.resignFirstResponder()
}

正如写在代理方法中的条件判断一样,给shouldShowSearchResults正确的值并且刷新tableView,就能每次都在视图控制器中显示正确的数据了。

上面的每一步都很完美,但还是缺少一个重要的步骤。目前为止,我们都还没有和filteredArray打过交道,所以上面的代码并不能得到我们想要的结果。在上一节中,我们遵循了UISearchResultsUpdating协议,那时我说,这个协议中只有一个方法需要实现。那好,现在我们来看一下这个方法。我们将在这个方法中,根据输入的关键字,来把原始数据过滤一下,把符合条件的数据,放入filteredArray数组:

func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchString = searchController.searchBar.text

// Filter the data array and get only those countries that match the search text.
filteredArray = dataArray.filter({ (country) -> Bool in
let countryText: NSString = country

return (countryText.rangeOfString(searchString, options: NSStringCompareOptions.CaseInsensitiveSearch).location) != NSNotFound
})

// Reload the tableview.
tblSearchResults.reloadData()
}

让我们深入了解下这个方法都干了啥。首先,我们将输入的关键字赋给了一个字符串常量searchString。其实可以不用专门拿中间量来接收的,但是这样更方面。

上面这个方法的核心,就是使用filter方法来过滤dataArray的操作。它的功能就像filter这个名字一样,它根据在闭包中写的过滤条件来过滤原始数据,然后将满足条件的数据添加到filteredArray中。每个国家字符串都作为闭包的参数传入,参数名为country。接着,将country转为了类型是NSString的常量countryText。这个操作是为了保证接下来调用NSStringrangeOfString()方法时不会出现类型错误。rangeOfString()方法能查找关键字(searchString)是否在当前国家字符串中(译者注:如,”斯”是否在”俄罗斯”中),如果能找到,则返回关键字所在的范围(NSRange)。如果关键字不在当前国家字符串中,那么会返回NSNotFound 。因为闭包的返回值是Bool类型,所以我们需要做的就是将rangeOfString()的返回值和NSNotFound 做对比。

在最后,刷新tableView,这样就能显示过滤或的国家列表了。我所提供的只是万千解决方案中的一种,你也可以对上面的代码进行精炼和修改。

现在运行程序来测试下功能。键入一个国家名称,可以看到tableView及时的进行了刷新。测试下搜索和取消按钮,观察App的响应。最后,尝试修改一下之前写的代码,这样更有助于理解。

到此,教程的第一部分就结束了。在接下来这个部分中,你将看到如何重写搜索控制器和搜索栏的样式,如何通过自定义来让他们更加符合App的UI风格。

自定义搜索栏

其实要自定义搜索控制器和搜索栏很简单,你只需要继承UISearchControllerUISearchBar,然后写入想要的自定义方法和逻辑就可以了。其实,这个步骤在每次需要重写 iOS SDK 时都要经历,所以接下来介绍的步骤以前都做过。

我们的自定义操作将从继承UISearchBar开始。接下来,会把这个自定义的搜索栏用到搜索控制器的子类上。最后,要在ViewController类中使用这两个自定义类。

首先,在 Xcode 创建新文件,步骤是File >New>File…,在左侧的菜单中,选择iOS >Source分类的Cocoa Touch Class。接着,将Subclass of内容设置为UISearchBar,类名设置为CustomSearchBar

结束创建,并选中打开这个文件。我们开始写自定义初始化方法。在初始化方法中,定义接下来自定义搜索栏和搜索输入框所需的framefonttext color。在此之前,先定义以下两个属性:

var preferredFont: UIFont!
var preferredTextColor: UIColor!

下面是自定义初始化方法:

init(frame: CGRect, font: UIFont, textColor: UIColor) {
super.init(frame: frame)

self.frame = frame
preferredFont = font
preferredTextColor = textColor

}

正如你所看到的,上面的代码先给搜索栏设置了frame,拿中间变量接收了fonttextColor,这两个属性会在之后使用到。

接下来,用下面一行代码来设置搜索栏的风格:

searchBarStyle = UISearchBarStyle.Prominent

这句话使搜索栏有半透明效果,而搜索输入框不透明。但这还不够,我们需要将搜索栏和输入框都设置为不透明的,所以可能需要设置颜色让配色看起来更协调。因此,先添加以下代码:

translucent = false

添加上面两句代码后,现在初始化方法变成了:

 init(frame: CGRect, font: UIFont, textColor: UIColor) {
? super.init(frame: frame)

? self.frame = frame
? preferredFont = font
? preferredTextColor = textColor

? searchBarStyle = UISearchBarStyle.Prominent
? translucent = false
}

注意:搜索栏并不只由一个控件组成,相反,它实际上它有一个UIView类型的子视图,在这个视图中,有两个相当重要的子视图:一个是搜索输入框(是UITextField的子类),还有一个是搜索输入框的背景视图。为了更清晰的了解它,我们打印一下它的子视图:

println(subviews[0].subviews)

控制台上会显示:

除了刚才的自定义初始方法以外,还需要添加一个初始化方法:

required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

根据刚才的注意事项,我们需要拿到真正的搜索输入框(就是搜索框中的textfield)。先添加下面这个方法,它将会返回输入框在搜索框所以子视图中的下标:

func indexOfSearchFieldInSubviews() -> Int! {
var index: Int!
let searchBarView = subviews[0] as! UIView

for var i=0; i<searchBarView.subviews.count; ++i {
if searchBarView.subviews[i].isKindOfClass(UITextField) {
index = i
break
}
}

return index
}</code></pre>

<p class="">有了上面这个方法,我们就可以通过重写本类中的<strong><span style="color: #008000;" spellcheck="false"><code>drawRect()</code></span></strong>方法来自定义了。在这里,我们有两个非常明确的任务:第一个是获取到输入框并把它改成需要的样子,第二个是给搜索栏画一条自定义的底部线条。</p>

具体需要对输入框进行如下修改:

<ol>
<li>修改一下<span spellcheck="false"><code>frame</code></span>,让它比搜索栏小一点;</li>
<li>设置自定义的字体;</li>
<li>设置字体颜色;</li>
<li>修改背景颜色。这是为了让他更契合搜索栏的主题色,这会在下一部分中介绍到。通过这样的手段,可以让搜索栏和输入框的风格更统一(可以在项目概述的地方看到最终效果)。</li>
</ol>

<p class="md-focus">把这些内容转换成代码:</p>

<pre><code class="swift">override func drawRect(rect: CGRect) {
// 获取搜索栏子视图中搜索输入框的下标
if let index = indexOfSearchFieldInSubviews() {
// 获取搜索输入框
let searchField: UITextField = (subviews[0] as! UIView).subviews[index] as! UITextField

// 设置 frame
searchField.frame = CGRectMake(5.0, 5.0, frame.size.width - 10.0, frame.size.height - 10.0)

// 设置字体和文字颜色
searchField.font = preferredFont
searchField.textColor = preferredTextColor

// 设置背景颜色
searchField.backgroundColor = barTintColor
}

super.drawRect(rect)
}

这里也用到了我们之前实现的自定义方法indexOfSearchFieldInSubviews()

最后,给搜索栏底部画一条线。现在,有两件事需要做:第一就是要根据绘出的线创建一条贝塞尔路径,第二就是要根据贝塞尔路径创建一个CAShapeLayer的实例,并给他设置颜色与线宽。最终,这个layer将作为搜索栏的子layer。实现如下:

 override func drawRect(rect: CGRect) {
...

var startPoint = CGPointMake(0.0, frame.size.height)
var endPoint = CGPointMake(frame.size.width, frame.size.height)
var path = UIBezierPath()
path.moveToPoint(startPoint)
path.addLineToPoint(endPoint)

var shapeLayer = CAShapeLayer()
shapeLayer.path = path.CGPath
shapeLayer.strokeColor = preferredTextColor.CGColor
shapeLayer.lineWidth = 2.5

layer.addSublayer(shapeLayer)

super.drawRect(rect)
}

当然,你也可以根据喜好或需求来修改线的颜色和线宽。在任何情况下,你都能方便的修改这些属性。

现在,自定义的搜索栏就搞定了。因为还缺少搜索控制器,所以暂时还不能看到效果,不过在下一节就会实现了。在这一节中,重要就是如何正确的去自定义搜索栏,让它和你的App更加和谐。

自定义搜索控制器

我们的自定义搜索控制器将会是UISearchController的子类,操作步骤也和上一节差不多。创建文件的步骤都一样,只是要注意Subclass要改为UISearchController,类名是CustomSearchController:

创建完以后,在工程目录里面选中这个类,在开头添加自定义搜索栏的属性:

var customSearchBar: CustomSearchBar!

接下来,依然需要添加自定义初始化方法。这个方法有五个参数:

  1. 用于显示搜索结果的视图控制器;
  2. 搜索栏的frame
  3. 搜索输入框的字体;
  4. 搜索输入框的字体颜色;
  5. 搜索栏的主题色

代码如下:

init(searchResultsController: UIViewController!, searchBarFrame: CGRect, searchBarFont: UIFont, searchBarTextColor: UIColor, searchBarTintColor: UIColor) {
super.init(searchResultsController: searchResultsController)

configureSearchBar(searchBarFrame, font: searchBarFont, textColor: searchBarTextColor, bgColor: searchBarTintColor)
}

这里的configureSearchBar()方法,会在后面进行实现。从方法名可以看出,我们会在这个方法中,对搜索栏进行一些配置。

在实现这个方法之前,还需要添加两个必要的初始化方法:

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}

required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}

现在可以实现配置搜索栏的方法了,方法很简单:

func configureSearchBar(frame: CGRect, font: UIFont, textColor: UIColor, bgColor: UIColor) {
customSearchBar = CustomSearchBar(frame: frame, font: font , textColor: textColor)

customSearchBar.barTintColor = bgColor
customSearchBar.tintColor = textColor
customSearchBar.showsBookmarkButton = false
customSearchBar.showsCancelButton = true
}

上面的第一行代码,利用了搜索栏的自定义初始化方法实例化了一个自定义搜索栏。接下来的事情就简单多了:给自定义搜索栏设置主题色(barTintColor)和它元素的主题色(tintColor)。接着,我们不显示bookmark按钮,而显示取消按钮。当然,这些属性都可以根据自己需求来设置。

现在,自定义搜索控制器就搞定了,即使还没有遵守搜索栏的UISearchBarDelegate协议。这个会在下一个部分中实现。然而,现在已经可以使用这两个自定义类了。

回到ViewController类,在顶部添加以下属性:

var customSearchController: CustomSearchController!

接下来,用一个超级简单额方法来初始化自定义的搜索控制器,并且给它设置framefontcolor,代码如下:

 func configureCustomSearchController() {
? customSearchController = CustomSearchController(searchResultsController: self, searchBarFrame: CGRectMake(0.0, 0.0, tblSearchResults.frame.size.width, 50.0), searchBarFont: UIFont(name: "Futura", size: 16.0)!, searchBarTextColor: UIColor.orangeColor(), searchBarTintColor: UIColor.blackColor())

? customSearchController.customSearchBar.placeholder = "Search in this awesome bar..."
? tblSearchResults.tableHeaderView = customSearchController.customSearchBar
}

通过上面的方法,就可以给自定义搜索栏设置响应的参数。接着,再给搜索框添加placeholder。最后,把搜索栏显示到tableView的header上。

现在,在viewDidLoad()方法中,还要做两件事:调用刚刚实现的configureCustomSearchController(),并注释configureSearchController()方法,防止系统默认搜索栏再显示。

override func viewDidLoad() {
...

// configureSearchController()

configureCustomSearchController()
}

在这个时候,自定义控制器还达不到想要的要求,因为搜索操作还没加上去。这个问题会在下一节中解决。不过现在已经可以看到自定义搜索控制器的效果了。

给自定义搜索控制器添加搜索操作

把搜索控制器设置为搜索栏的代理,这就意味着,和搜索相关的方法实现都会放在CustomSearchController类中。然后,自定义一个协议,让ViewController类遵循并实现代理方法。这样就在ViewController中处理搜索结果了,就像上一个部分中,使用系统默认的搜索逻辑一样。

接下来,按照上面逻辑进行实现。首先, 打开CustomSearchController.swift文件,找到configureSearchBar()方法,添加以下代码:

func configureSearchBar(frame: CGRect, font: UIFont, textColor: UIColor, bgColor: UIColor) {
...

customSearchBar.delegate = self
}

CustomSearchController设置为搜索栏的代理,先在CustomSearchController顶部遵循UISearchBarDelegate

class CustomSearchController: UISearchController, UISearchBarDelegate

接着,创建自定义协议,并添加几个代理方法(下面这段代码要添加在CustomSearchController类之前):

protocol CustomSearchControllerDelegate {
func didStartSearching()

func didTapOnSearchButton()

func didTapOnCancelButton()

func didChangeSearchText(searchText: String)
}

代理方法的功能就不多做解释了,完全的见名知意。在CustomSearchController类中,定义delegate属性:

var customDelegate: CustomSearchControllerDelegate!

这时,我们就可以添加搜索栏协议剩下的代理方法了,然后这些方法中,调用响应的CustomSearchControllerDelegate代理方法。

首先,当搜索框开始编辑时:

func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
customDelegate.didStartSearching()
}

当键盘上的搜索按钮点击时:

 func searchBarSearchButtonClicked(searchBar: UISearchBar) {
? customSearchBar.resignFirstResponder()
? customDelegate.didTapOnSearchButton()
}

当搜索栏上的取消按钮点击时:

func searchBarSearchButtonClicked(searchBar: UISearchBar) {
customSearchBar.resignFirstResponder()
customDelegate.didTapOnSearchButton()
}

最后,当搜索关键字变化时:

func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
customDelegate.didChangeSearchText(searchText)
}

以上的这些方法,会在搜索开始、结束和关键字改变时告诉ViewController。现在,打开ViewController.swift文件,在configureCustomSearchController()方法中添加下面这行代码:

func configureCustomSearchController() {
...

customSearchController.customDelegate = self
}

别忘了在类头部遵循CustomSearchControllerDelegate协议,这样实现的代理方法才有效:

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating, UISearchBarDelegate, CustomSearchControllerDelegate

最后,让我们来实现CustomSearchControllerDelegate的代理方法,这和普通的搜索控制器的处理差不多,代码如下:

开始搜索时:

func didStartSearching() {
shouldShowSearchResults = true
tblSearchResults.reloadData()
}

点击搜索按钮时:

func didTapOnSearchButton() {
if !shouldShowSearchResults {
shouldShowSearchResults = true
tblSearchResults.reloadData()
}
}

点击取消按钮时:

func didTapOnCancelButton() {
shouldShowSearchResults = false
tblSearchResults.reloadData()
}

搜索关键字改变时:

func didChangeSearchText(searchText: String) {
// Filter the data array and get only those countries that match the search text.
filteredArray = dataArray.filter({ (country) -> Bool in
let countryText: NSString = country

return (countryText.rangeOfString(searchText, options: NSStringCompareOptions.CaseInsensitiveSearch).location) != NSNotFound
})

// Reload the tableview.
tblSearchResults.reloadData()
}

到此,整个 App 的功能就已经完全 ok 了,并且使用的是自定义的搜索控制器。运行看下效果:

总结

再开速浏览一遍上面的实现流程,不管是使用 iOS 8 提供的UISearchController,还是自定义搜索栏和搜索控制器,都还比较容易理解。得到的结论都是一样的:我们可以很完美的在自己的 App 使用搜索功能。如果你的 App 对 UI 的要求不是很严格,那么可以选用默认的搜索栏样式,如果需要自定义的样式,那也可以搞定,反正已经知道该怎么做了。是时候结束了,希望这篇教程能在需要的时候帮到你。

完整的 Xcode 代码可以在这里下载。