I am trying to add a search bar to my table view so it can filter settings for the user. The only problem is it is initially hidden when the view first loads, giving no indication it is available.
It only shows after you scroll up.
I found a solution that worked back in iOS 11: https://stackoverflow.com/a/46352230/7838349. Unfortunately it seems like this implementation is rather buggy in iOS 13 and seems to no longer work and still seems to show after scrolling.
For reference this is the code I have with the buggy implementation:
class SelectSettingViewController : UITableViewController {
weak var delegate: SelectSettingDelegate?
var settings: [String] = []
var filteredSettings: [String] = []
let searchController = UISearchController(searchResultsController: nil)
var isSearchBarEmpty: Bool {
return searchController.searchBar.text?.isEmpty ?? true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 1
searchController.searchResultsUpdater = self
// 2
searchController.obscuresBackgroundDuringPresentation = false
// 3
searchController.searchBar.placeholder = "Search settings"
// 4
navigationItem.searchController = searchController
// 5
definesPresentationContext = true
navigationItem.hidesSearchBarWhenScrolling = false
if let indexPath = tableView.indexPathForSelectedRow {
tableView.deselectRow(at: indexPath, animated: true)
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationItem.hidesSearchBarWhenScrolling = true
}
Related
I'm making an app with a list of crypto-currencies. There should be an ability to make a search within those 3 filtered lists (BTC, ETH, USD). The only problem i have is with the search bar.
As soon as i press on the "magnifying glass" icon, the search bar presents, but the whole app freezes. I'm not able to press "Cancel", type in the bar, close it and move the UITableView. I don't understand the reason of such behavior.
var searchController : UISearchController!
#IBAction func searchingButton(_ sender: Any) {
searchController = UISearchController(searchResultsController: nil)
searchController.delegate = self
searchController.searchBar.delegate = self
searchController.searchBar.placeholder = "All currency pairs"
searchController.hidesNavigationBarDuringPresentation = false
searchController.searchResultsUpdater = self
definesPresentationContext = true
navigationItem.searchController = self.searchController
navigationItem.hidesSearchBarWhenScrolling = false
searchButton.isHidden = true
present(searchController, animated: true, completion: nil)
}
extension ViewController: UISearchControllerDelegate {
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
navigationItem.searchController = nil
navigationController?.view.setNeedsLayout()
navigationController?.view.layoutIfNeeded()
self.indexChange(self.segmentedControl!)
}
}
Please help me with this problem. If needed, more code will be provided.
Well, it's weird, but cleaning the project and system reboot helped.
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.
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
}
}
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