在自定义UIViewController容器中管理子项和在子项之间导航的正确方法是什么?

时间:2017-08-29 15:08:25

标签: ios swift uiviewcontroller

我一直使用标准UIKit UIViewController容器,例如UIPageViewController,以满足我的容器需求,但我想尝试创建自定义容器。但是有一些我不确定的具体事情。这将完全是程序化的,没有故事板(因此,没有segue)。

  1. 我希望容器能够容纳(为了这个例子)4 UIViewController,这将是UI的4个主要部分。在每个部分中,将有一个UINavigationController来处理其部分中的堆栈。因此,这4个UIViewController应该永久地作为子容器添加到容器中(并且从不删除),对吗?推理:当离开并返回该部分时,用户不会在导航堆栈中丢失他/她的位置。

  2. 我想在这4个视图控制器之间实现自定义动画过渡,这些视图控制器将使用平移手势将它们拖入和拖出视图。过去,例如,使用UINavigationController,自定义动画对象会有效地覆盖pushViewController(),但是我会在这里有效地覆盖什么?

  3. 如果我错了,请纠正我,但根据Apple的说法,据我所知,视频控制器既可以被推送也可以被呈现为#34;。我说"呈现"因为"呈现"包括"礼物" (我认为是令人困惑的东西),它带来UIViewController模态,而#34; show&#34 ;,也包括在"呈现"它不会以UIViewController模式引入,它以push()之类的方式工作,除了它不在导航堆栈中。

1 个答案:

答案 0 :(得分:1)

你问:

  

因此,这4个UIViewController应该永久地作为子容器添加到容器中(并且从不删除),对吗?

作为一项细微的改进,您可以及时加载它们,只有当您真正需要它们时才加载它们。这可以避免因加载许多可能尚未显示的视图控制器而导致的任何潜在延迟(如果有的话)。但是,当然,一旦加载,你可能会把它们留在记忆中。这是Apple自己的容器视图控制器采用的模式。

  

... show ...没有UIViewController模式,它以push()之类的方式工作,除了它不在导航堆栈中。

show方法将导航视图控制器层次结构,确定它在哪个上下文中找到自己并将相应地转换。如果您在导航控制器或拆分视图控制器中,它将执行适当的转换。但如果您不在其中一个控制器中, 将执行模态演示。请参阅show documentation

  

我想在这4个视图控制器之间实现自定义动画过渡,这些视图控制器将使用平移手势将它们拖入或拖出视图...

我建议引用Custom Container View Controller Transitions,其中概述了他们如何将custom transition模式与您自己的view controller containment结合使用。

如果那变得太笨重,你可能只是去老学校"并且只需使用手势为包含的视图控制器设置视图的动画,然后执行所有适当的视图控制器包含相关调用。它并不优雅,但我怀疑这可能更容易。

如果您手动执行此操作(更容易),唯一的方法是确保执行适当的外观并查看控制器包含调用。因此,在下面的示例中,我们从作为容器视图控制器的子项添加的四​​个子视图控制器之一开始,这显示了如何使用手势将该子项替换为另一个子视图控制器。这使视图控制器层次结构与视图层次结构保持同步,并确保子项获得称为的适当的外观方法:

import UIKit
import UIKit.UIGestureRecognizerSubclass
import os.log

class ViewController: UIViewController {

    lazy private var swipeFromRight: UIScreenEdgePanGestureRecognizer = {
        let swipe = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleSwipeFromRight(_:)))
        swipe.edges = .right
        return swipe
    }()

    private var nextChildController: UIViewController?
    private var currentChildController: UIViewController?

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addGestureRecognizer(swipeFromRight)
    }

    @objc func handleSwipeFromRight(_ gesture: UIScreenEdgePanGestureRecognizer) {
        let percent = -gesture.translation(in: gesture.view).x / gesture.view!.bounds.width

        if gesture.state == .began {
            os_log("starting gesture", type: .debug)

            guard let next = nextViewController() else {
                gesture.state = .cancelled
                return
            }

            currentChildController = childViewControllers.first!
            nextChildController = next
            startAppearance(next, replacing: currentChildController!, in: currentChildController!.view.superview)

            // prepare for gesture driven animation

            next.view.frame = currentChildController!.view.frame
            next.view.transform = .init(translationX: next.view.frame.width, y: 0)
        } else if gesture.state == .changed {
            // update animation based upon progress percent

            nextChildController!.view.transform = .init(translationX: nextChildController!.view.frame.width * (1 - percent), y: 0)
        } else if gesture.state == .ended {
            // figure out whether we should finish the animation (if not, we'll reverse it)

            let velocity = gesture.velocity(in: gesture.view).x
            let shouldFinish = velocity < 0 || (velocity == 0 && percent > 0.5)

            os_log("finishing gesture: shouldFinish=%@", type: .debug, shouldFinish ? "true" : "false")

            let next = nextChildController!
            let previous = self.currentChildController!

            if shouldFinish {
                UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut, animations: {
                    next.view.transform = .identity
                }, completion: { finished in
                    self.endAppearance(next, replacing: previous)
                })
            } else {
                beginCancelAppearance(next, replacing: previous)

                UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut, animations: {
                    next.view.transform = .init(translationX: next.view.frame.width, y: 0)
                }, completion: { finished in
                    self.endCancelAppearance(next, replacing: previous)

                    next.view.transform = .identity
                })
            }
        }
    }

    // MARK: Child appearance/containment helpers

    private func startAppearance(_ appearingController: UIViewController, replacing disappearingController: UIViewController, in view: UIView? = nil) {
        appearingController.beginAppearanceTransition(true, animated: true)

        addChildViewController(appearingController)
        view?.addSubview(appearingController.view)

        disappearingController.willMove(toParentViewController: nil)
        disappearingController.beginAppearanceTransition(false, animated: true)
    }

    private func beginCancelAppearance(_ appearingController: UIViewController, replacing disappearingController: UIViewController) {
        appearingController.willMove(toParentViewController: nil)
        appearingController.beginAppearanceTransition(false, animated: true)

        disappearingController.willMove(toParentViewController: self)
        disappearingController.beginAppearanceTransition(true, animated: true)
    }

    private func endCancelAppearance(_ appearingController: UIViewController, replacing disappearingController: UIViewController) {
        appearingController.view.removeFromSuperview()
        appearingController.removeFromParentViewController()
        appearingController.endAppearanceTransition()

        disappearingController.endAppearanceTransition()
    }

    private func endAppearance(_ appearingController: UIViewController, replacing disappearingController: UIViewController) {
        appearingController.endAppearanceTransition()
        appearingController.didMove(toParentViewController: self)

        disappearingController.view.removeFromSuperview()
        disappearingController.removeFromParentViewController()
        disappearingController.endAppearanceTransition()
    }

    // MARK: Next/Previous child helpers

    private func nextViewController() -> UIViewController? { ... }

    private func previousViewController() -> UIViewController? { ... }

}

显然,用你想要的任何动画替换动画。而且,很明显,出售适合您应用的视图控制器。但它说明了需要完成的手势,外观和遏制呼叫的性质。