内容简介:Apple also introducedIf you’ve ever written tedious, fragile code to traverse the search bar’s view hierarchy to get a reference to the search text field, there is more good news. The
UISearchBar
and UISearchController
are staples of iOS app development. But while UISearchBar
has received periodic changes since its introduction in iOS 2, UISearchController
has been pretty static since Apple introduced it in iOS 8. In iOS 13, Apple updated both.
Apple also introduced UISearchToken
, which provides much-needed power to UISearchController
. With very little effort, you can enable your users to perform complex search queries in addition to the text-based searches they’re used to.
If you’ve ever written tedious, fragile code to traverse the search bar’s view hierarchy to get a reference to the search text field, there is more good news. The UISearchTextField
is now exposed as a property, making customization much easier.
In this tutorial, you’ll learn:
UISearchController
Note : If UISearchController
is new to you, read UISearchController Tutorial: Getting Started first.
Getting Started
Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.
Throughout this tutorial, you’ll work on L.I.S.T.E.D., the Large International Sorted Tally of Earth Dwellers. L.I.S.T.E.D. is an organization that keeps track of the total population of the world. They currently have populations for all countries for the years 2018 and 2019.
Anticipating the release of 2020 population data, they want to expand their search capabilities. As an Earth dweller yourself, you’re going to help refactor the app and bring its search functionality to a whole new level for the organization.
Start by opening LISTED.xcodeproj inside the starter folder, then open Main.storyboard .
You’ll see the app is pretty simple. There are only two view controllers, which are wrapped inside a navigation controller.
Build and run. You’ll see this:
Now, search for “new”. You’ll see these search results:
The result displays matches for all years. Tapping 2018 or 2019 in the scope bar will narrow the search. Try it now by tapping 2019 and this is what you’ll see:
Great, basic searching works fine. Now, you’ll make it better!
Using the Search Results Controller
UISearchController
provides two options for displaying results: displaying them in the same view that shows the original data or using a search results controller.
L.I.S.T.E.D. uses a search results controller to display the results in a slightly different format from the main controller. When the user taps into the search bar, the main view controller remains visible. The result view controller displays after the user starts typing the search.
Before iOS 13, you had little control over this behavior, but now you can use the newly-added showsSearchResultsController
in UISearchController
to customize your results. You’ll see how in the next sections.
Displaying Results: You’re in Control
To control when search results display, you need to react to changes in the search bar. For this to work, the main view controller will conform to UISearchResultsUpdating
.
The delegate receives a call to updateSearchResults(for:)
when the search bar becomes the first responder or when text changes. You’ll use this to trigger the display of the results controller.
Open MainViewController.swift and add the following after UISearchBarDelegate
:
// MARK: - extension MainViewController: UISearchResultsUpdating { func updateSearchResults(for searchController: UISearchController) { searchController.showsSearchResultsController = true } }
The search results now display when the search bar becomes the first responder.
Before you can test that, add the following code as the last line in viewDidLoad()
:
searchController.searchResultsUpdater = self
The search results updater is responsible for updating the search results controller. Here, you’re assigning that responsibility to the main controller.
Build and run. Tap the search bar and you’ll see an empty search results controller.
You’re now in full control of showing — and hiding — the search results. As powerful as you must feel now, showing a blank results controller is not a great user experience. You’ll address that soon, but first, you need to know more about search tokens.
Everything You Need to Know About Search Tokens
Search tokens are arguably the most interesting feature Apple added to search in iOS 13. If you use Apple’s Mail or Photos apps on iOS 13, you’ve likely already seen search tokens in action.
The Mail app uses search tokens to create complex searches. Tapping the search bar shows suggestions like “unread messages” and “flagged messages”.
Tokens can represent complex searches like searching by geolocation or simple searches using predetermined text. The key to search tokens is representedObject
in UISearchToken
.
representedObject
is an Any?
and can contain any type of data that’s useful to you.
It’s important to keep in mind that representedObject
is strongly referenced. Any object it holds may stick around for quite some time. Use lightweight data to avoid problems. String
s, Int
s and NSManagedObjectID
s are great candidates.
Creating Tokens
It’s time to address the empty search results view you see when you tap the search bar. This is a great place to show a list of tokens available to your users.
Open ResultsTableViewController.swift . At the top of the class, after countries
, add the following:
var searchTokens: [UISearchToken] = []
Here, you’re creating an array to hold the search tokens. After that line, add the following:
var isFilteringByCountry: Bool { return countries != nil }
This computed Boolean will return true
when users are searching or false
when they’re not. You’ll use it soon.
At the end of the file, below the class, add the following extension:
// MARK: - extension ResultsTableViewController { func makeTokens() { // 1 let continents = Continent.allCases searchTokens = continents.map { (continent) -> UISearchToken in // 2 let globeImage = UIImage(systemName: "globe") let token = UISearchToken(icon: globeImage, text: continent.description) // 3 token.representedObject = Continent(rawValue: continent.description) // 4 return token } } }
This code does the following:
representedObject searchTokens
In viewDidLoad()
, add this as the last line:
makeTokens()
Here, you create the search tokens when the view loads. That’s it! Creating search tokens is that simple. :]
Before you build and run again, you need to update the results controller to display these new tokens. You’ll do that in the next step.
Making a UI for Selecting Tokens
Now, you’ll create a UI for selecting tokens using the Mail app as inspiration. To begin, replace tableView(_:numberOfRowsInSection:)
with the following:
override func tableView( _ tableView: UITableView, numberOfRowsInSection section: Int ) -> Int { return isFilteringByCountry ? (countries?.count ?? 0) : searchTokens.count }
Using isFilteringByCountry
, which you created earlier, you determine whether to use the count of search tokens or count of countries to set the number of rows in the table view. If the user is searching, you send the country count (or zero if countries
is nil). When they aren’t searching, you send the token count.
Next, replace tableView(_:cellForRowAt:)
with the following:
override func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell { // 1 if isFilteringByCountry, let cell = tableView.dequeueReusableCell( withIdentifier: "results", for: indexPath) as? CountryCell { cell.country = countries?[indexPath.row] return cell // 2 } else if let cell = tableView.dequeueReusableCell( withIdentifier: "search", for: indexPath) as? SearchTokenCell { cell.token = searchTokens[indexPath.row] return cell } // 3 return UITableViewCell() }
Here’s what you do with this code:
- You first check to see if the user is searching for a country. If so, you use
CountryCell
. You then assign the country to the cell’scountry
and return the cell. - Otherwise, you use a
SearchTokenCell
. You assign the token to the cell’stoken
and return the cell. - If all else fails, you return a
UITableViewCell
.
Build and run. Tap the search bar but don’t type any text. You’ll see the UI you created to select the search tokens.
This is coming along wonderfully, but there’s a problem: If you tap one of the tokens, nothing happens. Boo! Not to worry, you’ll fix that next.
Adding Tokens to the Search Bar
When you tap one of the token entries in the results view, nothing happens. What should happen is that the token gets added to the search bar. This indicates to users that they’re searching within the continent they specified.
The results controller cannot add the token because it isn’t the owner of the search bar. When the user taps a token, you must notify the main view controller. To do this, you’ll use a delegate protocol.
Start by adding the following code to the top of ResultsTableViewController.swift before the class:
protocol ResultsTableViewDelegate: class { func didSelect(token: UISearchToken) }
At the top of the class, after isFilteringByCountry
, add the following:
weak var delegate: ResultsTableViewDelegate?
You’ve created a simple protocol that you’ll use to inform the delegate when the user taps a token. Now, add the following code after tableView(_:cellForRowAt:)
:
override func tableView( _ tableView: UITableView, didSelectRowAt indexPath: IndexPath ) { guard !isFilteringByCountry else { return } delegate?.didSelect(token: searchTokens[indexPath.row]) }
First, you check if the view is showing tokens or countries. If it’s a country, you ignore the row selection. Otherwise, you inform the delegate which search token the user tapped.
In MainViewController.swift , add the following to the bottom of the file:
// MARK: - extension MainViewController: ResultsTableViewDelegate { func didSelect(token: UISearchToken) { // 1 let searchTextField = searchController.searchBar.searchTextField // 2 searchTextField.insertToken(token, at: searchTextField.tokens.count) // 3 searchFor(searchController.searchBar.text) } }
When notifying the main view controller the user has selected a token, you:
insertToken(_:at:)
In viewDidLoad()
, after the instantiation of resultsTableViewController
, add:
resultsTableViewController.delegate = self
Now, the main view controller will be the results controller’s delegate.
Build and run then tap the search bar. When the results controller appears, tap “Search by Europe”. Then, type “united” and you’ll see this:
Exciting! You’ve added a search token to the search bar. However, it doesn’t seem to be working.
Unless geography has changed since you were in high school, the United States and the United Arab Emirates are not in Europe. So what gives?
The problem is in the search algorithm: You haven’t updated it to take tokens into consideration. Fixing this is your next challenge.
Modifying Your Search to Use Tokens
The current search algorithm uses the search bar’s text and scope to perform a search. You’ll refactor it to use tokens as well.
Before doing that, you need to create a couple of helper properties. In MainViewController.swift , add the following after resultsTableViewController
:
var searchContinents: [String] { // 1 let tokens = searchController.searchBar.searchTextField.tokens // 2 return tokens.compactMap { ($0.representedObject as? Continent)?.description } }
This computed property will:
representedObject
Add this code after the searchContinents
:
var isSearchingByTokens: Bool { return searchController.isActive && searchController.searchBar.searchTextField.tokens.count > 0 }
This property returns true
if the search controller is active and the search bar contains search tokens.
Use these new properties by replacing searchFor(_:)
with:
func searchFor(_ searchText: String?) { // 1 guard searchController.isActive else { return } // 2 guard let searchText = searchText else { resultsTableViewController.countries = nil return } // 3 let selectedYear = selectedScopeYear() let allCountries = countries.values.joined() let filteredCountries = allCountries.filter { (country: Country) -> Bool in // 4 let isMatchingYear = selectedYear == Year.all.description ? true : (country.year.description == selectedYear) // 5 let isMatchingTokens = searchContinents.count == 0 ? true : searchContinents.contains(country.continent.description) // 6 if !searchText.isEmpty { return isMatchingYear && isMatchingTokens && country.name.lowercased().contains(searchText.lowercased()) // 7 } else if isSearchingByTokens { return isMatchingYear && isMatchingTokens } // 8 return false } // 9 resultsTableViewController.countries = filteredCountries.count > 0 ? filteredCountries : nil }
The new search algorithm does the following:
- If the search controller is not currently active, it will terminate.
- If the search text is
nil
, you set the result controller’scountries
tonil
and terminate. - Get the selected year from the scope bar. Next, create an array of all countries and start filtering the array.
- Countries appear once per year. With data for 2018 and 2019, each country is listed twice. Your first step when filtering is to create a Boolean, which is
true
if the selected year is “all”. If not, you return a Boolean based on whether the year for the country is equal to the selected year. - Using
searchContinents
, which you created earlier, create a Boolean if the country’s continent matches any selected tokens. You returntrue
ifsearchContinents
isnil
becausenil
means that you match all continents. - If there’s any search text, you’ll return the country if the year matches, the tokens match and the country’s name contains any of the characters in the search text.
- If you have tokens but no text, you return the country if it matches the year and the token’s continent.
- When neither of those two cases is true, you’ll return
false
. - Assign any filtered countries to the result controller’s
countries
. If there aren’t any, assignnil
.
Build and run. Tap the search bar and, like last time, tap Search by Europe and enter united . You’ll only see entries for the United Kingdom in 2018 and 2019.
While this is amazing, don’t celebrate just yet. Make sure that the changes you made haven’t affected the ability to search without tokens.
Select the Europe token in the search text field and delete it without deleting the word “united”. You’ll see:
Wow! You’ve done some great work. With only a few small changes, you have drastically transformed L.I.S.T.E.D.’s search capabilities.
Hiding the Scope Bar
You still have a few more things to do before you can call this ready for production. The L.I.S.T.E.D. design team has determined that the scope bar shouldn’t be visible when the results controller is showing the token selection UI.
To implement this, go to viewDidLoad()
and add this as the last line:
searchController.automaticallyShowsScopeBar = false
Prior to iOS 13, the scope bar would always display automatically. Now, you can control this behavior by using the new automaticallyShowsScopeBar
.
Note : there is a similar automaticallyShowsCancelButton
. This property allows you to control the search bar’s cancel button visibility. While you don’t need it for this project, you should be aware that it exists.
Next, find selectedScopeYear()
and add the following after it:
func showScopeBar(_ show: Bool) { guard searchController.searchBar.showsScopeBar != show else { return } searchController.searchBar.setShowsScope(show, animated: true) view.setNeedsLayout() }
Here, you check if the search bar’s showsScopeBar
matches show
. If it does, you’ll stop because there’s nothing to do.
If it doesn’t match, you use the new setShowsScope(_:animated:)
to show or hide the scope bar.
Finally, you must call setNeedsLayout()
on the view controller’s view.
You’ll now use this new function to show and hide the scope bar. In UISearchBarDelegate
, find searchBar(_:textDidChange:)
and add the following as the last lines:
let showScope = !searchText.isEmpty showScopeBar(showScope)
If the search text is not empty, the scope bar should be shown.
In searchBarCancelButtonClicked(_:)
, add the following as the last line:
showScopeBar(false)
Now, when the user taps the search bar’s Cancel button, you’ll hide the scope bar.
Finally, in ResultsTableViewDelegate
, add the following to the end of didSelect(token:)
:
showScopeBar(true)
When the user selects a token, you’ll now show the scope bar.
Build and run, then tap the search bar. You’ll no longer see the scope bar.
Tap a token and the scope bar will appear.
Customizing the Search Bar and Text Field
Your final task is to add a theme to the search bar. Before exposing the search text field in iOS 13, customizing the field was fraught with issues. Now, with the text field exposed, you can customize it like any other UITextField
.
Changing Text and Background Color
The design team wants the text field to stand out a bit more, so your first task is to change the color of the text.
In viewDidLoad()
, add this as the last line:
searchController.searchBar.searchTextField.textColor = .rwGreen()
The project has a UIColor
extension. This extension returns a very specific green, used by your favorite tutorial site. Here. you’re setting the search text field’s text color to that gorgeous shade of green.
Next on the list is to change the background color. When the search bar becomes the first responder, it should become a transparent green. When the user cancels the search, it should return to its default color.
To implement this, find updateSearchResults(for:)
in the UISearchResultsUpdating
extension and replace it with:
func updateSearchResults(for searchController: UISearchController) { // 1 if searchController.searchBar.searchTextField.isFirstResponder { searchController.showsSearchResultsController = true // 2 searchController.searchBar .searchTextField.backgroundColor = UIColor.rwGreen().withAlphaComponent(0.1) } else { // 3 searchController.searchBar.searchTextField.backgroundColor = nil } }
Here, you’re:
rwGreen
In UISearchDelegate
, find searchBarCancelButtonClicked(_:)
and add this as the last line:
searchController.searchBar.searchTextField.backgroundColor = nil
If the user cancels the search, you’ll set the text field’s background to the default.
Build and run. Tap the search bar, tap Search by Africa and type “faso”. You’ll see the following:
You’ve now set the search text field’s text and background theme. Things are looking sharp!
But, now the token doesn’t look quite right. There’s always something, isn’t there? Don’t worry, you’ll fix that next.
Changing the Color of Tokens
Tokens have a few theming options. For example, you can set the token’s icon, as you did earlier. You can also change the background color, which is what you’ll do next.
In viewDidLoad()
, add this as the last line:
searchController.searchBar.searchTextField.tokenBackgroundColor = .rwGreen()
Here, you’re setting the default background color for tokens to rwGreen
.
Build and run. Tap search and tap Search by Oceania and you’ll see this:
Look at all that green! It really helps your fantastic new search functionality stand out.
Where to Go From Here?
You can download the completed project using the Download Materials button at the top or bottom of this article.
Congratulations! You now have the latest UISearchController
functionality in your app.
While refactoring the app, you’ve learned how to:
- Control displaying the search results view controller.
- Use search tokens.
- Control the visibility of the scope bar.
- Customize the newly-exposed search text field.
- Customize search tokens.
Whew! That’s a lot. Take a look at these great resources to learn even more about UISearchController
:
- UISearchController Tutorial: Getting Started from raywenderlich.com.
- Modernizing Your UI for iOS 13 from WWDC 2019.
Please share any comments or questions about this article in the forum discussion below!
以上所述就是小编给大家介绍的《What’s New With UISearchController and UISearchBar [FREE]》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Beginning iPhone and iPad Web Apps
Chris Apers、Daniel Paterson / Apress / 2010-12-15 / USD 39.99
It seems that everyone and her sister has developed an iPhone App—everyone except you, the hard-working web professional. And now with the introduction of the iPad, you may even feel farther behind. B......一起来看看 《Beginning iPhone and iPad Web Apps》 这本书的介绍吧!