UISearchController in a UIViewController
Asked Answered
P

4

14

I'm looking to create similar functionality to Apple's maps application in Swift. Is there anyway to integrate a UISearchController in to a regular view (i.e.: not a UITableView). Dropping one in through Storyboard results in a crash after clicking inside the connected searchbar. Or is there some way I can achieve this outcome with a UITableView?

Paleoecology answered 17/10, 2014 at 3:51 Comment(0)
P
3

I added a Search Bar and Search Display Controller in my View Controller in the storyboard. The view controller contains only the search bar and search display controller and does not have it's own TableView. When you add the search bar in your view controller, it sets your view controller as it's delegate automatically.

Now the Search Bar and Search Display Controller has a table view of itself which it uses to display the search results when you click inside the box and start typing. This table view expects your view controller to provide the implementations of the numberOfRowsInSection and cellForRowAtIndexPath functions for it to display the data properly.

When you run your project without these and tap inside the search bar, you will get the following error:-

tableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x7fbf63449660 *** Terminating app due to uncaught exception 'NSInvalidArgumentException'

If you see, the error is at the numberOfRowsInSection method.

Change your view controller definition from

class ViewController: UIViewController

to

class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource

and implement the required methods which are:-

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

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 0
}

I have just added default return values in the above methods.

Now if you filter out your data source in your searchviewdelegate methods and set up your number of rows and cell info in the above two methods properly, it should work.

Hope this helps!

Pollypollyanna answered 17/10, 2014 at 6:9 Comment(5)
Thanks so much Rajeev! That worked like a charm. After a week of banging my head over this I am making progress. I had no idea SearchController had it's own tableview built in - that's pretty neat!Paleoecology
Glad to help and thank you for accepting as answer. I would also appreciate an upvote if you would be so kind!Pollypollyanna
Yet to get 15 reputation :( I will try my best to remember when I do and come back here and upvote you. Thanks so much again :)Paleoecology
Are you talking about UISearchDisplayController? You keep saying "search display controller", but that class is deprecated in iOS 8.Rew
The answer refers to a UISearchDisplayController (deprecated in iOS 8). Though implementing the methods in the answer above are still necessary for a results table's delegate, they aren't the full picture with the new UISearchController in iOS 8. See adauguet's answer for the rest.Willumsen
P
38

If you want to use UISearchController with a non UITableView, here is how I did it.

Since the UISearchController is not (yet!) supported by IB, you do not need to add anything in it, like a UISearchBar.

@interface UIViewControllerSubclass () <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchControllerDelegate, UISearchResultsUpdating>

@property (strong, nonatomic) UISearchController *searchController;

@end

@implementation UIViewControllerSubclass

- (void)viewDidLoad
{        
    [super viewDidLoad];
    // Do any custom init from here...

    // Create a UITableViewController to present search results since the actual view controller is not a subclass of UITableViewController in this case
    UITableViewController *searchResultsController = [[UITableViewController alloc] init];

    // Init UISearchController with the search results controller
    self.searchController = [[UISearchController alloc] initWithSearchResultsController:searchResultsController];

    // Link the search controller
    self.searchController.searchResultsUpdater = self;

    // This is obviously needed because the search bar will be contained in the navigation bar
    self.searchController.hidesNavigationBarDuringPresentation = NO;

    // Required (?) to set place a search bar in a navigation bar
    self.searchController.searchBar.searchBarStyle = UISearchBarStyleMinimal;

    // This is where you set the search bar in the navigation bar, instead of using table view's header ...
    self.navigationItem.titleView = self.searchController.searchBar;

    // To ensure search results controller is presented in the current view controller
    self.definesPresentationContext = YES;

    // Setting delegates and other stuff
    searchResultsController.tableView.dataSource = self;
    searchResultsController.tableView.delegate = self;
    self.searchController.delegate = self;
    self.searchController.dimsBackgroundDuringPresentation = NO;
    self.searchController.searchBar.delegate = self;        
}

@end

I hope it is enough to work :-)

Then of course you need at least to implement UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdater methods.

Enjoy!

Perspiratory answered 5/11, 2014 at 13:13 Comment(3)
Thank you! This helped me finally transition to UISearchController! Also, you don't necessarily create a UITableView to display the items. If you set searchResultsController to nil, it will look for any TableView in the current view.Yahrzeit
Thank you. I'd like to clarify for those having a similar issue to mine, where the UISearchBar would disappear as part of a UIViewController tableView header view (non-UITableViewController). The key line was self.definesPresentationContext = true (Swift). That fixed it.Interlay
You have to subclass the UITableViewController and implement its own behavior.Perspiratory
E
8

Trying to figure out UISearchController myself. Setting it to the titleView is convenient, but on one of my pages, I had to put the searchBar near the top of the UIViewController:

// Add a normal View into the Storyboard.
// Set constraints:
// - height: 44
// - leading and trailing so that it spans the width of the page
// - vertical position can be anywhere based on your requirements, like near the top
@IBOutlet weak var searchContainerView: UIView!

var searchResultsController = UISearchController()

override func viewDidLoad() {
    // TODO: set the searchResultsController to something
    let controller = UISearchController(searchResultsController: nil)
    // have the search bar span the width of the screen
    controller.searchBar.sizeToFit()
    // add search bar to empty View
    searchContainerView.addSubview(controller.searchBar)
    searchResultsController = controller
}

UPDATE:

After implementing UISearchController in a project or two, I found myself gravitating toward @adauguet's approach of embedding the search bar into the Navigation Bar.

Here's the code in Swift. One difference though is that it doesn't set the searchBar delegate, since searchResultsUpdater already listens for text changes.

override func viewDidLoad() {
    super.viewDidLoad()
//        locationManager.delegate = self
//        locationManager.desiredAccuracy = kCLLocationAccuracyBest
//        locationManager.requestWhenInUseAuthorization()
//        locationManager.requestLocation()
    let locationSearchTable = storyboard!.instantiateViewControllerWithIdentifier("LocationSearchTable") as! LocationSearchTable
    resultSearchController = UISearchController(searchResultsController: locationSearchTable)
    resultSearchController?.searchResultsUpdater = locationSearchTable
    let searchBar = resultSearchController!.searchBar
    searchBar.sizeToFit()
    searchBar.placeholder = "Search for places"
    navigationItem.titleView = resultSearchController?.searchBar
    resultSearchController?.hidesNavigationBarDuringPresentation = false
    resultSearchController?.dimsBackgroundDuringPresentation = true
    definesPresentationContext = true
}

Also, I wrote a blog post that creates a project from scratch that uses UISearchController to display map search results. It also does other things that you might want in a map project, like get the user location, drop pins, parse placemarks into a one-line address, and create callout buttons that take you to Apple Maps for driving directions.

http://www.thorntech.com/2016/01/how-to-search-for-location-using-apples-mapkit/

The blog post is quite long, so here's the associated git repo if you just want to skip to the code:

https://github.com/ThornTechPublic/MapKitTutorial

Epigraph answered 28/8, 2015 at 16:18 Comment(3)
Really helpful for me!Thrust
This UI structure was exactly what I was looking for. Thanks for sharing!Fourdimensional
hidesNavigationBarDuringPresentation = v.important!Exertion
P
3

I added a Search Bar and Search Display Controller in my View Controller in the storyboard. The view controller contains only the search bar and search display controller and does not have it's own TableView. When you add the search bar in your view controller, it sets your view controller as it's delegate automatically.

Now the Search Bar and Search Display Controller has a table view of itself which it uses to display the search results when you click inside the box and start typing. This table view expects your view controller to provide the implementations of the numberOfRowsInSection and cellForRowAtIndexPath functions for it to display the data properly.

When you run your project without these and tap inside the search bar, you will get the following error:-

tableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x7fbf63449660 *** Terminating app due to uncaught exception 'NSInvalidArgumentException'

If you see, the error is at the numberOfRowsInSection method.

Change your view controller definition from

class ViewController: UIViewController

to

class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource

and implement the required methods which are:-

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

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 0
}

I have just added default return values in the above methods.

Now if you filter out your data source in your searchviewdelegate methods and set up your number of rows and cell info in the above two methods properly, it should work.

Hope this helps!

Pollypollyanna answered 17/10, 2014 at 6:9 Comment(5)
Thanks so much Rajeev! That worked like a charm. After a week of banging my head over this I am making progress. I had no idea SearchController had it's own tableview built in - that's pretty neat!Paleoecology
Glad to help and thank you for accepting as answer. I would also appreciate an upvote if you would be so kind!Pollypollyanna
Yet to get 15 reputation :( I will try my best to remember when I do and come back here and upvote you. Thanks so much again :)Paleoecology
Are you talking about UISearchDisplayController? You keep saying "search display controller", but that class is deprecated in iOS 8.Rew
The answer refers to a UISearchDisplayController (deprecated in iOS 8). Though implementing the methods in the answer above are still necessary for a results table's delegate, they aren't the full picture with the new UISearchController in iOS 8. See adauguet's answer for the rest.Willumsen
E
0

I had some trouble converting from SearchDisplayController in UIViewController to SearchController in ViewController because SearchController implementation isn't so intuitive. But you can just add in searcher from SearchController itself into any view. You cannot set constraint though because the search bar would move up when you focus/select it (if you know how to set the constraint to seachController.searchbar after adding it to any view, LET ME KNOW!). Below I am sharing a checklist that I found very important/valuable when implementing SearchController in ViewController.

//you can just add searcher to any view. It would automatically move up to show the tableView like magic. But for this reason, you cannot set constraint to the search bar to the placeholder view that you are adding it to.

[self.searchBarPlaceHolderView addSubview:self.searchController.searchBar];

//you need this to prevent search bar to drop down when you focus/select it. You would want to set this to NO if you are adding searchBar to the navigation bar's titleview.

self.searchController.hidesNavigationBarDuringPresentation = YES;

//make sure you set this in your viewController

self.extendedLayoutIncludesOpaqueBars = true;
self.definesPresentationContext = YES;

// you also need to give the search controller a tableViewController that can be displayed. You can also do just self.searchResultsController = [[UITableView alloc] init] for a generic one.

self.searchResultsController = (UITableViewController *)[ [UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"searchResultsTableViewController"];

self.searchResultsController.tableView.dataSource = self;
self.searchResultsController.tableView.delegate = self;
self.searchResultsController.definesPresentationContext = NO;
self.searchController = [[UISearchController alloc] initWithSearchResultsController:self.searchResultsController];
Ez answered 12/11, 2017 at 15:44 Comment(1)
Have you managed to solve your problem? I am currently experiencing some difficulty with thisDevilish

© 2022 - 2024 — McMap. All rights reserved.