在 UIViewController 之间使用数组堆栈和动画会产生意外行为

Using array stack and animating between UIViewControllers yields unexpected behavior

使用 Swift 4 和 Xcode 10.1

我的目标是使用数组堆栈来跟踪我的 UIViewControllers,因为我在它们之间设置动画。 我已经完成了大部分工作,但是当我第二次将 ViewController 转换到视图中时,它似乎与下面的当前视图控制器交换了位置。

基本设置(故事板)是一个 UIViewController,它有一个 ContainerView (MainContainer) 和 4 个独立的 ViewControllers(VC1、VC2、 VC3,VC4)。每个 VC 也有一个 restorationId 以便我可以打印它并跟踪堆栈数组中的位置。

一开始我显示VC1,然后我用按钮来测试处理每个VC。

代码如下:

@IBOutlet weak var MainContainer: UIView!    
var vc1:UIViewController!
var vc2:UIViewController!
var vc3:UIViewController!
var vc4:UIViewController!
var vcStack = Stack<UIViewController>()

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    vc1 = self.storyboard?.instantiateViewController(withIdentifier: "VC1")
    vc2 = self.storyboard?.instantiateViewController(withIdentifier: "VC2")
    vc3 = self.storyboard?.instantiateViewController(withIdentifier: "VC3")
    vc4 = self.storyboard?.instantiateViewController(withIdentifier: "VC4")

    vcStack.push(vc1)
    displayVC(content: vc1!)
}

func displayVC(content: UIViewController) {
    // Works to change VCs - no transition
    self.addChild(content)
    content.view.translatesAutoresizingMaskIntoConstraints = false
    self.MainContainer.addSubview(content.view)

    NSLayoutConstraint.activate([
        content.view.leftAnchor.constraint(equalTo: self.MainContainer.leftAnchor, constant: 0),
        content.view.topAnchor.constraint(equalTo: self.MainContainer.topAnchor, constant: 0),
        content.view.bottomAnchor.constraint(equalTo: self.MainContainer.bottomAnchor, constant: 0),
        content.view.rightAnchor.constraint(equalTo: self.MainContainer.rightAnchor, constant: 0)
        ])

    content.didMove(toParent: self)
}

func slideINTransition(newVC: UIViewController){
    print("slide IN stack newVC: \(newVC.restorationIdentifier ?? "novalue")")
    print("slide IN stack stack top: \(vcStack.top!.restorationIdentifier ?? "novalue")")
    newVC.view.frame = vcStack.top!.view.frame;
    vcStack.top!.view.superview?.insertSubview(newVC.view, aboveSubview: vcStack.top!.view)
    newVC.view.transform = CGAffineTransform(translationX: vcStack.top!.view.frame.size.width, y: 0)

    UIView.animate(withDuration: 0.5,
                   delay: 0.0,
                   options: UIView.AnimationOptions.curveEaseInOut,
                   animations: {
                    newVC.view.transform = CGAffineTransform(translationX: 0, y: 0)
                  },
                   completion:nil// { finished in
                    //self.vcStack.top!.present(newVC, animated: false, completion: nil) //throws an error when uncommented
                //}
                  )
    vcStack.push(newVC)
    for junk in vcStack.array{
        print("slide IN stack: \(junk.restorationIdentifier ?? "novalue")")
    }
}
@IBAction func But_Back_Pressed(_ sender: UIButton) {
    for junk in vcStack.array{
        print("-----back but stack: \(junk.restorationIdentifier ?? "novalue")")
    }
    slideOUTTransition()
}
func slideOUTTransition(){
    print("slideOUT count: \(vcStack.count)")
    if (vcStack.count > 1) {
        let visibleVC = vcStack.pop()
        visibleVC!.view.transform = CGAffineTransform(translationX: 0, y: 0)
        UIView.animate(withDuration: 0.5,
                       delay: 0.0,
                       options: UIView.AnimationOptions.curveEaseInOut,
                       animations: {
                        visibleVC!.view.transform = CGAffineTransform(translationX: visibleVC!.view.frame.size.width, y: 0)
                       },
                       completion: { finished in
                        visibleVC!.dismiss(animated: false, completion: nil)
                       }
                       )
        for junk in vcStack.array{
            print("slideOUT stack: \(junk.restorationIdentifier ?? "novalue")")
        }
        print("BACK pop count: \(vcStack.count)")
    }
}

@IBAction func But_VC1_Pressed(_ sender: UIButton) {
    if vcStack.hasElement(vc1){
        print("Trans vc1 exists - doing nothing")
    }else{
        print("Trans vc1 unique")
        slideINTransition(newVC: vc1)
    }
}
@IBAction func But_VC2_Pressed(_ sender: UIButton) {
    if vcStack.hasElement(vc2){
        print("Trans vc2 exists - doing nothing")
    }else{
        print("Trans vc2 unique")
        slideINTransition(newVC: vc2)
    }
}
@IBAction func But_VC3_Pressed(_ sender: UIButton) {
    if vcStack.hasElement(vc3){
        print("Trans vc3 exists - doing nothing")
    }else{
        print("Trans vc3 unique")
        slideINTransition(newVC: vc3)
    }
}
@IBAction func But_VC4_Pressed(_ sender: UIButton) {
    if vcStack.hasElement(vc4){
        print("Trans vc4 exists - doing nothing")
    }else{
        print("Trans vc4 unique")
        slideINTransition(newVC: vc4)
    }
}

public struct Stack<T> where T: Equatable {
    fileprivate var array = [T]()

    public var isEmpty: Bool {
        return array.isEmpty
    }

    public var count: Int {
        return array.count
    }

    public mutating func push(_ element: T) {
        array.append(element)
    }

    public mutating func pop() -> T? {
        return array.popLast()
    }

    public func hasElement(_ element: T) -> Bool {
        return array.contains(element)
    }

    public var top: T? {
        return array.last
    }
}

一个失败的简单测试:

通过单击 VC2 按钮添加 VC2,它从右向左滑动到 VC1 上,然后 VC2 被压入堆栈.单击后退按钮使 VC2 从左向右向后滑动,再次显示 VC1,然后 VC2 从堆栈中弹出。到目前为止这是有效的 - 只有 VC1 是可见的。

但是,当我第二次单击 VC2 按钮时,它似乎与 VC1 交换了位置。 VC1 立即替换为 VC2,然后 VC1 从左到右滑过顶部。 不知何故,看起来 VC2 和 VC1 在动画之前被交换了。

另请注意 - 我可以将 VC2、VC3 和 VC4 相互压入,并将它们压入堆栈,然后单击返回以删除他们从视图和它的工作原理。但是,一旦任何 vc 被再次推送,交换就会发生在紧邻下方的任何视图中。

我使用 print 语句跟踪堆栈数组,它似乎工作正常。动画也能正常工作,所以我一定是在 display/add VC 的方式上做错了....

知道为什么每个 VC 可以动画显示,然后删除,但不能再次动画显示吗?

我认为在视图控制器包含方面有很多地方可以改进(无意冒犯!)但要暂时解决您的问题,请执行以下操作:

而不是在 slideOUTTransition() 的完成块中调用 visibleVC!.dismiss(animated: false, completion: nil)(您没有提供任何内容,因此您不必关闭任何内容)从父视图中删除视图并重置其转换:

// visibleVC!.dismiss(animated: false, completion: nil)
visibleVC!.view.removeFromSuperview()
visibleVC!.view.transform = .identity