UISearchController Tutorial: Getting Started

Thanks @douglas always good to know our work is appreciated! :]

Sadly I donā€™t have a solution for the ā€˜scope bar in landscape modeā€™ mess just yet. I spent most of my weekend playing around with exactly that problem. From what I can tell it just doesnā€™t work, which is super weird.

Iā€™ve tried updating Appleā€™s demo code for search to iOS 11 (as theyā€™ve not done it yet) following the WWDC video where they discussed exactly this, as well as this tutorial and a simple super test app I put together with both IB and code built UI and all give me exactly the same problem. Iā€™ve also been through the private headers and the view hierarchy using Reveal and couldnā€™t find any clues there either.

Iā€™m going to open a radar with Apple, see if we get any more luck there. The really weird thing is the API is built in such a way as you have no flexibility or control over this, so I really donā€™t understand what we can be doing wrong :frowning:

If you find a solution do let me know so I can update the tutorial, and if/when I figure it out Iā€™ll come back here, update the tutorial and reply to this message again.

@rcrahul4 this should be fixed now.

@mattharg if you have an array of arrays Iā€™m assuming each element in your outer array is an array of items for a given section.

For example, if you array looks like this:

let data = [
      [1,2,3],
      [4,5,6],
      [7,8,9]
    ]

you can just return the size of the ā€˜dataā€™ array in -[UITableViewDataSource numberOfSectionsInTableView:], by calling data.count.

In -[UITableViewDataSource tableView:numberOfRowsInSection:], you can use data[section].count to get the number of rows.

Then finally in -[UITableViewDataSource tableView:cellForRowAtIndexPath:] use data[indexPath.section][indexPath.row].

If you need more help, check out our UITableView tutorial: https://www.raywenderlich.com/68072/video-tutorial-table-views-multiple-sections

1 Like

@alokc83 if you add a button to the code in the finished project at the end of this tutorial youā€™ll notice this doesnā€™t happen, so I think it must be something in your code perhaps?

Sorry, I donā€™t have a better suggestion for right now. Maybe youā€™re accidentally calling searchController.isActive = false somewhere?

Hi @tomelliott,

I am using a navigation controller, followed by a TabBarController for my TableViewController.

searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search Candies"

self.navigationController?.navigationItem.searchController = searchController
self.navigationController?.navigationBar.topItem?.searchController = searchController
navigationItem.searchController = searchController

definesPresentationContext = true

When I run the app, I do not see any search bar. How do I add it? Neither of the lines work.

Iā€™m trying to adapt this code to my own view controller, but for some reason, it doesnā€™t work at all like in the CandySearch example and I canā€™t figure out why. The most obvious problems/discrepancies I have:

  • The search bar appears on top by default instead of appearing hidden by default
  • The search bar doesnā€™t disappear when scrolling, even though Iā€™m forcing hidesSearchBarWhenScrolling to true
  • When I activate the search bar by tapping it, the content of the table remains where it is instead of sticking to the bottom of the search bar.

Here is the code of my viewDidLoad():

override func viewDidLoad() {
        super.viewDidLoad()

        // Setup the Search Controller
        searchController.searchResultsUpdater = self
        searchController.obscuresBackgroundDuringPresentation = false
        searchController.searchBar.placeholder = NSLocalizedString("Search references by project, customer or city", comment: "")
        if #available(iOS 11.0, *) {
            navigationItem.searchController = searchController
            navigationItem.hidesSearchBarWhenScrolling = true
        } else {
            tableView.tableHeaderView = searchController.searchBar
        }
        definesPresentationContext = true
        
        self.modeSelector.layer.cornerRadius = 5.0

        if let split = splitViewController {
            let controllers = split.viewControllers
            detailViewController = (controllers[controllers.count - 1] as! UINavigationController).topViewController as? ReferenceViewController
        }
        
        self.navigationItem.rightBarButtonItem?.isEnabled = false
    }

And Iā€™m not modifying tableView or searchController in any other place in my code.
I also noted another different in my storyboard: the sample project has top and bottom layout guides, I have a safe area. Donā€™t know if itā€™s relevant (cf. Dropbox - Screenshot 2017-11-23 12.24.10.png - Simplify your life)

Can someone please help me figure out why my code doesnā€™t behave like this sample?

I am new to Swift, and learning Swift 4 through Apple Educationā€™s App Development With Swift iBook in their Everyone Can Code series. I finished the Chapter 4 Guided Project: List, and I am repurposing your code to achieve the end of project stretch goal, add a search field to filter the list. I like the features of your implementation, and was able to integrate everything with one problem that I cannot solve: the project has a custom UITableViewCell with a UIButton that toggles a checkmark; when I tap the button, even though I am not filtering the list, the search footer appears with no label. Once it appears, it does not disappear, even if I push the detail view and segue back. I have tried to isolate the bug, but when I step through the code, the footer appears while stepping through the low level calls that I cannot read. Any clue as to why this is happening? Thanks for any guidance you can offer.

Update: since posting this question, I have continued on to the Chapter 5 Guided Project: Restaurant, and tried implementing a custom footer in the order list scene (to display a dynamically updated total order price), and ran into trouble again leveraging this solution. However, I discovered that NavControllers have a built-in footer, so rather than embed a TableView in a ViewController and add a custom footer as another View, I was able to use a TableViewController embedded in a NavController (which I was doing anyway) and got the footer for free (almost)!

All I had to do was the following to make it work (I did not go back and try this with my previous project, but Iā€™m sure it would work there too):

  1. In Storyboard, in my NavControllerā€™s Attributes, I check Shows Toolbar in the Nav Controller section near the top (you will see the toolbar appear in both the NavController and TableViewController - note I also embed the NavController in a TabBarController, and they do not conflict; the toolbar appears above the tab bar, which is what I wanted!).
  2. In my TableViewController, I let a computed UILabel outside all functions (code below), and in ViewDidLoad, I add the label, define centered constraints for it, set the content with a subfunction (this is important), and unhide the toolbar (code below).
  3. As I add/remove table cells (i.e., order items to/from the order), I call the same subfunction, and the order total price dynamically updates (code below).

This was truly easier than I expected it would be, and works like a charm. I wanted to share for anyone else who might be looking for a simple solution. Enjoy!

CODE
Computed UI Label:
let orderTotalPriceLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.sizeToFit()
label.font = .preferredFont(forTextStyle: UIFontTextStyle.body)
label.textAlignment = .center
return label
}()

viewDidLoad (note I also have an Edit button in the header):
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = editButtonItem
navigationController?.toolbar.addSubview(orderTotalPriceLabel)
orderTotalPriceLabel.centerXAnchor.constraint(equalTo: (navigationController?.toolbar.centerXAnchor)!).isActive = true
orderTotalPriceLabel.centerYAnchor.constraint(equalTo: (navigationController?.toolbar.centerYAnchor)!).isActive = true
updateOrderUI()
navigationController?.toolbar.isHidden = false
}

updateOrderUI (this is the subfunction; note I also have a Submit button in the header, use badgeValue for the number of order items in the order, my OrderItems array includes a price property for each element, and I use property keys [PK] for the strings, but the key names should be self-explanatory):
func updateOrderUI() {
if orderItems.count == 0 {
self.setEditing(false, animated: true)
navigationItem.leftBarButtonItem?.isEnabled = false
orderSubmitButton.isEnabled = false
orderTotalPriceLabel.text = PK.orderTotalPrice_Title + PK.orderTotalPrice_Zero
navigationController?.tabBarItem.badgeValue = nil
} else {
navigationItem.leftBarButtonItem?.isEnabled = true
orderSubmitButton.isEnabled = true
orderTotalPriceLabel.text = PK.orderTotalPrice_Title + String(format: PK_MenuItem.price_StringFormat, orderItems.reduce(0.0, { $0 + $1.price }))
navigationController?.tabBarItem.badgeValue = ā€œ(orderItems.count)ā€
}
}

Howdy @tomelliott

I love this tutorial! Itā€™s been a total life saver!

Iā€™m wondering if there is a way to have the the search bar visible by default, but not the tiles?

I was able to get the search bar and tiles to be visible by default using:
searchController.isActive = true

But this also hides the title of the table. I want instead to have just the search bar visible, as if you had just scrolled up and made it visible, but the title is still visible at the top.

Any suggestions?

Hi, thanks for the wonderful tutorial.
Can you please extend this tutorial and show the best way to do the dynamic search while working with external APIā€™s. (data is not pre available)
for example, Git search API so table view data will update results as the user types the name without them having to press a search button.
It will be too helpful thanks in advance. or if you any links for this please tell.

@dturvey - regarding your question of how to make the search bar visible from the very beginning:

  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    searchController.searchBar.becomeFirstResponder()
  }

Have a look a Appleā€™s example: Apple Developer Documentation

(project file in the Swift subfolder).

Best wishes,

Karsten

Thank you so much! That did the trick!

Excellent Tutorial used a few times. However the Search Bar is still hidden under the navigational bar in the simulator, the only way to make search bar appear is to swipe down on the first item in the table View. This is the case not only in my finished app, but also the Final app I downloaded from the Tutorial.

@tomelliott Can you please help with this when you get a chance? Thank you - much appreciated! :]

the above link does have code to make the Search box always appear.
https://developer.apple.com/library/content/samplecode/TableSearch_UISearchController/Introduction/Intro.html
There is a box to down load the code it is in there and works but it does take up allot of room , but its real easy to use

Ty so much for this tutorial Awesome!!

This tutorial is more than six months old so questions are no longer supported at the moment for it. Thank you!