Back button animation with large title jumps - ios

We have two UIViewController with an UINavigationController.
In the first presented VC inside of viewWillAppear(_ animated: Bool) we do:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationItem.largeTitleDisplayMode = .always
}
....
Inside of the second VC we deactive that behaviour with inside of viewWillAppear(_ animated: Bool):
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = false
}
...
The transition animation to the second VC is smooth while tapping automatic generated back button causes the navigation controller title to create a strange jump to large title instead of the normal grow to large title animation as it does for example in the Messages App.
If i tap the tabbar icon as "back" operation, it does the right transition animation.
Any idea what could cause that issue or how i can fix it?

on the second view controller set the largeTitleDisplayMode to .never
you won't need to set the prefersLargeTitles to false.
To clarify things here, you've to set the largeTitleDisplayMode directly for the navigationItem of the view controller, not the navigation controller!
self.navigationItem.largeTitleDisplayMode = .never // This fixes the issue
self.navigationController?.navigationItem.largeTitleDisplayMode = .never // This doesn't work / Title will stay large

#dave's answer worked for me! Thanks! Here's the code that I used in its entirety:
FirstViewController:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 11.0, *) {
navigationItem.largeTitleDisplayMode = .always
navigationController?.navigationBar.prefersLargeTitles = true
}
}
}
SecondViewController:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 11.0, *) {
navigationItem.largeTitleDisplayMode = .never
}
}
}

One should make force layout of navigation bar right after switching off large title
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.navigationBar.prefersLargeTitles = false
navigationController?.navigationBar.layoutIfNeeded()
}
This cancels out large navigation title immediately.

I had the same transition bug: from large title to small one or backwards. It was not growing/diminishing from one state to the another, but it was staying ugly on the screen for 1 sec, then just jumping from large to small or vice-versa.
The simple solution:
Make sure each view controller has a navigationItem in the Storyboard.
And for each navigationItem, set the corresponding Large Title
value:
Also, you don't need to set anything in viewDidLoad,viewWillAppear, etc. related to largeTitle. Just what I showed above.

For me it was something completely different. In my project we set a custom back button without title on every VC. Standard way to do this for ages was to set an empty BarButtonItem like this:
navigationItem.backBarButtonItem = UIBarButtonItem()
Removing that line fixed the jumping back button when moving from a VC with large title to one without large title. Still having design requirement I found out that since iOS 14 this can be done much more neatly:
navigationItem.backButtonDisplayMode = .minimal
So just replace setting a new BarButtonItem with setting the display mode.

Related

Large title to small title switch in navigation bar is not smooth iOS 13, sticky

I have a UINavigationController with default value of it's navigationBar.prefersLargeTitles = true .
I am switching that to false when I push into a new scene lets call it (DetailsViewController), by changing it into the viewWillDisappear .
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.navigationBar.prefersLargeTitles = false
}
Now in DetailsViewController I am using willMove(to parent:) .
override func willMove(toParent parent: UIViewController?) {
navigationController?.navigationBar.prefersLargeTitles = true
}
To transition back to large titles .
Observe the attached snapshots of how iOS 13 doing it with how iOS 12 is doing it, considering iOS 12 is the correct behavior.
iOS 13 :
iOS 12 :
What you're doing was always wrong. You should set prefersLargeTitles to true once for the navigation bar and never touch it again.
The way to change what each view controller does about large titles as it appears is that that view controller sets its own navigationItem (in its viewDidLoad) to have the desired largeTitleDisplayMode. So if the first v.c. has .always and the second has .never everything will be smooth.
Swift 5, Xcode 13:
UIViewController(1) + UINavigationController:
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.title = "Your title here"
}
UIViewController(2 - "i.e.: detailsViewController"):
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.largeTitleDisplayMode = .never
navigationItem.title = "Your title here"
}
It works like a charm!
I had the same issue and had to place a NavigationItem on the second ViewController's storyboard. My NavigationItem was being created automatically by the segue and its prefersLargeTitle in the viewDidLoad() was not finished creating before the view appeared. Adding a NavigationItem to the storyboard fixed this issue and allowed me to set the prefersLargeTitle in the storyboard's properties menu.
In my case this problem was occurring during a segue to a view controller which is a child of a UITabBarController. Setting largeTitleDisplayMode on the child view controller was not enough to fix this bug.
I have solved the issue by adding a navigation item to the UITabBarController scene and setting largeTitleDisplayMode as .never there.
I solved this problem like this:
override func viewWillDisappear(_ animated: Bool) {
title = ""
}
All ingenious is simple))
final class CustomHosting<Content: View>: UIHostingController<Content> {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.largeTitleDisplayMode = .never
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationItem.largeTitleDisplayMode = .automatic
}
}
I fixed same problem like this :) My problem is presenting SUI Detail Collection view from UIKit Collection, and had some jumping while navigation title changing.

iOS 13 - Buggy Large Title UINavigationBar while pushing

With the iOS 13 update, I've got an annoying bug that I still wasn't able to solve when I have the prefersLargeTitles = true on my UINavigationBar and I perform a push segue.
Plus, even if I'm not 100% sure if it's related to it, my view controller has a collection view embedded.
Anyway the bug/glitch I'm talking about is the following:
Basically the text doesn't animate as I would expect when I'm pushing, and it continues to stay there till the new screen is presented. Any tips? Thanks
I had the same issue. Try to set navigationItem.largeTitleDisplayMode to .always for your first VC and then .never for your second VC with prefersLargeTitles = true in both cases.
The reason is written 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.
Which is causing the animation glitch, and it's not a iOS13 bug only, on iOS12/11 it's already the case it's just the other way around (the animation glitch is happening when dismissing from the secondVC back to the firstVC).
I wrote an article that explain a bit more about this:
https://www.morningswiftui.com/blog/fix-large-title-animation-on-ios13
Try to set largeTitleDisplayMode param inside viewWillAppear() method.
for the base VC set it to .always and in the destination VC set it to .never
BASE VC
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationItem.largeTitleDisplayMode = .always
}
DESTINATION VC
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationItem.largeTitleDisplayMode = .never
}

Transition to Large Title from Small title shows small title for a moment in the destination view controller

I have two view controller in a navigation controller. The root view controller has small title and the next view controller has large title.
When I push the next view controller, I set
self.navigationItem.largeTitleDisplayMode = .always
In the viewDidLoad of next view controller. The problem is, when transitioning it shows the title as small for a moment and then the title becomes big. But I just want to show the big title. I tried setting the title text in ViewDidAppear, it kind of works but the title appears late. I don't want that too.
Say you are doing something like this in vc1
let vc2 = SomeVC()
vc2.navigationItem.largeTitleDisplayMode = .always
self.navigationController.pushViewController(vc2, animated: true)
So try to set the property before pushing the viewcontroller
Had the same issue, I did this :
In VC 1 :
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationItem.largeTitleDisplayMode = .never
}
In VC 2 :
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationItem.largeTitleDisplayMode = .always
navigationController?.navigationBar.prefersLargeTitles = true
}

Delay in changing navigationBar.barTintColor in viewWillAppear [duplicate]

This question already has answers here:
NavigationBar delay updating barTintColor iOS10
(2 answers)
Closed 4 years ago.
I have two view controllers in a navigation controller. I'm trying to change the tint color of the navigation bar for each view controller.
I'm using the following code to accomplish this:
FirstVC:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
navigationController?.navigationBar.barTintColor = .yandasRed
navigationController?.navigationBar.isTranslucent = false
}
SecondVC:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
navigationController?.navigationBar.barTintColor = .white
navigationController?.navigationBar.isTranslucent = true
}
The transition between FirstVC to SecondVC is seemless, and looks great. However when I go back to the FirstVC from the SecondVC, there is a delay in changing the navigation bar tint color. About 1/2 a second.
Why is this? I was under the impression that all code inside viewWillAppear is executed before the view controller is loaded.
Based on the answer here, you can implement:
override func willMove(toParentViewController parent: UIViewController?) {
super.willMove(toParentViewController: parent)
// Setup your navigation bar
}
I believe this will eliminate the delay.

Show search bar in navigation bar without scrolling on iOS 11

I’m attaching a UISearchController to the navigationItem.searchController property of a UITableViewController on iOS 11. This works fine: I can use the nice iOS 11-style search bar.
However, I’d like to make the search bar visible on launch. By default, the user has to scroll up in the table view to see the search bar. Does anyone know how is this is possible?
Left: default situation after launch. Right: search bar made visible (by scrolling up). I’d like to have the search bar visible after launch, as in the right screenshot.
I already found that the search bar can be made visible by setting the property hidesSearchBarWhenScrolling of my navigation item to false. However, this causes the search bar to always be visible — even when scrolling down —, which is not what I want.
The following makes the search bar visible at first, then allows it to hide when scrolling:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 11.0, *) {
navigationItem.hidesSearchBarWhenScrolling = false
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if #available(iOS 11.0, *) {
navigationItem.hidesSearchBarWhenScrolling = true
}
}
Using isActive didn't do what I wanted, it makes the search bar active (showing cancel button, etc.), when all I want is for it to be visible.
You can set the property isActive to true after adding the searchController to the navigationItem.
Just like this:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchController.isActive = true
}
For me it worked after adding following lines in viewDidLoad() method:
navigationController?.navigationBar.prefersLargeTitles = true
navigationController!.navigationBar.sizeToFit()
On iOS 13, #Jordan Wood's answer didn't work.
Instead I did:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.performWithoutAnimation {
searchController.isActive = true
searchController.isActive = false
}
}
For (iOS 13.0, *) and SwiftUI
navigationController?.navigationBar.sizeToFit()
Example:
struct SearchBarModifier: ViewModifier {
let searchBar: SearchBar
func body(content: Content) -> some View {
content
.overlay(
ViewControllerResolver { viewController in
viewController.navigationItem.searchController = self.searchBar.searchController
viewController.navigationController?.navigationBar.sizeToFit()
}
.frame(width: 0, height: 0)
)
}
}

Resources