iOS Swift 4 - How to push UISearchController in a NavigationController - ios

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)

Related

UINavigationController + UISearchController

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
}
}

Smooth transition when changing navigation bar "prefersLargeTitles"

I have a view controller that is pushed onto a navigation stack. The stack has navigationBar.prefersLargeTitles = true, whilst this new view controller has navigationBar.prefersLargeTitles = false. I achieve this using the following code in the view controller that is pushed onto the stack:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.prefersLargeTitles = false
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.navigationBar.prefersLargeTitles = true
}
However, when I return back to the presenting view controller, the change in the navigation bar from navigationBar.prefersLargeTitles = false to navigationBar.prefersLargeTitles = true is a bit glitchy. Is there any way to make this smoother?
Many thanks
Instead of directly changing the preference via the navigation controller, you should change the behavior via the navigation item of the specific view controller you would like to affect.
// Root UIViewController
class ViewControllerA: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .always
}
}
// Pushed UIViewController
class ViewControllerB: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.largeTitleDisplayMode = .never
}
}
You can remove the lines you have in viewWillAppear and viewWillDisappear.

How to add scope buttons in a UISearchController embedded in UINavigationController

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

Swift: SearchBar stays visible on parentViewController

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
}

Search Bar incorrectly overlays in multiple view controller of navigation controller

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
}

Resources