i have a UICollectionViewController where i am showing list of task which is working fine, recently i tried to implement a UISearchBar for my TaskController after implementing that, when i try to launch any new viewcontroller by clicking on row inside my TaskController the newly launched view controller does not have UINavigationBar so i cant move back to my task list again. see following TaskController with task list:
Image
in above screen shot there is a star icon when user click on that, I launch following view controller which have a navigation bar(note: I have click directly without filtering records thats why i can see the navigation bar here.). UIViewController with UINavigationBar
Image
this is what i get when i click on star icon after filtering data with search bar.
navigation bar gone missing here
so i can not go back to task list controller also when i change a tab from below and come back the view controller got destroyed and i get a black screen with tab bar.
following code i have used to implement search bar which have the problem please help me to figure it out.
let taskSearchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
//set taskSearchController
taskSearchController.searchResultsUpdater = self
taskSearchController.dimsBackgroundDuringPresentation = false
navigationItem.searchController = taskSearchController
getTaskList(){
}
}
following method gives the filtered data from tasklist
func updateSearchResults(for searchController: UISearchController) {
guard let searchText = searchController.searchBar.text, !searchText.isEmpty else{
self.taskList = self.originalTaskist
collectionView?.reloadData()
return
}
taskList = originalTaskist.filter({ task -> Bool in
return task.name!.lowercased().contains(searchText.lowercased())
})
collectionView?.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
// This prevents the search bar to make trouble on pushed view controllers
definesPresentationContext = true
//...
}
Put definesPresentationContext = true inside of your View Controller that shows the search bar (the UICollectionViewController in your case.
Unfortunately, the documentation doesn't explain very well why this is working. This blog post explains it a little better.
Related
I'm trying to present a search controller in response to a button, by setting its isActive property, but it doesn't appear.
lazy var searchController: UISeachController = {
let searchController = UISearchController(searchResultsController: nil)
searchController.delegate = self
return searchController
}()
override func viewDidLoad() {
super.viewDidLoad()
definesPresentationContext = true
}
// Called when a button on the navbar is tapped
#obcj private func searchTapped() {
// The doc says you can force the search controller to appear by setting isActive,
// but nothing happens
searchController.isActive = true
// Calling present does show it, but the search bar appears behind the navbar.
// And the delegate methods are still not called
//present(searchController, animated: true)
}
func willPresentSearchController(_ searchController: UISearchController) {
// Not called, even when calling 'present'
}
It seems isActive only works if the search bar is in the view hierarchy.
// This will place the search bar in the title view, allowing you to
// have other buttons on the right of the search bar.
navigationItem.titleView = searchController.searchBar
Or
// This takes up a whole other row under title row.
navigationItem.searchController = searchController
But if you go this route, you don't need to set isActive yourself - the search controller automatically comes up when the user interacts with the search bar. I still don't understand how isActive is supposed to be used, but it does answer the original question.
I am presenting a UISearchController programmatically, without adding it to the navigationItem. The Calendar app does something similar.
Without a presentation context, the search bar appears correctly, but persists after pushing another view controller.
This is expected, so we need to set definesPresentationContext on the list view controller... But that causes the search bar to render incorrectly.
Here's the code for context:
private lazy var searchController: UISearchController! = {
let searchController = UISearchController(searchResultsController: nil)
searchController.obscuresBackgroundDuringPresentation = false
// If this is set to true, the search bar animates correctly, but that's
// not the effect I'm after. See the next video.
searchController.hidesNavigationBarDuringPresentation = false
return searchController
}()
override func viewDidLoad() {
super.viewDidLoad()
definesPresentationContext = true
searchButton.rx.tap.subscribe(onNext: { [unowned self] in
present(searchController, animated: true)
}).disposed(by: disposeBag)
}
Setting hidesNavigationBarDuringPresentation kind of fixes it, but we lose the tab bar, and the whole thing just looks bad.
I tried this solution (Unable to present a UISearchController), but it didn't help.
Any suggestions?
UPDATE: The issue is, more specifically, that the search bar appears behind the translucent navigation bar. Making the nav bar solid ( navigationController?.navigationBar.isTranslucent = false) makes the search bar appear under the nav bar.
I have the same problem not been able to solve this either. It seems like the problem is that either
a) the searchcontroller is presented at the very top of the viewcontroller stack, even above the navigation controller, so that it stays active into the next viewcontroller push. or,
b) the searchcontroller is presented underneath the navigationcontroller so that it remains covered by the navigation bar
One idea: don't embed the viewcontroller which is presenting the searchcontroller in a navigation controller. instead, just create a UIView which looks like a navigation bar a the top. would this be an inappropriate solution?
I haven't found a solution to the original problem, but I found a workaround: intercept navigation events, and manually dismiss the search controller.
override func viewDidLoad() {
...
// This makes the search bar appear behind the nav bar
// definesPresentationContext = true
navigationController?.delegate = self
}
extension JobListViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController,
willShow viewController: UIViewController, animated: Bool) {
// `animated` is false because, for some reason, the dismissal animation doesn't start
// until the transition has completed, when we've already arrived at the new controller
searchController.dismiss(animated: false, completion: nil)
}
}
I found that presenting the search controller over the navigation bar can be achieved by calling present(_:animated:completion:) on the navigation controller itself rather than the navigation controller's child.
So in your view controller you can do
navigationController?.present(searchController, animated: true)
And this will behave like the search button in the Apple's Calendar app.
Update
Regarding dismissing the search controller before pushing a new controller to the navigation stack, you can do this manually depending on how the push is done.
All the bellow will animate dismissing the search controller before the push happens. Note that I disable user interaction until the dismiss animation completes to prevent pushing the same view controller multiple times.
UIStoryboardSegue
Add this override to your view controller:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if navigationController?.presentedViewController == searchController {
view.isUserInteractionEnabled = false
searchController.dismiss(animated: true) {
self.view.isUserInteractionEnabled = true
}
}
}
IBAction (Programmatically)
Just dismiss the search controller before pushing view controllers to the navigation stack:
#IBAction func showSecondTapped(_ sender: UIButton) {
// Dismiss the search controller first.
view.isUserInteractionEnabled = false
searchController.dismiss(animated: true) {
self.view.isUserInteractionEnabled = true
}
// Build and push the detail view controller.
if let secondViewController = storyboard?.instantiateViewController(withIdentifier: "SecondViewController") {
navigationController?.pushViewController(secondViewController, animated: true)
}
}
Handling pop gesture
If the view controller that is presenting the search controller is not the root of your navigation controller, the user might be able to use the interactive pop gesture, which will also keep the search controller presented after the pop. You can handle this by making your view controller the delegate for the search controller, conform to UISearchControllerDelegate and adding the following code:
extension ViewController: UISearchControllerDelegate {
func willPresentSearchController(_ searchController: UISearchController) {
navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}
func willDismissSearchController(_ searchController: UISearchController) {
navigationController?.interactivePopGestureRecognizer?.isEnabled = true
}
}
Below Gif is from my app, the 1st VC includes a search bar to filter the songs, and when press a row to transition to 2nd VC to show selected playing song.
The question here is that when 2nd VC is opened, the search bar is not disappeared immediately, it has like 1 or 2 seconds delay, could see that behavior from below GIF.
/ / / Here is my code, how could I solve this issue? Any hint is appreciate.
The search bar
var resultSearchController = UISearchController()
override func viewDidLoad() {
...
// add search bar
resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.hidesNavigationBarDuringPresentation = false
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
// set search Bar covered color, same with tableView's background color.
controller.searchBar.barTintColor = UIColor(rgb: 0x292f33)
self.tableView.tableHeaderView = controller.searchBar
return controller
})() // closure, learn it later!!
...
}
I set search bar to disabled state when leaving current VC.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
resultSearchController.isActive = false
}
/ / / Update, as matt's comment, I change the code to integrate search bar into navigation bar, and now the search bar is disappear immediately after opening VC2.
remove self.tableView.tableHeaderView = controller.searchBar and integrate the search bar into the nav bar navigationItem.searchController = resultSearchController. Now the behavior is same as Apple's inbox app.
searchController.searchBar.isHidden = false
Hiding the searchController instead of making the active state to false may solve your issue.
Try adding in the above line to your code in viewWillDisappear()
hopefully this helps.
I have a tab bar app where one of the views is a UITableViewController containing static cells as content with 1 section and 1 row.
I want the Large Title to be set to "Always," so I made the selection on the storyboard and the title was large on the simulator. Now when the user taps "Start Chat," the app will segue to the Virtual Assistant View Controller, where the Large Title is set to "Never" on the storyboard. Now the problem is that when the user segues back to the previous view controller with the "Start Chat" table view cell, the title is not large anymore.
It is interesting that when I set the table view to be scrollable, the title becomes large again upon dragging down the table view. I made sure the navigation bar on the Navigation Controller storyboard is checked with the "Prefers Large Titles." I am using Xcode 11, and this was not a problem when using Xcode 10.
I tried creating a custom class for the view with the start chat button and this code did not work in making the title large from a segue back:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationItem.largeTitleDisplayMode = .always
navigationController?.navigationBar.prefersLargeTitles = true
What else could I do? Any help will be greatly appreciated!
I'd use willMove(toParent:) to change the title back before the segue is performed.
override func willMove(toParent parent: UIViewController?) {
navigationController?.navigationItem.largeTitleDisplayMode = .always
navigationController?.navigationBar.prefersLargeTitles = true
}
Set the properties when setting up the UINavigationController, before presenting it. If you already presented the navigation controller, try doing this to force-update the navigation bar:
navigationController?.navigationItem.prompt = ""
navigationController?.navigationItem.prompt = nil
I took this workaround from this question.
In your particular case, it would be better to subclass the navigation controller and set those properties in its viewDidLoad method, so its properties (largeTitleDisplayMode and prefersLargeTitles) are set in a self-contained code.
Im currently designing an app that utilizes a tab bar controller.
On the messages tab (instant messages), I want the tab bar to disappear whenever a user is having/viewing his/her conversation with another person. To do so I used this:self.tabBarController?.tabBar.isHidden = true
It disables the tabBar, but now the issue is that whenever I hit the back button to return to previous views (embedded in a navigation controller), the tab bar is still hidden. On the other views, I've set tabBar.isHidden = false, but that doesn't seem to fix it and now I can't access any of the other tabs.
My question is: How can I hide the tabBar on one view but keep it visible when I return to previous views?
In TabBar firstViewController
override func viewWillAppear(animated: Bool) {
// Enable TabBar
self.tabBarController?.tabBar.hidden = false
}
In SecondViewController (Pushed from firstViewController)
override func viewDidLoad() {
super.viewDidLoad()
// Disable TabBar
self.tabBarController?.tabBar.hidden = true
}