在推送时制作一个透明的导航栏并在弹出时恢复它

Make a transparent navigation bar on push and restore it on pop

我正在尝试创造一种效果,即使它接近我想要的效果,但它有一些 UI 故障,我将解释。

比方说,我有我的主页导航控制器,我点击一个单元格来推送一个新的视图控制器。

在该视图控制器的 viewWillAppear(:) 上,我实现了以下内容:

self.navigationController?.navigationBar.isTranslucent = true
self.navigationController?.navigationBar.backgroundColor = .clear
self.navigationController?.navigationBar.tintColor = .white
self.navigationController?.navigationBar.barTintColor = .clear
self.navigationController?.navigationBar.shadowImage = UIImage()
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)

通过这样做,推送的视图控制器将使其 navigationBar 透明,并且仍然保持按钮可见(这是我想要的),但是在推送动画中,它在父控制器,因为它也隐藏了父控制器的 navigationBar

然后在推送的视图控制器上 viewWillDisappear(_:) 我实现了以下内容:

self.navigationController?.navigationBar.shadowImage = nil
self.navigationController?.navigationBar.setBackgroundImage(nil, for: .default)
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.backgroundColor = .white
self.navigationController?.navigationBar.barTintColor = .white

通过这样做,我试图重置父级的 navigationBar 默认属性,但是这样做我在动画期间看到一个黑条,在它完成动画之前,这会导致错误 UI/UX.

我是不是做错了什么,或者有更好的方法吗?

谢谢。

所以在经过一些挖掘和来自@Paulo 的一些非常有用的提示之后,我设法按照我的意愿解决了这个问题。

这应该更容易实现,Apple 应该为开发人员提供简单的选项,而不是绕过一些 hack 来实现它,但无论如何。

我发现其中一个秘密是我在浏览视图控制器时滥用了 navigationBar.isTranslucent = true / false

为了做到这一点,我在 parentViewController 中设置了默认的 navigationBar 属性,该属性将使用透明 navigationBar; 推送到视图控制器;我按照以下方式完成了它:

self.navigationController?.navigationBar.backgroundColor = .white
self.navigationController?.navigationBar.barTintColor = .white
self.navigationController?.navigationBar.shadowImage = nil
self.navigationController?.navigationBar.setBackgroundImage(nil, for: .default)

pushedViewController viewWillAppear(_:) 您需要执行以下操作:

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

    guard self.navigationController?.topViewController === self else { return }
    self.transitionCoordinator?.animate(alongsideTransition: { [weak self](context) in
        self?.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
        self?.navigationController?.navigationBar.shadowImage = UIImage()
        self?.navigationController?.navigationBar.backgroundColor = .clear
        self?.navigationController?.navigationBar.barTintColor = .clear
    }, completion: nil)
}

这里我设置了想要的 navigationBar 透明度,但是正如你注意到的,不需要使用 isTranslucent 属性,我注意到通过强制 UI 会在推送动画上显示一些闪烁和奇怪的布局。

然后在同一个视图控制器(推送)上,您需要实现您在 parentViewController:

中实现的默认、期望的 navigationBar 属性
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    self.transitionCoordinator?.animate(alongsideTransition: { [weak self](context) in
        self?.navigationController?.navigationBar.setBackgroundImage(nil, for: UIBarMetrics.default)
        self?.navigationController?.navigationBar.shadowImage = nil
        self?.navigationController?.navigationBar.backgroundColor = .white
        self?.navigationController?.navigationBar.barTintColor = .white
        }, completion: nil)
}

通过这样做,一切都应该按预期工作。

希望对以后的人有所帮助。

经过 DAYS 的思考,我想我已经找到了最好的(但远非完美的)解决方案。这样您就可以在发生转换时自定义导航栏,还可以处理中断的转换,例如当用户取消弹出时。

    func yourCustomizationMethod(bar: UINavigationBar) {
            // Modify the navigation bar
        }
            
    override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    guard navigationController?.topViewController == self else {
        return
    }
    // The coordinator is nil in some cases
    if let coordinator = transitionCoordinator,
        coordinator.animate(alongsideTransition: { context in
            guard let bar = self.navigationController?.navigationBar else {
                return
            }
            self.yourCustomizationMethod(bar: bar)
        }, completion: nil) {
        return
    } else if let bar = navigationController?.navigationBar {
        yourCustomizationMethod(bar: bar)
    }
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    // Customize only if user transition gets canceled to avoid multiple customizations.
    guard transitionCoordinator?.isCancelled == true,
        let bar = navigationController?.navigationBar else {
        return
    }
    yourCustomizationMethod(bar: bar)
}