So I have the following setup.
InitialViewController(VC) -> NavigationController -> ViewController1(VC1) contains a UISearchController, displays results in a new controller ViewController2(VC2) -> Segue from a cell click on VC2 -> Launch ViewController3 (VC3)
I need to dismiss VC3 and come back to VC1. However, neither VC2 nor VC3 are part of the navigation controller stack. I have pretty much tried all the suggested alternatives but of no avail.
How do I push the UISearchcontroller and the results on to the navigation stack ?
I am instantiating my UISearchController in VC1 like this:
searchController = UISearchController(searchResultsController:
searchResultsController)
searchController!.searchResultsUpdater = searchResultsController
searchController?.delegate = self
searchController!.obscuresBackgroundDuringPresentation = true
searchController!.searchBar.placeholder = "Search stocks"
searchController?.hidesNavigationBarDuringPresentation = false
definesPresentationContext = true
searchController!.searchBar.delegate = searchResultsController
//navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
navigationItem.titleView = searchController?.searchBar
definesPresentationContext = true
And my searchResultsController:
extension WatchListTableViewController: UISearchResultsUpdating, UISearchBarDelegate {
// MARK: - UISearchResultsUpdating Delegate
func updateSearchResults(for searchController: UISearchController) {
// TODO
filterContentForSearchText(searchController.searchBar.text!)
}
}
class WatchListTableViewController: UITableViewController {
var stocks = [String]()
var filteredStocks = [String]()
override func viewDidLoad() {
super.viewDidLoad()
stocks = ["Apple", "Google", "Microsoft", "Tesla"]
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem
}
Solved this in the following way:
Removed the Storyboard Segue from VC2 to VC3.
In VC2 - pushed VC3 on to the navigation stack using:
self.presentingViewController?.navigationController?.pushViewController(vc, animated:true)
I have a problem when trying to add a search bar (UISearchController) on the header of the table of MasterViewController.
As you can see, the table is getting under the search bar.
Any ideas on how can I fix this?
Here is the code that I use for the MasterViewController
private var searchController : UISearchController = ( {
let controller = UISearchController(searchResultsController: nil)
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
return controller
})()
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.translucent = false
self.edgesForExtendedLayout = .None
self.extendedLayoutIncludesOpaqueBars = false
self.automaticallyAdjustsScrollViewInsets = false
searchController.searchResultsUpdater = self
searchController.searchBar.delegate = self
self.tableView.tableHeaderView = searchController.searchBar
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
}
Open your storyboard, select your view controller and in attributes inspector, uncheck Under Top Bars in View Controller group
I have an App that is presenting a MKMapView embedded in a UINavigationController. In the UINavigationController I have put a UISearchController. When the User touch the UISearchController it displays a UITableViewController.
It works well while I'm not adding the Scope button in the UISearchController.
Here the screenshot of the UISearchController in the UINavigationController when I start the App.
Next when I touch the UISearchController, it displays the UITableViewController and scope button.
Here we can already see there's an issue with the scope button because they are not well integrated in the UISearchController (color should be translucent)
Next, when I touch the Cancel button to go back to the Main viewController, the UISearchController is not recovering its original style
it has a dark gray border (that probably comes from the scope button).
Here's how I add the UISearchController in the Main view Controller
func initSearchController() {
let mySearchController = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("SearchControllerId") as! SearchController
self.searchController = UISearchController(searchResultsController: mySearchController)
mySearchController.theSearchController = self.searchController
mySearchController.delegate = self
// Configure the UISearchController
self.searchController.searchResultsUpdater = self
self.searchController.delegate = self
self.searchController.searchBar.delegate = self
self.searchController.searchBar.placeholder = "data.."
self.searchController.hidesNavigationBarDuringPresentation = false
self.searchController.dimsBackgroundDuringPresentation = true
self.navigationItem.titleView = searchController.searchBar
self.definesPresentationContext = true
}
this method is called in the viewDidLoad() of my Main ViewController.
Next, when the SearchController is displayed, I'm adding the scope button with the following code in my TableViewController subclass
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Mandatory to make sure the TableView is displayed when the search field is empty
// when user touch it.
view.hidden = false
var rect = delegate.searchController.searchBar.superview?.frame
rect?.size.height = 88
self.delegate.searchController.searchBar.scopeButtonTitles = ["one", "two", "three"]
self.delegate.searchController.searchBar.showsScopeBar = true
self.delegate.searchController.searchBar.superview?.frame = rect!
}
and the following code is executed when search is closed
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
var rect = delegate.searchController.searchBar.superview?.frame
rect?.size.height = 44
self.delegate.searchController.searchBar.superview?.frame = rect!
self.delegate.searchController.searchBar.showsScopeBar = false
self.delegate.searchController.searchBar.scopeButtonTitles = nil
}
As you can see I have severals issues with this code.
Scope buttons are not displayed correctly and I'm unable to add them with a nice animation
When user exits the search Scope buttons are removed but it impacts the background of the UISearchController
Can you tell me what I'm doing wrong and what should I do to integrate correctly Scope Button in UISearchController?.
I have found examples but only when the UISearchController is not embedded in the UINavigationController.
Thanks for your help!
Sébastien.
You should try using the searchBar.scopeButtonTitles in your instance of UISearchController:
func initSearchController() {
let mySearchController = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("SearchControllerId") as! SearchController
searchController = UISearchController(searchResultsController: mySearchController)
// Set Scope Bar Buttons
searchController.searchBar.scopeButtonTitles = ["one", "two", "three"]
// searchController.searchBar.showsScopeBar = true //if you want it always visible
// Configure the UISearchController
searchController.searchResultsUpdater = self
searchController.searchBar.sizeToFit()
tableView.tableHeaderView = searchController.searchBar
searchController.delegate = self
searchController.searchBar.delegate = self
searchController.searchBar.placeholder = "data.."
searchController.hidesNavigationBarDuringPresentation = false
searchController.dimsBackgroundDuringPresentation = true
definesPresentationContext = true
}
No need to show or hide your scopeButtons in willAppear/didDisapear. This is set by: searchController.searchBar.showsScopeBar = true
I have code that creates a UISearchController' in my UIVIew'sviewDidLoad`.
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.searchBar.delegate = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
controller.hidesNavigationBarDuringPresentation = false //prevent search bar from moving
controller.searchBar.placeholder = "Search for song"
self.myTableView.tableHeaderView = controller.searchBar
return controller
})()
Right after this closure finishes, this warning appears in the console:
Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior (<UISearchController: 0x154d39700>)
I don't get what I am doing wrong. This similar question is not really my situation (At least I don't think so). What is going on?
UISearchController's view has to be removed from its superview before deallocate. (guess it is a bug)
Objective-C...
-(void)dealloc {
[searchController.view removeFromSuperview]; // It works!
}
Swift 3...
deinit {
self.searchController.view.removeFromSuperview()
}
I struggled with this issue for a couple of weeks. ^^
Solved! It was a simple fix. I changed this code
class ViewController: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate {
var resultSearchController = UISearchController()
to this:
class ViewController: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate {
var resultSearchController: UISearchController!
This fixes the problem.
Here is the Swift version that worked for me (similar toJJHs answer):
deinit{
if let superView = resultSearchController.view.superview
{
superView.removeFromSuperview()
}
}
class SampleClass: UITableViewController, UISearchBarDelegate {
private let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
searchController.loadViewIfNeeded() // Add this line before accessing searchController
}
}
Hacking together a few solutions I managed to get mine working by adding lines to viewDidLoad before fully setting up the UISearchController:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = self.editButtonItem()
if #available(iOS 9.0, *) {
self.resultSearchController.loadViewIfNeeded()// iOS 9
} else {
// Fallback on earlier versions
let _ = self.resultSearchController.view // iOS 8
}
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
self.tableView.reloadData()
}
In Swift2 I got the same error message due to an obvious bug:
let alertController = UIAlertController(title: "Oops",
message:"bla.", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Ok",
style: UIAlertActionStyle.Default,handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
Due to a stupid copy error from myself, I had not included the self.presentViewController line. This caused the same error.
In Swift 2.2 version that worked for me
deinit {
self.searchController?.view.removeFromSuperview()
}
I think it's helpful!
It's not a bug. It seems that you have to avoid creating ViewControllers without presenting them. So after SomeViewController() or let variable: SomeViewController you have to call something like this self.presentViewController(yourViewController ...etc). If you don't do that, you will get this warning when this view controller will be dealocated.
Mine is working like this
func initSearchControl(){
searchController = UISearchController(searchResultsController: nil)
if #available(iOS 9.0, *) {
searchController.loadViewIfNeeded()
} else {
let _ = self.searchController.view
}
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
searchController.searchBar.sizeToFit()
}
searchController.loadViewIfNeeded() solves the problem but you need to call it after initializing the searchController
Creating a search controller in viewDidLoad() and setting its search bar as the navigation item's title view doesn't create a strong reference to the search controller, which is why it's deallocated.
So instead of doing this:
override func viewDidLoad() {
super.viewDidLoad()
// Create search controller
let searchController = UISearchController(searchResultsController: nil)
// Add search bar to navigation bar
navigationItem.titleView = searchController.searchBar
// Size search bar
searchController.searchBar.sizeToFit()
}
You should do this:
var searchController: UISearchController!
override func viewDidLoad() {
super.viewDidLoad()
// Create search controller
searchController = UISearchController(searchResultsController: nil)
// Add search bar to navigation bar
navigationItem.titleView = searchController.searchBar
// Size search bar
searchController.searchBar.sizeToFit()
}
I used Derek's answer, but had to change it slightly.
The answer that was provided crashed for me because the call to loadViewIfNeeded() happened before the resultSearchController was defined. (My declaration was
var resultSearchController: UISearchController!
). So I just moved it afterwards and it worked.
If I left out the call entirely, the bug remained, so I'm sure it is an essential part of the answer. I was unable to test it on iOS 8.
It seem the view is lazy loaded, if you allocated the controller and never show it, the view is not loaded. In this case, if the controller is deallocated, you will received this warning. you could show it once, or call it's loadViewIfNeed() method, or use 'let _ = controller.view' to force load the view to avoid this warning.
I'm a bit late to the party, but here's my solution:
var resultSearchController: UISearchController!
override func viewDidLoad()
{
super.viewDidLoad()
self.resultSearchController = ({
let searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.sizeToFit()
return searchController
})()
self.tableView.tableHeaderView = self.resultSearchController.searchBar
self.tableView.reloadData()
}
I hope it works for you.
I have a Search Bar working fine created like this for my UITableView Class shown below,
class customTableViewController: UITableViewController, UISearchResultsUpdating
{....
override func viewDidLoad() {
super.viewDidLoad()
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
controller.hidesBottomBarWhenPushed = false
controller.hidesNavigationBarDuringPresentation = false
controller.searchBar.searchBarStyle = UISearchBarStyle(rawValue: 2)!
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
self.tableView.reloadData()
}
}
It works fine, it's just that when I segue to another view controller, the image of the search bar remains drawn on my screen no matter what view controller I'm in.
When I try "searchBar.active = false", I get nil errors.
What can I do so this searchBar is only drawn on this one tableViewController and nowhere else in my navigation?
Thanks a ton.
I've found a bit of a work around,
In my prepareToSegue, I said:
self.resultSearchController.searchBar.hidden = true
self.resultSearchController.view.endEditing(true)
That's it, and in the destinationViewController I wrote the code below to recognize that its returning to the originalViewController and to redraw the bar.
override func viewWillDisappear(animated: Bool) {
....
resultSearchController.searchBar.hidden = false
}