UIPageViewController 切换页面时避免 iOS 状态栏抖动

Avoid iOS status bar jitter when switching between pages of UIPageViewController

我正在构建一个使用带有两个页面的 UIPageViewController 的简单应用程序。在一个页面上,我想显示状态栏,但在另一页上我不想。为了使过渡看起来不错,我在用户完全到达 UIPageViewController 的页面后将状态栏滑入和滑出,具体取决于它是哪个页面。

这在像 iPhone XR 这样带有传感器外壳(凹口)的设备上运行良好,但在像 iPhone 8 这样的具有普通矩形屏幕的设备上看起来非常故障。

iPhone XR 示例导航栏的高度及其下方的内容保持一致。

iPhone 8 示例当状态栏开始动画(滑动)时,内容会向上跳,但随后 returns随着导航栏动画向下移动到它的原始位置。


最佳解决方案?

似乎最好的解决方案之一是以某种方式强制导航栏始终为导航栏的完整高度 + 状态栏高度,因此导航栏和内容保持在同一位置,而状态酒吧向下动画。这类似于 iPhone XR 屏幕上的行为。我怎样才能做到这一点?


现有代码

在我的 UIPageViewController 上,我跟踪 currentViewController 属性,并更新覆盖的 prefersStatusBarHidden 相应地变化。

private var currentViewController: UIViewController?

override var prefersStatusBarHidden: Bool {
    if let current = currentViewController,
        current == settingsNavigationController {
        return false
    } else {
        return true
    }
}

为了设置 currentViewController 变量并调用必要的 setNeedsStatusBarAppearanceUpdate() 方法,我覆盖了 didFinishAnimating UIPageViewControllerDelegate 的方法:

extension PageViewController: UIPageViewControllerDelegate {

    func pageViewController(_ pageViewController: UIPageViewController,
                            didFinishAnimating finished: Bool,
                            previousViewControllers: [UIViewController],
                            transitionCompleted completed: Bool) {

        currentViewController = pageViewController.viewControllers?.first

        UIView.animate(withDuration: 0.2) { () -> Void in
            self.setNeedsStatusBarAppearanceUpdate()
        }
    }
}

为了不那么刺耳,我使用了 .slide 动画(如果我不考虑这个就没有动画,在矩形屏幕上它仍然会跳动):

override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
        return .slide
}

更新:

感谢 Leon Deriglazov 的回答,我已经能够通过子类化 UINavigationController 来避免内容的跳跃性。我添加了以下代码来触发动画(与状态栏动画具有相同的持续时间),在状态栏向下滑动时将内容向上移动,使内容看起来停留在同一个位置:

class SettingsNavigationController: UINavigationController {

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

        UIView.animate(withDuration: 0.2) {
            self.additionalSafeAreaInsets.top = 0
        }
    }

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

        additionalSafeAreaInsets.top = 20
    }
}

注意:这不包含检测设备类型所需的附加逻辑。

这是它的样子:

虽然理想情况下我希望避免导航栏随状态栏向下动画,就像在 iPhone XR 屏幕上一样,但这是一种更流畅的体验。如果有人对如何将导航栏固定在同一位置有任何想法,我仍然会很感激。

我会考虑在您的设置控制器中使用 additionalSafeAreaInsets 以使其始终考虑状态栏的高度。然后,当您显示状态栏时,您可能必须将其关闭(对此不是 100% 确定)。