Summary
UINavigationController is showing broken animation when a child UIViewController has a UISearchController embedded into the navigation item's search controller.
This only happens if I set the UISearchController in the navigation item.
In the image below there are 2 examples:
Change Location ViewController - has animation lag when clicking on the Back (Settings) button.
Customize ViewController - works fine.
Flow
UITableViewController > UIViewController with UISearchController embeded inside the navigation item
Findings
I have researched this behavior and found some answers that described a similar behavior but not exactly the same as I have setup.
Trying to implement a solution suggested in the below post by setting the navigation item search controller to nil - did not solve this behavior:
Broken UISearchBar animation embedded in NavigationItem
The code is below. Thanks in advance.
class ChangeLocationViewController: UIViewController {
// MARK: - Outlets
#IBOutlet weak var locationBanner: CustomView!
#IBOutlet weak var locationNameLabel: UILabel!
#IBOutlet weak var locationTimeLabel: UILabel!
#IBOutlet weak var mapView: MKMapView!
let loadingBanner = LoadingBanner()
var resultsViewController: GMSAutocompleteResultsViewController?
var searchController: UISearchController?
let locationManager = LocationManager.shared
override func viewDidLoad() {
super.viewDidLoad()
locationManager.locationManagerDelegate = self
GMSPlacesClient.provideAPIKey(AppSettings.googleAPIKey)
self.definesPresentationContext = true;
resultsViewController = GMSAutocompleteResultsViewController()
resultsViewController?.delegate = self
let autoCompletedFilter = GMSAutocompleteFilter()
autoCompletedFilter.type = .city
resultsViewController?.autocompleteFilter = autoCompletedFilter
searchController = UISearchController(searchResultsController: resultsViewController)
searchController?.searchResultsUpdater = resultsViewController
searchController?.hidesNavigationBarDuringPresentation = false
searchController?.searchBar.placeholder = "Search a place".localized
searchController?.delegate = self
// Setting the search controller [when it is not set, everything works great :)]
navigationItem.searchController = searchController
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Removing the search controller
self.navigationItem.searchController = nil
}
}
Set navigationItem.searchController to nil when the other view controller appears as well.
class ChangeLocationViewController: UIViewController {
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
/* searchController */
searchController.isActive = false
navigationItem.searchController = nil
}
}
class SettingsTableController: UITableViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
/* searchController */
navigationItem.searchController = nil
}
}
Related
In my application, I'm using the searchController that I placed in the navigationItem.
I create a searchController like this:
class FirstVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
private var searchController = UISearchController()
var creator: FirstVC?
#IBOutlet var navigationBarItem: UINavigationItem!
override func viewDidLoad() {
super.viewDidLoad()
searchController = setupSearchController()
navigationBarItem = searchController
}
private func setupSearchController() -> UISearchController {
let controller = UISearchController(searchResultsController: nil)
...
return controller
}
If my tableView is empty, then I hide the searchController, otherwise I display it:
private func hideSearchBar() {
navigationBarItem.searchController = nil
}
private func showSearchBar() {
navigationBarItem.searchController = searchController
}
private func reloadCreator(vc: FirstVC) {
...
if !....isEmpty {
showSearchBar()
} else {
hideSearchBar()
}
But when I present a SecondVC above this one (for this I use navigationController?.showDetailViewController(...) func) and then dismiss it, the searchController doesn't show up. But if I push the VC and come back, it shows up.
I try to use func reloadCreator to reload data in FirstVC when I dismiss my SecondVC, but searchController doesn't appear.
#objc private func dismissSecondVC() {
...
guard let vc = creator else { return }
reloadCreator(vc: vc)
dismiss(animated: true, completion: nil)
}
How can I get the searchController to be displayed?
I'm following the raywenderlich.com tutorial to show a search bar for my table, but I can't see it anywhere (yes, I scrolled up)
Here's my code:
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchControllerDelegate, UISearchResultsUpdating {
#IBOutlet weak var tableView: UITableView!
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
filterContentForSearchText(searchBar.text!)
}
var isSearchBarEmpty: Bool {
return searchController.searchBar.text?.isEmpty ?? true
}
func filterContentForSearchText(_ searchText: String) {
filteredStrings = stockArr.filter { (string: String) -> Bool in
return string.lowercased().contains(searchText.lowercased())
}
tableView.reloadData()
}
var filteredStrings: [String] = []
var searchController : UISearchController!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
let searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search Candies"
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
definesPresentationContext = true
}
}
Please help me, I've tried every StackOverflow solution and nothing works, this code didn't make a difference from the normal tableview I had before.
You're probably using the ViewController without embedding it into a UINavigationController. You've to embed it in UINavigationController to make it show up in the UINavigationBar. So while using ViewController use it as follows, if you're using it programmatically.
UINavigationController(rootViewController: ViewController())
If the ViewController is created in storyboard then Use Editor-> Embed In -> Navigation Controller.
I want to achieve something very simple: a search bar in the navigation bar, with a Cancel button that shows when the bar is activated.
I am using a UISearchController, and currently I have the following:
override func viewDidLoad() {
super.viewDidLoad()
...
let searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.delegate = self
searchController.searchBar.delegate = self
searchController.hidesNavigationBarDuringPresentation = false
searchController.searchBar.sizeToFit()
self.navigationItem.titleView = searchController.searchBar
self.definesPresentationContext = true
}
The search bar shows. However, if I focus on it no Cancel button is shown, and no delegate methods are called whatsoever. That is, no methods in the UISearchResultsUpdating, UISearchControllerDelegate, or UISearchBarDelegate protocols are called, although I've set self to respond to all of them.
If I put the following line into viewDidLoad, the delegate methods start to function:
self.navigationItem.searchController = searchController
However, then I can't put the search bar inside the navbar. It's shown under the navbar instead.
I've searched extensively in SO but nothing seems to work for me. I may be missing something obvious here - how can I make it work? Thanks!
Just define UISearchController as property then everything working fine.
I have tested below in the sample project.
class ViewControllerName: UIViewController{
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
let search = UISearchController(searchResultsController: nil)
self.navigationItem.searchController = search
self.navigationItem.searchController!.searchBar.delegate = self
self.navigationItem.searchController!.searchResultsUpdater = self
self.navigationItem.searchController?.searchBar.showsCancelButton = true
}
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
print("Called")
}
func updateSearchResults(for searchController: UISearchController) {
print("Called")
}
}
Hope it will work for you.
I have tried this code and everything working prop
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, UISearchResultsUpdating {
#IBOutlet weak var tblView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
:
:
let search = UISearchController(searchResultsController: nil)
self.navigationItem.searchController = search
self.navigationItem.searchController!.searchBar.delegate = self
self.navigationItem.searchController!.searchResultsUpdater = self
self.navigationItem.searchController?.searchBar.showsCancelButton = true
:
:
}
Tested below delegates and both are called as expected.
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
print("search end editing.")
}
func updateSearchResults(for searchController: UISearchController) {
print("update search results ... called here")
}
UI:
Call this
self.navigationItem.titleView = searchController.searchBar
from viewWillAppear(), not from viewDidLoad(). And delegates methods works fine.
I programmatically created a search bar in ViewController.
But when I scroll up the screen, the search bar goes up.
How can I freeze the search bar?
class searchbarTable: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating {
#IBOutlet var tableview: UITableView!
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
self.tableview.tableHeaderView = searchController.searchBar
}
}
You can try this and set searchbar in Navigation Bar
self.navigationController?.searchDisplayController = searchController
OR
lazy var searchBar = UISearchBar(frame: CGRectZero)
override func viewDidLoad() {
super.viewDidLoad()
searchBar.placeholder = "Search"
navigationItem.titleView = searchBar
}
OR
var leftNavBarButton = UIBarButtonItem(customView:Yoursearchbar)
self.navigationItem.leftBarButtonItem = leftNavBarButton
OR
searchController.hidesNavigationBarDuringPresentation = false
it hide naviationbar when we present okay
Hope it helps You :)
I am using searchBar and when I select to search , it workes just fine (besides this runtime warning that I can't fix Attempting to load the view of a view controller while it is deallocating... UISearchController)
But if the searchBar isActive and I press "Back" from the NavBar , the parentView is presented but the searchBar from the previous screen is also visible. I tried dismissing the searchBar if the "Back" button is pressed but it is still visible for some time. I am thinking about
self.navigationItem.backButtonItem.enabled = false
while searchBar is active , but I don't like this solution
my code (I also have func updateSearchResultsForSearchController) :
class ViewController: UIViewController ,UISearchResultsUpdating {
var resultSearchController = UISearchController()
override func viewDidLoad() {
super.viewDidLoad()
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()
controller.hidesNavigationBarDuringPresentation = false
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
The runtime warning (not sure if that's the issue here)
Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior (<UISearchController: 0x7ff88130fdb0>)
To remove the warning,Change the following code :
var resultSearchController = UISearchController()
to:
var resultSearchController : UISearchController!
And in viewWillDisappear method dismiss your resultSearchController.
Thanks to UISearchController - Warning Attempting to load the view of a view controller
Try following:
class ViewController: UIViewController ,UISearchResultsUpdating {
var resultSearchController : UISearchController!
override func viewDidLoad() {
super.viewDidLoad()
self.resultSearchController = ({
let controller = UISearchController()
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
controller.hidesNavigationBarDuringPresentation = false
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
}
deinit {
self.searchController.loadViewIfNeeded() // iOS 9
let _ = self.searchController.view // iOS 8
}