My SearchController has unintended behaviour of showing up translucent on top of my scrolling content:
Ideally, I want it to scroll up with my content and the navigation bar to collapse.
Any tips on how to achieve this?
I'm creating my SearchController in ViewDidLoad like so:
let searchResultsController = SearchViewController(nibName: "SearchViewController", bundle: nil)
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = searchResultsController
searchController.searchBar.autocapitalizationType = .sentences
searchController.obscuresBackgroundDuringPresentation = true
searchController.searchBar.placeholder = "Search Birds"
searchController.searchBar.delegate = self
navigationItem.searchController = searchController
definesPresentationContext = true
In ViewWillAppear, I set my NavigationController:
self.navigationController!.setNavigationBarHidden(false, animated: true)
I create my Navigation controller in AppDelegate like:
public func applicationDidFinishLaunching(_ application: UIApplication) {
// Instantiate the initial controller
let initialViewController = HomeViewController(nibName: "HomeViewController", bundle: nil)
let navigationController = UINavigationController(rootViewController: initialViewController)
You can try (If you App runs on iOS 11.0 and more)
override func viewDidAppear(_ animated: Bool) {
if #available(iOS 11.0, *) {
navigationItem.hidesSearchBarWhenScrolling = true
}
}
Apple doc:
If this property is true (the default), the searchController’s search
bar will hide as the user scrolls in the top view controller’s scroll
view. If false, the search bar will remain visible and pinned
underneath the navigation bar.
You can try with following:
override func viewWillAppear(_ animated: Bool) {
if #available(iOS 11.0, *) {
navigationItem.hidesSearchBarWhenScrolling = false
}
}
override func viewDidAppear(_ animated: Bool) {
if #available(iOS 11.0, *) {
navigationItem.hidesSearchBarWhenScrolling = true
}
}
navigationItem.hidesSearchBarWhenScrolling = true
Make your controller confirm to UIScrollViewDelegate and override these methods:
extension ViewController: UIScrollViewDelegate{
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.navigationController!.setNavigationBarHidden(true, animated: true)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
self.navigationController!.setNavigationBarHidden(false, animated: true)
}
}
This might require some changes as per the aesthetics you are looking for in the app.
I have navigation bar with search bar (UISearchController)
I have left bar button icon that when clicked shows this search controller by assigning it to navigationItem like so:
if navigationItem.searchController != nil {
navigationItem.searchController = nil
navigationController?.view.setNeedsLayout()
navigationController?.view.layoutIfNeeded()
} else {
navigationItem.searchController = searchController
navigationController?.view.setNeedsLayout()
navigationController?.view.layoutIfNeeded()
searchController.searchBar.becomeFirstResponder()
}
It works but then if on cancel button touch I try to hide search bar then I have view controller dismissed and black screen appears (no view controllers)
extension SearchableMenuViewController : UISearchBarDelegate {
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
guard #available(iOS 11.0, *) else { return }
guard !isAlwaysVisible else { return }
if #available(iOS 13.0, *) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.navigationItem.searchController = nil
self.navigationController?.view.setNeedsLayout()
self.navigationController?.view.layoutIfNeeded()
}
} else {
navigationItem.searchController = nil
navigationController?.view.setNeedsLayout()
navigationController?.view.layoutIfNeeded()
}
}
I have tried to add delay cause not removing this searchcontroller from navigationItem animates it to extended navigation bar with Title + Search Controller and then tapping Search icon properly hides search controller. So the problem is I think removing search controller while it is animating to extended navigation bar
Super lame haxx that will solve your issue temporarily:
func didDismissSearchController(_ searchController: UISearchController) {
if #available(iOS 13, *) {
navigationItem.searchController = nil
self.navigationController?.view.setNeedsLayout()
self.navigationController?.view.layoutSubviews()
let view = UIView()
self.navigationController?.navigationBar.insertSubview(view, at: 1)
view.removeFromSuperview()
}
}
Just for context my keyboard successfully appears so that's not the problem.
I have a searchButton as my rightBarButtonItem, when pressed it modally presents a vc that contains a SearchController. When the SearchController is presented the keyboard is also presented but the keyboard appears a second late, there's like a 1 second delay before it shows itself. Basically the vc appears on the scene and then the keyboard appears afterwards, I cannot get the keyboard to appear at the same time the SearchController is presented. I was on YouTube's and Vimeo's iOS apps and when I pressed their search button the keyboard is presented with the SearchController at the same exact time, there isn't a 1 second delay.
How can I get the keyboard to present itself at the same time the SearchController is presenting itself?
button to modally present SearchController:
#objc func searchButtonTapped() {
let searchVC = SearchController()
let nav = UINavigationController(rootViewController: searchVC)
present(nav, animated: true, completion: nil)
}
SearchController:
I've already tried adding searchController.isActive = true and searchController.searchBar.becomeFirstResponder() in DispatcQeue.main in viewWillAppear and viewDidAppear and it made no difference
class SearchController: UIViewController {
var searchController: UISearchController!
override func viewDidLoad() {
super.viewDidLoad()
searchController = UISearchController(searchResultsController: nil)
searchController.delegate = self
searchController.searchBar.delegate = self
searchController.searchResultsUpdater = self
searchController.searchBar.showsCancelButton = true
searchController.searchBar.placeholder = "Search"
searchController.searchBar.returnKeyType = .search
searchController.dimsBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false
searchController.searchBar.sizeToFit()
searchController.searchBar.tintColor = UIColor.black
definesPresentationContext = true
navigationItem.hidesBackButton = true
navigationItem.titleView = searchController.searchBar
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
searchController.isActive = true
}
// I tried both of these searchContrller delegate methods SEPERATELY but it made no difference, there's still a 1 second delay
func presentSearchController(_ searchController: UISearchController) {
DispatchQueue.main.async {
self.searchController.searchBar.becomeFirstResponder()
}
}
func didPresentSearchController(_ searchController: UISearchController) {
DispatchQueue.main.async {
self.searchController.searchBar.becomeFirstResponder()
}
}
}
Sure there is a delay.. ones this animation is completed, then keyboard appears.
present(nav, animated: true, completion: nil)
Please try this.
It will immediately open the keyboard if you would not preset view controller with an animation but if we present view controller animation it opens keyboard after the present animation is finished.
Thanks.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
DispatchQueue.main.async {
self.searchController.searchBar.becomeFirstResponder()
}
}
I want to hide navigation bar after a tap
navigationController?.hidesBarsOnTap = true
The navigationBar hides properly after a tap
But after adding a searchController (code below)
let searchController = UISearchController(searchResultsController: nil)
navigationItem.searchController = searchController
My view (cyan color) could not extend correctly
And I also tried rotated it. The search bar appears.
Finally found a solution
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.barHideOnTapGestureRecognizer.addTarget(self, action: #selector(barHideAction(_:)))
let searchController = UISearchController(searchResultsController: nil)
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
navigationController?.hidesBarsOnTap = true
}
#objc func barHideAction(_ guesture: UITapGestureRecognizer) {
updateFrame()
}
func updateFrame() {
if let nc = navigationController {
let isHidden = nc.isNavigationBarHidden
searchController.searchBar.superview?.isHidden = isHidden
if isHidden {
self.additionalSafeAreaInsets.top = -64 // fixed by a magic num
}
else {
self.additionalSafeAreaInsets.top = 0
}
}
}
example code
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.