Modifying the Y-position of navigation bar isn't possible in iOS 15.1 Beta / iOS 15 simulators

Our logic for modifying the navigation bar isn't working on devices running iOS 15.1 Beta or simulators running iOS 15.0 on Xcode 13. When scrolling, we want to change the position of the navigation bar so that it goes off screen to enable a so called fullscreen mode.

  1. Create a scroll view and implement UIScrollViewDelegate
  2. Inside scrollViewDidScroll, modify the Y-position of self.navigationController.navigationBar.frame
  3. Launch the app and scroll vertically

Expected: Navbar is moved off screen when you scroll down and comes back when you scroll up. Actual: Navbar stays in position at all times.

Workarounds: Use iOS 14 or below

Here are two video demonstrations of Expected VS Actual results:

iOS 14.5: https://www.dropbox.com/s/kplb13taioxrw7g/iOS14.5_scroll.mov?dl=0

iOS 15.0: https://www.dropbox.com/s/uyvy6oxcgi1wj8i/iOS15_scroll.mov?dl=0

Code:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if let navController = self.navigationController {
        var navBarFrame = navController.navigationBar.frame

        // Change y-position
        let newYPosition = min(0, max(-(navbarHeight), (scrollView.contentOffset.y * -1))) + statusBarHeight
        navBarFrame.origin.y = newYPosition
        navController.navigationBar.frame = navBarFrame

        NSLog("New Y positon: %f", newYPosition)
    }
}

And here's the full demo project: https://github.com/karlingen/NavbarHideOnScroll

Is this a bug?

Accepted Reply

UINavigationController owns the geometry of its own navigationBar and attempts to modify that geometry from outside of UINavigationController are not supported. This is a general rule, that a view's superview owns its geometry, and in the case of the UINavigationBar vended by UINavigationController that is the UINavigationController's view, Since you have no access to that view to subclass (or otherwise modify its behavior) there is no means to modify the position of the navigationBar without making assumptions that will change over time.

  • @Rincewind   Thanks for this definitive answer; that closes the question. However, I do not understand how it worked once on iPad and only once ! And seems to work on iPhone (simulators). Probably that's in the gray zone of unsure assumptions… Does that mean that those who implement create their own "phone" navbar ?

  • @Rincewind Thanks. So what are our options?

Add a Comment

Replies

I tested with Xcode 13 on both simulators:

  • iPhone 12 Pro Max iOS 15.0
  • iPhone 12 Pro Max iOS 4.4

I get the same behaviour.

Here is the screen shot for iOS 15:

What are your exact test conditions ? Is it on iPad simulator ?

I tested on iPad simulator as well, it works correctly.

Edit: Really strange: I repeat the test, on the exact same simulator, it doesn't work anymore ! But the above screenshot proves that it worked once.

Of course I mean iOS 14.4, not 4.4…

I still don't understand why it worked once. Another bug ?

But documentation says :

https://developer.apple.com/documentation/uikit/uinavigationcontroller

We cannot change frame: The navigation controller manages the creation, configuration, and display of the navigation bar and optional navigation toolbar. It is permissible to customize the navigation bar’s appearance-related properties but you must never change its frame, bounds, or alpha values directly. If you subclass UINavigationBar, you must initialize your navigation controller using the init(navigationBarClass:toolbarClass:) method. To hide or show the navigation bar, use the isNavigationBarHidden property or setNavigationBarHidden(_:animated:) method.

See also:

https://developer.apple.com/forums/thread/31500

There was also an interesting discussion on how to hide navigation bar progressively, a la Facebook. Just what you try to achieve. It is in french, but code is in english (objC) !

h t t p s : / / qastack.fr/programming/19819165/imitate-facebook-hide-show-expanding-contracting-navigation-bar

They set the frame of self.navigationController.navigationBar but probably in a subclass.

UINavigationController owns the geometry of its own navigationBar and attempts to modify that geometry from outside of UINavigationController are not supported. This is a general rule, that a view's superview owns its geometry, and in the case of the UINavigationBar vended by UINavigationController that is the UINavigationController's view, Since you have no access to that view to subclass (or otherwise modify its behavior) there is no means to modify the position of the navigationBar without making assumptions that will change over time.

  • @Rincewind   Thanks for this definitive answer; that closes the question. However, I do not understand how it worked once on iPad and only once ! And seems to work on iPhone (simulators). Probably that's in the gray zone of unsure assumptions… Does that mean that those who implement create their own "phone" navbar ?

  • @Rincewind Thanks. So what are our options?

Add a Comment

@karlingen I see an option: design a custom view that will mimic the navigation bar

  • when you start scrolling, hide the navigation bar
  • show this custom view (with all the same buttons that were in navbar): buttons still to be active once scroll started
  • move the custom view according to scroll position
  • when scroll return to original position, unhide navigation bar and hide custom view…
Add a Comment