banner



How To Interpret Winnicott’s Squiggle Drawings.

Update annotation: Lorenzo Boaro updated this tutorial to Xcode xi, Swift five and iOS 13. Tom Elliott wrote the original.

Scrolling through massive lists of items tin can be a slow and frustrating process for users. When dealing with big datasets, information technology's vitally important to permit the user search for specific items. UIKit includes UISearchBar, which seamlessly integrates with UINavigationItem via a UISearchController and allows for quick, responsive filtering of information.

In this tutorial, you lot'll build a searchable Processed app based on a standard table view. You'll add table view search capability, dynamic filtering and an optional scope bar, all while taking advantage of UISearchController. In the terminate, y'all'll know how to make your apps much more convenient and how to satisfy your users' urgent demands.

Prepare for some saccharide-coated search results? Read on.

Getting Started

Outset by downloading the starter project using the Download Materials button at the top or lesser of this tutorial. Once information technology'due south downloaded, open CandySearch.xcodeproj in Xcode.

To keep you focused, the starter projection has everything unrelated to searching and filtering already set upwards for y'all.

Open up Main.storyboard and look at the view controllers contained within:

Main storyboard

The view controller on the left is the root navigation controller of the app. Then you have:

  1. MasterViewController: This contains the tabular array view that y'all'll use to display and filter the candies yous're interested in.
  2. DetailViewController: This displays the details of the selected processed along with its image.

Build and run the app and you'll run into an empty list:

Empty table view

Back in Xcode, the file Processed.swift contains a struct to store the data almost each piece of candy y'all'll display. This struct has ii properties:

  • proper name: This property has type Cord and is fairly self-explanatory.
  • category: This is an enum of blazon Candy.Category, which represents the category each processed belongs to. It also conforms to RawRepresentable and then that yous tin catechumen it to and from an associated raw value of type String.

When the user searches for a type of candy in your app, you'll search the proper noun property using the user'south query string. The category will go important nigh the end of this tutorial, when y'all implement the scope bar.

Populating the Table View

Open MasterViewController.swift. Yous'll manage all the dissimilar Processed for your users to search in candies. Speaking of which, information technology's time to create some candy!

Notation: In this tutorial, you only need to create a express number of values to illustrate how the search bar works; in a production app, you lot might accept thousands of these searchable objects. But whether an app has thousands of objects to search or simply a few, the methods you lot use will remain the same. This is scalability at its finest!

To populate candies, add the following code to viewDidLoad() afterwards the call to super.viewDidLoad():

candies = Candy.candies()        

Build and run. Since the sample project has already implemented the table view's information source methods, you'll see that you now take a working table view:

Populated table view

Selecting a row in the table will too display a particular view of the corresponding candy:

Candy detail

And then much candy, so footling time to notice what you want! You demand a UISearchBar.

Introducing UISearchController

If you await at UISearchController's documentation, you lot'll observe it's pretty lazy. Information technology doesn't do whatever of the work of searching at all. The class just provides the standard interface that users take come to await from their iOS apps.

UISearchController communicates with a delegate protocol to let the balance of your app know what the user is doing. Yous have to write all of the bodily functionality for string matching yourself.

Although this may seem scary at first, writing custom search functions gives you tight control over how your specific app returns results. Your users will appreciate searches that are intelligent and fast.

If you've worked with searching table views in iOS in the by, you may be familiar with UISearchDisplayController. Since iOS 8, Apple has deprecated this grade in favor of UISearchController, which simplifies the unabridged search process.

In MasterViewController.swift, add a new property under candies' declaration:

allow searchController = UISearchController(searchResultsController: zilch)        

By initializing UISearchController with a nil value for searchResultsController, you're telling the search controller that you want to utilise the same view you're searching to display the results. If you specify a different view controller here, the search controller will display the results in that view controller instead.

In order for MasterViewController to reply to the search bar, it must implement UISearchResultsUpdating. This protocol defines methods to update search results based on data the user enters into the search bar.

However in MasterViewController.swift, add the post-obit class extension exterior of the main MasterViewController:

extension MasterViewController: UISearchResultsUpdating {   func updateSearchResults(for searchController: UISearchController) {     // TODO   } }        

updateSearchResults(for:) is the one and just method that your class must implement to conform to the UISearchResultsUpdating protocol. You'll fill up in the details shortly.

Setting Up searchController'south Parameters

Next, you need to set upwards a few parameters for your searchController. Nonetheless in MasterViewController.swift, add the following to viewDidLoad(), merely afterward the assignment to candies:

// 1 searchController.searchResultsUpdater = self // 2 searchController.obscuresBackgroundDuringPresentation = simulated // 3 searchController.searchBar.placeholder = "Search Candies" // iv navigationItem.searchController = searchController // 5 definesPresentationContext = true        

Hither'due south a breakdown of what you've just added:

  1. searchResultsUpdater is a property on UISearchController that conforms to the new protocol, UISearchResultsUpdating. With this protocol, UISearchResultsUpdating will inform your class of whatsoever text changes within the UISearchBar.
  2. By default, UISearchController obscures the view controller containing the information you're searching. This is useful if yous're using another view controller for your searchResultsController. In this instance, you lot've fix the current view to show the results, so you lot don't want to obscure your view.
  3. Here, you fix the placeholder to something that's specific to this app.
  4. New for iOS xi, yous add the searchBar to the navigationItem. This is necessary because Interface Architect is not yet compatible with UISearchController.
  5. Finally, by setting definesPresentationContext on your view controller to truthful, you ensure that the search bar doesn't remain on the screen if the user navigates to another view controller while the UISearchController is active.

Filtering With UISearchResultsUpdating

After you fix the search controller, you'll need to do some coding to get it working. Kickoff, add the following property near the elevation of MasterViewController:

var filteredCandies: [Candy] = []        

This belongings will concord the candies that the user searches for.

Adjacent, add the following computed belongings to the principal MasterViewController:

var isSearchBarEmpty: Bool {   return searchController.searchBar.text?.isEmpty ?? truthful }        

isSearchBarEmpty returns true if the text typed in the search bar is empty; otherwise, it returns false.

Still within MasterViewController.swift, add the following method at the end of MasterViewController:

func filterContentForSearchText(_ searchText: String,                                 category: Processed.Category? = nil) {   filteredCandies = candies.filter { (candy: Processed) -> Bool in     return processed.proper name.lowercased().contains(searchText.lowercased())   }      tableView.reloadData() }        

filterContentForSearchText(_:category:) filters candies based on searchText and puts the results in filteredCandies, which yous've merely added. Don't worry about the category parameter for now; you lot'll apply that in a subsequently section of this tutorial.

filter(_:) takes a closure of type (candy: Candy) -> Bool. It and then loops over all the elements of the assortment and calls the closure, passing in the electric current element, for every one of the elements.

You can use this to make up one's mind whether a processed should exist part of the search results that the user receives. To do and so, yous need to return truthful if you want to include the current candy in the filtered array or imitation otherwise.

To make up one's mind this, you use contains(_:) to see if the proper name of the candy contains searchText. But before doing the comparing, you convert both strings to their lowercase equivalents using lowercased().

Note: Almost of the time, users don't carp with the instance of letters when performing a search, and so by merely comparison the lowercase version of what they type with the lowercase version of the name of each candy, you can easily return a case-insensitive match. At present, you can type "Chocolate" or "chocolate" and either will return a matching processed. How useful is that?! :]

Recall UISearchResultsUpdating? Y'all left it unimplemented. Well, you lot've just written a method that you lot want to telephone call when you update the search results. Voilà!

Supervene upon the TODO in updateSearchResults(for:) with the following code:

let searchBar = searchController.searchBar filterContentForSearchText(searchBar.text!)        

Now, whenever the user adds or removes text in the search bar, the UISearchController will inform the MasterViewController grade of the change via a telephone call to updateSearchResults(for:), which in turn calls filterContentForSearchText(_:category:).

Build and run and you lot'll notice that there'southward now a search bar higher up the table. Y'all may demand to ringlet downward to run across it.

Scrolling table view

However, when you enter search text, you yet don't see whatsoever filtered results. What gives?

This is only because you oasis't written the code to allow the table view know when to use the filtered results yet.

Updating the Tabular array View

In the chief MasterViewController class of MasterViewController.swift, add a computed property to make up one's mind if you lot are currently filtering results or not:

var isFiltering: Bool {   return searchController.isActive && !isSearchBarEmpty }        

Next, supersede tableView(_:numberOfRowsInSection:) with the post-obit:

func tableView(_ tableView: UITableView,                numberOfRowsInSection section: Int) -> Int {   if isFiltering {     render filteredCandies.count   }        return candies.count }        

Not much has changed hither. Y'all just cheque whether the user is searching or not, so use either the filtered or the normal candies as the data source for the tabular array.

Adjacent, supervene upon tableView(_:cellForRowAt:) with the post-obit:

func tableView(_ tableView: UITableView,                 cellForRowAt indexPath: IndexPath) -> UITableViewCell {   permit prison cell = tableView.dequeueReusableCell(withIdentifier: "Prison cell", for: indexPath)   let candy: Candy   if isFiltering {     candy = filteredCandies[indexPath.row]   } else {     candy = candies[indexPath.row]   }   jail cell.textLabel?.text = candy.name   cell.detailTextLabel?.text = candy.category.rawValue   return cell }        

Both methods at present utilise isFiltering, which refers to the isActive holding of searchController to determine which array to brandish.

When the user taps the search field of the search bar, isActive is automatically gear up to true. If the search controller is agile and the user has typed something into the search field, the returned information comes from filteredCandies. Otherwise, the data comes from the full list of items.

Think that the search controller automatically handles showing and hiding the results tabular array, and so all your code has to do is provide the correct data (filtered or non-filtered) depending on the state of the controller and whether the user has searched for anything.

Build and run the app. You now have a performance Search Bar that filters the rows of the main table. Huzzah!

Filtering with search bar

Play with the app for a bit to see how yous can search for diverse candies.

But wait, there's still ane more problem. When you select a row from the search results list, you may notice the particular view is from the wrong candy! Time to fix that.

Sending Information to a Item View

When sending information to a detail view controller, yous need to ensure the view controller knows which context the user is working with: The total table list or the search results. Here'due south how you handle that.

Yet in MasterViewController.swift, in prepare(for:sender:), find the post-obit code:

let candy = candies[indexPath.row]        

And replace it with the following:

let candy: Candy if isFiltering {   processed = filteredCandies[indexPath.row] } else {   candy = candies[indexPath.row] }        

Here, you perform the same isFiltering check as before, but now you're providing the proper candy object when segueing to the particular view controller.

Build and run the code at this betoken and see how the app now navigates correctly to the detail view from either the primary table or the search table with ease.

Creating a Scope Bar to Filter Results

To give your users another mode to filter their results, add a scope bar in conjunction with your search bar to filter items by category. The categories you lot'll filter past are the ones you lot assigned to the candy object when you created candies: Chocolate, Hard and Other.

Starting time, you lot take to create a scope bar in MasterViewController. The telescopic bar is a segmented control that narrows a search past only looking in certain scopes. The scope is any you lot define it to be. In this case, it's a candy'southward category, simply scopes could also exist types, ranges or something completely different.

Using the scope bar is every bit piece of cake as implementing one additional consul method.

In MasterViewController.swift, you'll add another extension that conforms to UISearchBarDelegate. So afterward UISearchResultsUpdating, which you added earlier, add together the post-obit:

extension MasterViewController: UISearchBarDelegate {   func searchBar(_ searchBar: UISearchBar,        selectedScopeButtonIndexDidChange selectedScope: Int) {     let category = Processed.Category(rawValue:       searchBar.scopeButtonTitles![selectedScope])     filterContentForSearchText(searchBar.text!, category: category)   } }        

You call this delegate method when the user switches the scope in the scope bar. When that happens, you desire to redo the filtering. Thank you to RawRepresentable conformance, you create a new category instance that retrieves the specified raw value from the selected scope button title. So y'all call filterContentForSearchText(_:category:) with the new category.

Now, modify filterContentForSearchText(_:category:) to take the supplied category into business relationship:

func filterContentForSearchText(_ searchText: Cord,                                 category: Candy.Category? = nil) {   filteredCandies = candies.filter { (candy: Candy) -> Bool in     allow doesCategoryMatch = category == .all || candy.category == category          if isSearchBarEmpty {       return doesCategoryMatch     } else {       render doesCategoryMatch && candy.name.lowercased()         .contains(searchText.lowercased())     }   }      tableView.reloadData() }        

This now checks to see if the category of the candy matches the category that the telescopic bar passes, or whether the scope is gear up to .all. You and then check to meet if there is text in the search bar and filter the candy appropriately. Now, replace isFiltering with the post-obit:

var isFiltering: Bool {   let searchBarScopeIsFiltering =      searchController.searchBar.selectedScopeButtonIndex != 0   render searchController.isActive &&      (!isSearchBarEmpty || searchBarScopeIsFiltering) }        

Here, you update isFiltering to return true when the user selects the scope bar.

You're almost finished, but the scope filtering mechanism doesn't quite work yet. You'll need to modify updateSearchResults(for:) in the showtime class extension yous created to send the current category:

func updateSearchResults(for searchController: UISearchController) {   let searchBar = searchController.searchBar   permit category = Candy.Category(rawValue:     searchBar.scopeButtonTitles![searchBar.selectedScopeButtonIndex])   filterContentForSearchText(searchBar.text!, category: category) }        

The only trouble left is that the user doesn't actually encounter a telescopic bar yet! Inside MasterViewController.swift in viewDidLoad(), add the following code just later the search controller setup:

searchController.searchBar.scopeButtonTitles = Processed.Category.allCases   .map { $0.rawValue } searchController.searchBar.consul = self        

Since Candy.Category conforms to CaseIterable, the compiler can automatically synthesize allCases for any RawRepresentable enumeration, adding the titles that match the categories you assigned to your candy objects.

Now, when you type, the selected telescopic push button will appear in conjunction with the search text.

Testing the Telescopic Bar

Build and run. Try entering some search text and changing the scope.

Scope application

Type in "caramel" with the scope prepare to "All". It shows up in the listing, only when y'all change the scope to Chocolate, "caramel" disappears considering it'due south not a chocolate-type candy. Hurrah!

There's notwithstanding one pocket-sized trouble with the app. You oasis't added a results indicator to tell the user how many results they should expect to see. This is particularly important when no results are returned at all, as information technology's difficult for the user to distinguish between no results returned and a delay in receiving the answer due to a deadening network connection.

Adding a Results Indicator

To set up this, yous're going to add a footer to your view. The user volition run into this footer when they filter the list of candies, and it will tell them how many candies are in the filtered array.

Offset by opening SearchFooter.swift. Here, you take a simple UIView which contains a label as well as a public API that will receive the number of results returned.

Head dorsum to MasterViewController.swift. searchFooter is an IBOutlet for the search footer that the starter project already prepare upwardly for you. You tin can find it in the primary scene in Main.storyboard, at the bottom of the screen.

Inside MasterViewController.swift, add together the following to viewDidLoad(), later the spot where you ready the scope bar:

let notificationCenter = NotificationCenter.default notificationCenter.addObserver(   forName: UIResponder.keyboardWillChangeFrameNotification,   object: nil, queue: .principal) { (notification) in     self.handleKeyboard(notification: notification) } notificationCenter.addObserver(   forName: UIResponder.keyboardWillHideNotification,   object: nil, queue: .main) { (notification) in     self.handleKeyboard(notification: notification)  }        

These ii observers allow you lot to control the results indicator, which will motion up or downward based on the visibility of the system keyboard.

Next, add this method to MasterViewController:

func handleKeyboard(notification: Notification) {   // 1   guard notification.proper noun == UIResponder.keyboardWillChangeFrameNotification else {     searchFooterBottomConstraint.constant = 0     view.layoutIfNeeded()     return   }    guard      let info = notification.userInfo,     let keyboardFrame = info[UIResponder.keyboardFrameEndUserInfoKey] equally? NSValue      else {       return   }    // 2   let keyboardHeight = keyboardFrame.cgRectValue.size.height   UIView.animate(withDuration: 0.1, animations: { () -> Void in     self.searchFooterBottomConstraint.constant = keyboardHeight     self.view.layoutIfNeeded()   }) }        

Here's what's happening with the code y'all but added:

  1. Yous outset cheque if the notification is has anything to do with hiding the keyboard. If not, you lot move the search footer downward and bail out.
  2. If the notification identifies the ending frame rectangle of the keyboard, you movement the search footer simply in a higher place the keyboard itself.

In both cases, yous manage the distance betwixt the search footer and the bottom of the screen through a constraint represented by an IBOutlet called searchFooterBottomConstraint.

Finally, yous need to update the number of results in the search footer when the search input changes. So replace tableView(_:numberOfRowsInSection:) with the following:

func tableView(_ tableView: UITableView,                numberOfRowsInSection section: Int) -> Int {   if isFiltering {     searchFooter.setIsFilteringToShow(filteredItemCount:       filteredCandies.count, of: candies.count)     render filteredCandies.count   }      searchFooter.setNotFiltering()   render candies.count }        

All y'all've done hither is to add in calls to the searchFooter.

Build and run, perform a few searches and picket every bit the footer updates.

Show filter

Where to Become From Here?

Congratulations! You now have a working app that allows you to search directly from the primary table view.

You tin download the concluding project using the Download Materials button at the acme or bottom of this tutorial.

All kinds of apps use table views, and offering a search option is a dainty bear on that improves usability. With UISearchBar and the UISearchController, iOS provides much of the functionality out of the box, so there's no excuse not to use it. The ability to search a large table view is something that today's users expect; without it, they won't exist happy campers.

In the meantime, if you have any questions or comments, please bring together the forum discussion below!

Source: https://www.raywenderlich.com/4363809-uisearchcontroller-tutorial-getting-started

Posted by: austinyouthisesir87.blogspot.com

0 Response to "How To Interpret Winnicott’s Squiggle Drawings."

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel