setNavigationBarHidden animation not working as expected on iPhone X
Asked Answered
L

2

4

I have code that enters full screen mode by hiding the UINavigationController's navigation bar. I want a smooth animated zooming effect when entering full screen. I use setNavigationBarHidden(_:animated:). This has all worked fine up to now, even on iOS 11, but on iPhone X the animation is not working well. On hiding, there is no animation and the nav bar just disappears. On unhiding, it does animate but the nav bar appears at a slower rate than the navigation controller's content area reduces, so an ugly black background shows through the navigation bar area during the animation.

I can recreate this in a simple test app. I have a UIViewController embedded in a UINavigationController.

Storyboard

  • UINavigationController Navigation Bar: Style == Black; Translucent OFF
  • UIViewController: Extend Edges: all options OFF.

I have tried all the combinations of Adjust Scroll View Insets and Extend Edges that I can think of but they made no difference.

Code

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    setFullScreen(on: fullScreen, animated: animated)
}

override var prefersStatusBarHidden: Bool
{
    return fullScreen
}

override var preferredStatusBarStyle: UIStatusBarStyle
{
    return .lightContent
}

@IBAction func onToggleNavBarVisibility(_ sender: Any) {

    if let navBarHidden = self.navigationController?.isNavigationBarHidden {
        // Toggle the state
        fullScreen = !navBarHidden

        setFullScreen(on: fullScreen, animated: true)
    }
}

private func setFullScreen(on : Bool, animated : Bool) {

    self.navigationController?.setNavigationBarHidden(on, animated: animated)
    self.setNeedsStatusBarAppearanceUpdate()
}

Result on iPhone X (slow animations)

Larena answered 11/11, 2017 at 15:41 Comment(0)
R
3

In your case you are using both barTintColor & navigationBarStyle with Show Hide animation. barTintColor overrides the value implied by the Style attribute You should select either barTintColor or navigationBarStyle In below code i have just used barTintColor & navigationBarStyle is default with Transulent.

enter image description here

    var fullScreen = false{
      didSet{
        self.setNeedsStatusBarAppearanceUpdate()
     }
   }
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Navigation Bar"
        navigationController?.navigationBar.barTintColor = .red
    }
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)
        setFullScreen(on: fullScreen, animated: animated)
    }
    @IBAction func onToggleNavBarVisibility(_ sender: Any) {
        if let navBarHidden = 
          self.navigationController?.isNavigationBarHidden {
            // Toggle the state
            fullScreen = !navBarHidden
            setFullScreen(on: fullScreen, animated: true)
        }
    }
    private func setFullScreen(on : Bool, animated : Bool) {
        self.navigationController?.setNavigationBarHidden(on, animated: animated)
        self.setNeedsStatusBarAppearanceUpdate()
    }

EDIT: If you want to hide status bar- use prefersStatusBarHidden with the bool value. & use setNeedsStatusBarAppearanceUpdate

   override var prefersStatusBarHidden: Bool {
        return fullScreen
    }

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

Rumen answered 13/11, 2017 at 5:4 Comment(3)
Since your image shows the transition working correctly it made me understand the problem better. The reason your code works is because you have omitted the prefersStatusBarHidden override, and this is seems to be the root cause. Really my problem title is "setNavigationBarHidden animation does not work with prefersStatusBarHidden on iPhone X". Commenting out the prefersStatusBarHidden override makes my code work, though of course I want to hide it to get the full screen effect. (My original code works fine on all iPhone simulators apart from iPhone X.)Larena
@Ben, Please see EDIT. It works as you need, replaced prefersStatusBarHidden with UIApplication.shared.isStatusBarHidden = on. However keeping status bar on iPhone-X is good to include,The status bar also displays information people find usefulRumen
Interesting, and definitely helpful. Not a full answer though because (1) The status bar disappearance doesn't animate, and if I use UIApplication.shared.setStatusBarHidden(on, with: .fade) I get a deprecation warning; (2) 'View controller-based status bar appearance' is supposed to be YES in the Info.plist nowadays; (3) The reappearance animation is still not quite right. I feel a bug report coming on... If you use the Photos app on iPhone X, the status bar is hidden in full screen mode so I guess it's OK to do it for temporary presentation reasons, which I am.Larena
E
0

That's clearly a UIKit bug. I've filed FB8980917:

When hiding the navigation bar simultaneously with the status bar using a slide animation, the navigation bar hides without animation. In the opposite direction, the status bar appears with a fade animation instead of the specified slide animation.

To reproduce, run the attached sample project. Use Simulator's slow animations or record the device's screen and step through the frames.

I've also attached a "Screen video.mp4" for your reference.

Note 1: As a workaround, we could resort to the deprecated UIApplication.setStatusBarHidden(_:with:) API (see "Screen video legacy.mp4"). This mostly works except that the status bar animation duration is longer than the navigation bar animation duration. However, it requires setting UIViewControllerBasedStatusBarAppearance=NO in Info.plist so it's an all or nothing approach which opts out the whole app of the modern API.

Note 2: Returning .fade for preferredStatusBarUpdateAnimation doesn't work either. First, it's ugly because the navigation bar still slides out (and can't be configured to fade out), second, the problem of the missing hide animation of the navigation bar persists.

Note 3: Using UINavigationController's hidesBarsOnTap property doesn't work either. The problem remains. The sample app also has hidesBarsOnTap enabled.

Sample code:

class ViewController: UIViewController {
    var fullScreen = false

    override var prefersStatusBarHidden: Bool {
        return navigationController!.isNavigationBarHidden
    }

    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }
    
    override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
        return .slide
    }

    @IBAction func toggleFullscreen(_ sender: Any) {
        fullScreen = !fullScreen

        navigationController?.setNavigationBarHidden(fullScreen, animated: true)
        setNeedsStatusBarAppearanceUpdate()
    }
}

While the workaround described in Note 1 kind of works, I can't recommend it since the API is deprecated since iOS 9.0. So really, it's at the folks @Apple to fix this. The fact that apps such as Photos implement a similar behavior without that bug show that there is a way to do it, albeit with private API or ugly hacks.

Egyptian answered 25/1, 2021 at 16:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.