UINavigationBar with Large Titles - how to find extra height in iOS 11

I think you won't find a way to clearly resolve your problem. What you are asking for is a part of a navigation bar internals which is not exposed to the user. However, I think I can offer you a workaround.

To understand this let's take a look at the navigation bar (with large titles enabled) in the view debugger. As you can see on the below image there is an extra view containing the large title for a particular view controller. This view is not present when you don't support large titles.

Basically, what you want to know is the height of that view.

debugger preview of the navigation bar

This is my proposed solution/workaround

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

What it does

  1. Filters out subviews which start on the top of the navigation bar. They are most probably a background or the navigation area, we don't need them.
  2. Finds the lowest subview in the in the navigation bar.
  3. Maps the resulted subview frame to the frame size.
  4. Returns the height of the found subview or 0 if none was found.

The obvious drawback of this approach is that it's tightly coupled to the structure of the navigation bar which may change in the future. However, it's unlikely that you will get something more than one or another dirty trick.

The result of the execution should look like follows.

print("Extra height: \(navigationController!.navigationBar.lagreTitleHeight)")
Extra height: 52.0

Seems like calling navigationBar.sizeToFit() forces the navigationBar to adjust its size as it shows large title. Therefore you can calculate top safe area easily. The solution that we came up with is next:

self.navigationController?.navigationBar.sizeToFit()
let navigationBarOffset = navigationController?.navigationBar.frame.origin.y ?? 0
let navigationBarHeight = navigationController?.navigationBar.frame.height ?? 0
let offset = -navigationBarOffset - navigationBarHeight
if collectionView.contentOffset.y > offset {
let contentOffset = CGPoint(x: 0, y: offset)
   collectionView.setContentOffset(contentOffset, animated: true)
}

As I understand it, you have your tableView origin under the navigationBar, at y = 0 in your UIViewController's view.

Your tableView should have its top bound fix to the top layout guide, or use the new safe area. That way you won't have to programmatically calculate what's the size of the navigationBar.

If you never used it, take a look at auto-layout.