【问题标题】:Keep a view while push transitioning between view controllers在视图控制器之间推送转换时保持视图
【发布时间】:2020-04-24 10:48:42
【问题描述】:

我有一个标签栏 iOS 应用。其中一个选项卡是地图 (MyMapViewController)。 MyMapViewController 顶部有一个自定义“搜索”栏:

一旦用户点击“搜索”栏,他就会进入搜索屏幕:

现在用户可以键入一些名称,对象列表被过滤,并允许用户找到所需的对象。一切正常。

唯一的问题是标签栏在搜索屏幕上可见。我需要在搜索屏幕可见时将其删除,并在用户返回地图屏幕后立即将其返回。这就是我想要实现的目标:

目前,搜索屏幕是MyMapViewController 的子视图控制器。它被称为MySearchViewController。 “地图”模式和“搜索”模式之间的动画过渡是使用核心动画执行的。视图控制器上没有任何“push”/“pop”或“present”/“dismiss”操作。

我无法通过将标签栏 (UITabBar) 设为 isHidden = true 或移动其框架来隐藏标签栏 (UITabBar),因为它会留下一个空白矩形。 据我所知,隐藏标签栏只有两种方法:

  1. 将新控制器(使用hidesBottomBarWhenPushed = true)推送到导航堆栈
  2. 呈现模态控制器

看来我需要返工了

(父视图控制器)MyMapViewController,(子视图控制器)MySearchViewController

UINavigationStack:MyMapViewController --(推送)--> MySearchViewController

但是。在这种情况下,我应该如何处理“搜索”栏?它是MyMapViewController 的一部分,也是MySearchViewController 的一部分。视图是否有可能成为两个UIViewControllers 的一部分?另外,在从MyMapViewControllerMySearchViewController 的推动过渡期间,我需要它进行一些动画处理(如您所见,放大的玻璃必须转换为后退箭头)。

【问题讨论】:

  • 我认为最简单的方法是在新控制器上设置一个类似的搜索字段,相同的大小和位置,以便在视觉上看起来是相同的元素。

标签: ios uiviewcontroller uinavigationcontroller uitabbarcontroller uitabbar


【解决方案1】:

UITabBarController 的问题是它的 TabBar 是在没有使用 NSLayoutConstraints 的情况下添加的(或者更准确地说,它将自动调整大小的掩码转换为约束)。因此,您可以使用两种方法:

1) 以您现在的方式使用 UITabBarController,但它需要一些技巧来隐藏它 - 基本上在 UINavigationController 中使用 UITabBarController 以便在其上推送一个视图(但转换将是可见的,即使您会在没有动画的情况下推送它(键盘会开始隐藏),或者您可以手动隐藏 TabBar 并调整 TabBar 内容视图的框架,如https://stackoverflow.com/a/6346096/7183675所示)。

在最后一种情况下,您还必须在更改内容视图之前记住它的框架(或在再次取消隐藏 TabBar 之前计算它)。此外,由于它不在官方 API 中,您必须考虑到 UITabBarController 中的子视图顺序可能会发生变化,并且效果可能看起来很奇怪(或者只是让应用程序崩溃)

2) 将“普通”UIViewController 与 UITabBar 一起使用,并使用约束手动添加其项目。它也可以是自定义 UIView 子类和从 XIB 创建的几个按钮。在这里,您直接创建约束,因此您可以更好地控制。 但是这个也不会没有一些技巧,因为添加到单个 UIViewController 的 UITabBar 会在每次转换时与这个 UIViewController 一起使用(假设你在每个 UIViewController 中都有 UINavigationController 它会很常见)。

因此,在这种情况下,主要问题是制作单个底栏并将其传输到 viewDidAppear 上的 UIWindow,其中创建了您唯一的底栏 - 从情节提要或 xib 文件中推荐。对于下一个视图,您只会传递对它的引用或将此指针保留在一个类中。您还应该记住在标签栏下创建覆盖安全区域的视图。

看起来像这样:

    private var firstRun = false

    override func viewDidLoad() {
        super.viewDidLoad()
        firstRun = true
}

    override func viewDidAppear(_ animated: Bool) {
                super.viewDidAppear(animated)
                guard firstRun else {
                    bottomBar.superview?.bringSubviewToFront(bottomBar)
                    bottomSafeAreaView.superview?.bringSubviewToFront(bottomSafeAreaView)
                    return
                }

                guard let window = UIApplication.shared.windows.first, let bottomB = bottomBar, let bottomSafeArea = bottomSafeAreaView else { return }

                if bottomB.superview != window {
                    bottomB.deactivateConstrainsToSuperview()
                    bottomSafeArea.deactivateConstrainsToSuperview()

                    window.addSubview(bottomSafeArea)
                    window.addSubview(bottomB)
                    let bottomLeft = NSLayoutConstraint(item: bottomSafeArea, attribute: .leading, relatedBy: .equal, toItem: window, attribute: .leading, multiplier: 1, constant: 0)
                    let bottomRight = NSLayoutConstraint(item: bottomSafeArea, attribute: .trailing, relatedBy: .equal, toItem: window, attribute: .trailing, multiplier: 1, constant: 0)
                    let bottomBottom = NSLayoutConstraint(item: bottomSafeArea, attribute: .bottom, relatedBy: .equal, toItem: window, attribute: .bottom, multiplier: 1, constant: 0)
                    let leftConstraint = NSLayoutConstraint(item: bottomB, attribute: .leading, relatedBy: .equal, toItem: window, attribute: .leading, multiplier: 1, constant: 0)
                    let rightConstraint = NSLayoutConstraint(item: bottomB, attribute: .trailing, relatedBy: .equal, toItem: window, attribute: .trailing, multiplier: 1, constant: 0)
                    let bottomConstraint = NSLayoutConstraint(item: bottomB, attribute: .bottom, relatedBy: .equal, toItem: bottomSafeArea, attribute: .top, multiplier: 1, constant: 0)
                    NSLayoutConstraint.activate([bottomLeft, bottomRight, bottomBottom, leftConstraint, rightConstraint, bottomConstraint])
                }

                window.layoutIfNeeded()

                DispatchQueue.main.async(execute: {
                    bottomB.superview?.bringSubviewToFront(bottomB)
                    bottomSafeArea.superview?.bringSubviewToFront(bottomSafeArea)
                })

                firstRun = false
            }

加上一个在扩展中创建的实用方法:

extension UIView {

    func deactivateConstrainsToSuperview() {
        guard let superview = self.superview else {return}
        NSLayoutConstraint.deactivate(self.constraints.filter({
            return ($0.firstItem === superview || $0.secondItem === superview)
        }))
    }
}

所以要写一点代码,但只能写一次。之后,您将拥有在必要时易于显示或隐藏的 TabBar,以这种方式在“内容视图”和安全区域之间使用约束

private func hideBottomBar() {
        UIView.animate(withDuration: Constants.appAnimation.duration, animations: { [weak self] in
            guard let self = self else { return }
            self.bottomBar.isHidden = true
            self.bottomBarHeightConstraint.constant = 0
            self.bottomBar.superview?.layoutIfNeeded()
        })
    }

private func showBottomBar() {
        UIView.animate(withDuration: Constants.appAnimation.duration, animations: { [weak self] in
            guard let self = self else { return }
            self.bottomBar.isHidden = false
            self.bottomBarHeightConstraint.constant = Constants.appConstraintsConstants.bottomBarHeight
            self.bottomBar.superview?.layoutIfNeeded()
        })
    }

关于覆盖安全区域的视图高度(tabBar底部和BottomLayoutGuide顶部之间)

if #available(iOS 11.0, *) {
                self.bottomSafeAreaViewHeightConstraint.constant = self.view.safeAreaInsets.bottom
            } else {
                self.bottomSafeAreaViewHeightConstraint.constant = 0
            }

希望对你有所帮助,祝你好运!

【讨论】:

    【解决方案2】:

    如果您需要隐藏 Tapbar,可以通过 self.tabBarController?.tabBar.isHidden = true 来实现。

    如果想从 SearchViewController 中使用它(不显示或推送),您需要通过 self.addChild(searchVC) 在视图控制器的层次结构中添加 SearchViewController 并添加 self.tabBarController?.tabBar.isHidden = true self.tabBarController?.tabBar.setNeedsLayout()

    【讨论】:

    • tabBar.isHidden = true 无法按预期工作。它留下一个白色矩形:stackoverflow.com/questions/1110052/…, stackoverflow.com/questions/2426248/…
    • @Nick 你能分享一些示例项目吗,因为我已经创建了测试项目并且一切正常
    • 将 tabBarController 放入 naviagationViewController 然后点击搜索推送 searchVC。 self.tabbarController.naviagtionController.push(searchVC) 编码愉快。
    猜你喜欢
    • 1970-01-01
    • 2016-10-22
    • 2010-12-11
    • 1970-01-01
    • 2015-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多