0

Using Swift 4 and Xcode 10.1

My goal is to use an Array Stack to track my UIViewControllers as I animate between them. I've gotten most of it working, however when I transition a ViewController into view for the second time, it seems to swap places with the current view controller below.

The basic setup (storyboard) is a UIViewController that has a ContainerView (MainContainer) and 4 separate ViewControllers (VC1, VC2, VC3, VC4). Each VC also has a restorationId so that I can print it and track the position in the stack array.

At the start I display VC1, then I use buttons to test handling each VC.

Here's the code:

@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
    }
}

A simple test that fails:

Add VC2 by clicking the VC2 button, and it slides right-to-left over VC1 and VC2 is pushed onto the stack. Click the Back button to make VC2 slide backwards, left-to-right, to reveal VC1 again and VC2 is popped off the stack. So far this works - only VC1 is visible.

However, when I then click the VC2 button for the second time it appears to swap places with VC1. VC1 is immediately replaced with VC2, and then VC1 slides left-to-right over the top. Somehow it looks like VC2 and VC1 are swapped before animating.

Also of note - I can push VC2, VC3, and VC4 over each other, and push them onto the stack, and then click Back to remove them from view and it works. However, once any vc is then pushed again, the swapping occurs with whatever view is immediately below.

I track the stack array using print statements and it appears to work properly. The animations work properly as well so I must be doing something wrong with how I display/add the VCs....

Any ideas why each VC can be animated into view, then removed, but not animated into view again?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
wayneh
  • 4,393
  • 9
  • 35
  • 70
  • You should probably use `UIViewController.present()` instead of trying to add the VC's view through `insertSubview()`. Any reason you aren't using UINavigationController which is designed for this? – adamfowlerphoto Dec 12 '18 at 08:45
  • @Spads I'm working from other code found here on SO and some of it is a bit old. I'm not using UINavigationController because the flow of the Views branches off several times and I wasn't sure how to do it. My first post related to this explains: https://stackoverflow.com/questions/53549918/how-to-use-one-containerview-to-show-multiple-viewcontrollers – wayneh Dec 12 '18 at 14:58

1 Answers1

1

I think there are many things to make better regarding view controller containment (no offence!) but to fix your problem for now do the following:

Instead of calling visibleVC!.dismiss(animated: false, completion: nil) in the completion block of your slideOUTTransition() (you did not present anything, so you do not have to dismiss anything) remove the view from the superview and reset its transform:

// visibleVC!.dismiss(animated: false, completion: nil)
visibleVC!.view.removeFromSuperview()
visibleVC!.view.transform = .identity
André Slotta
  • 13,774
  • 2
  • 22
  • 34
  • Andre - no offense taken! I'll try your suggestion, but if you also have a suggestion for a better way, please let me know. My first post related to this explains: https://stackoverflow.com/questions/53549918/how-to-use-one-containerview-to-show-multiple-viewcontrollers – wayneh Dec 12 '18 at 14:59
  • @wayneh I set up a quick demo project. Take a look and feel free to ask if anything is unclear. Hope you get an idea: https://www.dropbox.com/sh/f2osv7f147kmbcg/AAC0pr7Asu7aNVvPbtY69QrYa?dl=0 – André Slotta Dec 12 '18 at 16:23
  • First, your two line fix above works perfectly! Second, I tried your code and I have never seen such a clear usage of the Navigation Controller before. I'm almost embarrassed by my efforts, but also grateful for your sample - THANKS! – wayneh Dec 13 '18 at 02:44
  • Haha. Glad I could help. :) – André Slotta Dec 13 '18 at 10:12