UINavigationController transition issue - swift

This is a Apple simple guide about Table Search with UISearchController, and I have downloaded the demo project and it is working well.
https://developer.apple.com/library/content/samplecode/TableSearch_UISearchController/Introduction/Intro.html
I want to have transparent navigation bar on product detail view (DetailViewController.swift). After changing some behaviors I have transition issue when going back from product detail to device list with swipe gesture(Video link below).
https://www.dropbox.com/s/denqomhhxasxv58/issue.mov?dl=0
For transparent navigation bar I have added this lines on DetailViewController
viewWillAppear method
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// ...
navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
}
In MainTableViewController.swift I have added this code to make navigationBar default style.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.setBackgroundImage(nil, for: .default)
navigationController?.navigationBar.shadowImage = UIImage()
}
Also I made changes on MainTableViewController viewDidLoad method and sets this properties to true.
navigationItem.hidesSearchBarWhenScrolling = true
searchController.dimsBackgroundDuringPresentation = true
So my problem is to fix that issue. Anyone can help me?
Update:
Swipe transition from product detail to device list
Search controller active state when product detail not opened
Search controller active state after product detail opened

Related

Presenting UISearchController programmatically

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

small navigation title is showing for few seconds

I have set large title in viewWillAppear()
self.navigationItem.largeTitleDisplayMode = .always
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationItem.title = "Reports"
But still when I redirect to next VC and come back I can see navigation Title in small size for a while and then I see large title, anybody know why and how to fix it?
If you want the next VC to have largeTitleDisplayMode false, you could try to set it in viewWillDisappear() of the current VC, like this:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.navigationBar.prefersLargeTitles = false
}
You should set this in the viewDidLoad of your view controller, not the viewWillAppear. It's the first part in the view lifecycle and where this work should be done

bottom tabbar disappears and leave black when pushed from UITableViewController to UIViewController

In my use case, I want to hide the bottom tabbar when navigating away from UITabbarController.
I was using
let vc = storyboard?.instantiateViewController(withIdentifier: tableData[indexPath.row]["vcIdentifier"]!)
self.hidesBottomBarWhenPushed = true
self.show(vc!, sender: self)
It sorta works, because the pushed view controller doesn't have tabbar at bottom. However, as soon as I click on navigate, the bottom tabbar of the "sender" view controller vanishes and leaves black area.
Please let me know if you need to have more information about anything. Thanks a lot in advance!
If the pushed view controller doesn't have a tab bar at the bottom, you can add this lifecycle of view controller codes.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tabBarController?.tabBar.isHidden = true
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
tabBarController?.tabBar.isHidden = false
}
you can use segue if you want to hide tabbar while going to the next screen. it will automatically hide it.

Swift Small to Large Nav Bar Title Jumpy Transition

So on my first vc I have set the nav bar to a large title. Then I have a button which goes to a vc with a nav bar with a small title.
When I go back from my second vc to the first, it displays the small title for a bit then jumps down to the large title.
Here is my code in the first vc bc its a tab view controller:
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.navigationBar.prefersLargeTitles = true
}
Here is the code for the second vc in the viewDidLoad():
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.font: UIFont(name: "Avenir-Black", size: 20)!]
self.navigationController?.navigationBar.prefersLargeTitles = false
This is what I mean about the jumpy transition
Thanks
On your second ViewController Try changing the NavBarPreference inside viewWillDisappear function.
Example:
func viewWillDisappear(_ animated: Bool){
self.navigationController?.navigationBar.prefersLargeTitles = true
}
So before going back to the first vc you change the NavBar preference first
Alternative Solution: Using Storyboard
You can click on the desired VC then click on its navBarItem then go to your right to properties and on Large Title Select Never, Always or Automatic from the dropdown list.
Example
Short answer is to not rely on largeTitleDisplayMode = .automatic (the default value) and prefersLargeTitles = true/false but instead to be explicitly setting .always or .never with prefersLargeTitles = true (yes even when using never).
You need to always have prefersLargeTitles to be true because of this from Apple doc:
If the prefersLargeTitles property of the navigation bar is false, this property has no effect and the navigation item’s title is always displayed as a small title.
This is an issue you have on iOS11/12 but on iOS13 it will be surfaced a bit differently.

Search Bar always visible

Hello I want the searchBar always visible, that is what I have:
searchController = UISearchController(searchResultsController: nil)
tableView.tableHeaderView = searchController.searchBar
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
Is there way to accomplish this?
Thanks in advance
What about
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchController.searchBar.becomeFirstResponder()
}
?
Have a look at Apple's example (source code): https://developer.apple.com/library/content/samplecode/TableSearch_UISearchController/Introduction/Intro.html
NB - from iOS 11 onwards you should use UINavigationController instead of the table header view:
if #available(iOS 11.0, *) {
// For iOS 11 and later, we place the search bar in the navigation bar.
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.searchController = searchController
// We want the search bar visible all the time.
navigationItem.hidesSearchBarWhenScrolling = false
} else {
// For iOS 10 and earlier, we place the search bar in the table view's header.
tableView.tableHeaderView = searchController.searchBar
}
In viewDidLoad where you have your UISearchController setup:
navigationItem.hidesSearchBarWhenScrolling = false
You could try to put it in the header for the first section like this:
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return self.searchController.searchBar
}
I had to solve a very similar situation, and this is what I ended up with:
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.delegate = self
searchController.searchBar.sizeToFit()
searchController.searchBar.tintColor = CARBONCOLOR
searchController.hidesNavigationBarDuringPresentation = false
self.definesPresentationContext = true
let search = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Search, target: self, action: "presentSearch")
self.navigationItem.setRightBarButtonItem(search, animated: true)
With this code to present the searchController
func presentSearch()
{
self.navigationController!.presentViewController(searchController, animated: true, completion: nil)
}
This would put a barbutton in the navbar which would present the searchController over the navbar. This is not exactly what I wanted, but turned out to be the simplest way to allow search from any point in the tableview. It also saves a bit of space, which is better. If you don't have a navbar, you can present from self.
The UISearchController class is supposed to be hidden when not in use (i.e. when no search is performed), so I would suggest to either use it in a way it was designed for or go another way.
In your case it might be more appropriate to implement a UISearchBar and e.g. configure it as an item within an (existing) UINavigationBar. That way, the search bar will be always visible.
Do you already have a view controller which holds a navigation bar in your view hierarchy? If so, try to set the search bar as an item and implement display of search results in another way as intended with the UISearchController pattern.
You're setting your searchBar in the tableHeaderView of your UITableView, the default behaviour is that the searchBar scroll with the UITableView, I'm afraid that you cannot change this in the way you did it.
But you can change the way you did it, you can use the UISearchDisplayController and manage the view yourself adn put it above your UITableViewin your hierarchy view, these two tutorials can help you a lot about how to achieve it and understand how it works:
Tableview Search in Swift
UISearchController Tutorial: Getting Started
I hope this help you.