3

I have enabled large titles for the navigation bar with:

navigationController?.navigationBar.prefersLargeTitles = true

This makes the navigation bar start with an expanded height, and shrink as the user scrolls down.

Now, I want to add a subview inside the navigation bar that resizes, based on how tall the navigation bar is. To do this, I will need to get both the maximum and minimum height of the navigation bar, so I can calculate the fraction of how much it's expanded.

I can get the current height of the navigation bar like this:

guard let height = navigationController?.navigationBar.frame.height else { return }
print("Navigation height: \(height)")

I'm calling this inside scrollViewDidScroll, and as I'm scrolling, it seems that the expanded height is around 96 and the shrunk height is around 44. However, I don't want to hardcode values.

iPhone 12

Expanded (96.33) Shrunk (44)
enter image description here enter image description here

iPhone 8

Expanded (96.5) Shrunk (44)
enter image description here enter image description here

I am also only able to get these values when the user physically scrolls up and down, which won't work in production. And even if I forced the user to scroll, it's still too late, because I need to know both heights in advance so I can insert my resizing subview.

I want to get these values, but without hardcoding or scrolling

Is there any way I can get the height of both the shrunk and expanded navigation bar?

aheze
  • 24,434
  • 8
  • 68
  • 125
  • I'm curious if using Auto Layout would solve the fraction calculation problem for you. Instead of trying to calculate the fraction and applying it to the size of your image, try to give the image constraints to the top and bottom anchors of the navigation bar. Maybe that would help. – alobaili Jan 04 '21 at 12:04
  • @alobaili good idea. But I would also need to change transform values/font size so I will need to know the the exact values, as auto layout will only take care of the frame of the view... – aheze Jan 04 '21 at 17:35

2 Answers2

2

Came across my own question a year later. The other answer didn't work, so I used the view hierarchy.

View Hierarchy with '_UINavigationBarContentView" selected

It seems that the shrunk appearance is embedded in a class called _UINavigationBarContentView. Since this is a private class, I can't directly access it. But, its y origin is 0 and it has a UILabel inside it. That's all I need to know!

extension UINavigationBar {
    func getCompactHeight() -> CGFloat {
        
        /// Loop through the navigation bar's subviews.
        for subview in subviews {
            
            /// Check if the subview is pinned to the top (compact bar) and contains a title label
            if subview.frame.origin.y == 0 && subview.subviews.contains(where: { $0 is UILabel }) {
                return subview.bounds.height
            }
        }
        
        return 0
    }
}

Usage:

override func viewDidLoad() {
    super.viewDidLoad()
    
    self.title = "Navigation"
    
    if
        let navigationBar = navigationController?.navigationBar,
        let window = UIApplication.shared.keyWindow
    {
        navigationBar.prefersLargeTitles = true /// Enable large titles.
        
        let compactHeight = navigationBar.getCompactHeight() // 44 on iPhone 11
        let statusBarHeight = window.safeAreaInsets.top // 44 on iPhone 11
        let navigationBarHeight = compactHeight + statusBarHeight
        print(navigationBarHeight) // Result: 88.0
    }
}

The drawback of this answer is if Apple changes UINavigationBar's internals, it might not work. Good enough for me though.

aheze
  • 24,434
  • 8
  • 68
  • 125
0

Using following extension u can get extra height

extension UINavigationBar
{
    var largeTitleHeight: CGFloat {
        let maxSize = self.subviews
            .filter { $0.frame.origin.y > 0 }
            .max { $0.frame.origin.y < $1.frame.origin.y }
            .map { $0.frame.size }
        return maxSize?.height ?? 0
    }
} 

And I said earlier u can get extended height by following

guard let height = navigationController?.navigationBar.frame.maxY else { return }
print("Navigation height: \(height)")

let window = UIApplication.shared.keyWindow
let topPadding = window?.safeAreaInsets.top

let extendedHeight = height - topPadding

You can get shrunk height by subtracting difference from extended height

guard let difference = navigationController?.navigationBar.lagreTitleHeight else {return}
let shrunkHeight = extendedHeight - difference 
abh
  • 1,189
  • 2
  • 14
  • 30
  • Thanks for the answer, but this only gets the height of the expanded navigation bar... what about the shrunk version? – aheze Jan 01 '21 at 20:59
  • At the beginning, large-title mode, `finalHeight` is 96. That's correct for the expanded navigation bar, but I need to also know how tall the shrunk version is. I edited my question so it's more clear. – aheze Jan 02 '21 at 19:06
  • I think filtering through the subviews is on the right track. But both `extendedHeight` and `shrunkHeight` are 49.0 on iPhone X, and 76.0 on iPhone 8 Plus. – aheze Jan 04 '21 at 18:30
  • And also `UIApplication.shared.keyWindow` is deprecated and `topPadding` needs to be unwrapped – aheze Jan 04 '21 at 18:30
  • 1
    may be this answer can help u @aheze https://stackoverflow.com/questions/46821623/uinavigationbar-with-large-titles-how-to-find-extra-height-in-ios-11 – abh Jan 05 '21 at 10:41