Navigation between ViewControllers created programmatically


I would like to create the tree of questions and to show different screens based on the answers. Also I would like to add custom navigation transitions. Which approach is better to use for such a navigation:
self.present(self.nextScreen, animated: true, completion: nil)
or
self.navigationController?.pushViewController(self.nextScreen, animated: true)

Why have one view controller per question? Why not just change the question in the one view controller after each step?

View controllers are not meant to be where you keep your data, that should be kept in a data provider class that the view controller can access. You should think about writing a data provider using the State Machine design pattern to keep track of which qestions have been answered.

I would also question why you want to build your view controllers in code, rather than using a storyboard or XIB files?

Thank you for a good idea. I would like to create something like interactive book and perhaps several screens (ViewControllers) with different layout.
As a designer I can draw different layouts but I need to understand how better to implement such an idea

Do you offer to change layout inside single ViewController?

class RootVC: UIViewController {

//happens only once:
override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .white
}

//happens every time when shown:
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    setupLayout()
}

private func setupLayout() { //to create different layouts here? }

You have another option: SwiftUI. Views are light weight and you can create as many views as you want and navigate between them however you want.

SwiftUI for Xcode-beta and last version of the iOS?

Yes.

Depending on how simple/complex you're wanting to get, with UINavigationViewController You can customize the animations via this delegate method.

Details on the data structure and/or content may change this, but if the basic content remains similar my tendency would be to recursively push/pop instances of the same ViewController and pass in the relevant data directly

1 Like

Thank you guys. The reason of errors was the place of the declaration of the next ViewController.

//Screen1.swift
import UIKit

class Screen1: UIViewController, UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
    
    let transition = FadePresentAnimator()
    //let nextScreen = Screen2() //2nd Screen <--------------- ERROR due to loop in initialization)
    
    let btnSize:CGFloat = 56.0
    let btn1 = ClickableButton()
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    
    //happens only once:
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "Screen 1"
        view.backgroundColor = .brown // HexColor.GoldenColors.DarkKhaki
        
        self.navigationController?.delegate = self
    }
    
    //happens every time when shown:
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        setupLayout()
    }
    
    private func setupLayout() {
        //btn1:
        view.addSubview(btn1)
        btn1.setDefaultTitle(title: "▶") // ⏹ "▶" "■"
        btn1.apply(height: btnSize)
        btn1.apply(width: btnSize)
        btn1.applyDefaultStyle()
        if #available(iOS 11.0, *) {
            btn1.alignXCenter(to: view.safeAreaLayoutGuide.centerXAnchor)
        } else {
            // Fallback on earlier versions
        }
        if #available(iOS 11.0, *) {
            btn1.alignYCenter(to: view.safeAreaLayoutGuide.centerYAnchor)
        } else {
            // Fallback on earlier versions
        }
        btn1.clickHandler {
            let nextScreen = Screen2() //2nd Screen <--------------- LOCAL
            nextScreen.transitioningDelegate = self
            //self.present(self.nextScreen, animated: true, completion: nil)
            self.navigationController?.pushViewController(nextScreen, animated: true) //false for a custom transition
        }
    }
    
    //forward
    func animationController(forPresented presented: UIViewController,
                            presenting: UIViewController, source: UIViewController) ->
        UIViewControllerAnimatedTransitioning? {
            return transition
    }
    
    //backward
    func animationController(forDismissed dismissed: UIViewController) ->
        UIViewControllerAnimatedTransitioning? {
            return nil
    }
}

//FadePresentAnimator.swift

//FadePresentAnimator.swift
import UIKit


class FadePresentAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    
    let duration = 2.0
    var presenting = true
    var originFrame = CGRect.zero
    
    var dismissCompletion: (()->Void)?
    
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return duration
    }
    
    //Setting the transition’s context
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        
        //Adding a fade transition
        let containerView = transitionContext.containerView
        let toView = transitionContext.view(forKey: .to)!
        containerView.addSubview(toView)
        toView.alpha = 0.0
        UIView.animate(withDuration: duration,
                    animations: {
                        toView.alpha = 1.0
        },
                    completion: { _ in
                        transitionContext.completeTransition(true)
        }
        )
    }        
}

//ClickableButton.swift

//ClickableButton.swift
import UIKit

class ClickableButton: UIButton {
    
    @objc func setDefaultTitle(title text: String = "Button", color textColor: UIColor = .black) {
        self.setTitle(text, for: .normal)
        self.setTitleColor(textColor, for: .normal)
    }
    
    //Adding a closure as a target to a UIButton:
    var action: (() -> Void)?
    
    @objc func triggerClickHandler() { action?() }
    
    /*
    Use inside UIViewController:
        btn.clickHandler {
            self.present(self.secondVC, animated: true, completion: nil)
        }
    */
    func clickHandler(_ action: @escaping () -> Void) {
        self.action = action
        self.addTarget(self, action: #selector(ClickableButton.triggerClickHandler), for: .touchUpInside)
    }
    
    @objc func applyDefaultStyle(bgColor: UIColor = .white) {
        self.translatesAutoresizingMaskIntoConstraints = false
        self.backgroundColor = bgColor
        self.setTitleColor(.black, for: .normal)
        self.layer.cornerRadius = 4
        self.layer.shadowColor = UIColor.black.cgColor
        self.layer.shadowOffset = CGSize(width: 0, height: 2)
        self.layer.shadowRadius = 5
        self.layer.shadowOpacity = 1
        //btn.layer.masksToBounds = true
        self.titleEdgeInsets.left = 8
        self.titleEdgeInsets.right = 8
        self.titleEdgeInsets.top = 4
        self.titleEdgeInsets.bottom = 4
    }
}

Based on another tutorial I have created such a struct which uses CATransition:

struct Transitions {
    
    static func setTransitionTo(view: UIView!, from: CATransitionSubtype = .fromRight) {
        let transition = CATransition()
        transition.duration = 0.5
        transition.type = CATransitionType.push
        transition.subtype = from  //CATransitionSubtype.fromRight
        transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
        view.window!.layer.add(transition, forKey: kCATransition)
    }
    
    static func setFadeTransitionTo(view: UIView!) {
        //let transition = SKTransition.fade(withDuration: 0.5)
        let transition = CATransition()
        transition.duration = 0.5
        transition.type = CATransitionType.fade
        transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
        view.window!.layer.add(transition, forKey: kCATransition)
    }

}

Usage:

topBtn.clickHandler {
        let secondVC = SecondVC() //2nd Screen
        secondVC.transitionId = 1
        Transitions.setTransitionTo(view: self.view, from: .fromBottom)
        self.present(secondVC, animated: false, completion: nil)
    }

Now I have idea to combine UIViewControllerAnimatedTransitioning protocol with CATransition... but I don't know how...

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    let containerView = transitionContext.containerView
    let transition = CATransition()
    ....
}

At this point. i woul make one comment - this is getting very complicated for what you initially said you wanted to do.

Don't start messing around with transitions and animations until you have got the basic design sorted out.

1 Like
  1. Constraints - I don't know why but I absolutely cannot use GUI for them - I constantly have terrible ugly results...
  2. Source Control
  3. I like to see and to read my code (90% of my professional life I use Visual Studio Code for HTML/CSS/JavaScript)
  1. Learn constraints - that way you can use Interface Builder to preview your layouts on different devices without having to continually run, check, alter code.
  2. Storyboards are text files, they work in source control.
  3. My experience is that using visual designers gets things done without creating a mountain of code that could contain hard to trace bugs

Hi Joanna
Thank you for your comments.

this is getting very complicated for what you initially said you wanted to do.

From my point of view it's not getting complex so far:
import UIKit

class Screen3: UIViewController, UINavigationControllerDelegate {
    
    //happens only once
    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "Second"
        view.backgroundColor = .blue
        
        self.navigationController?.delegate = self
    }
    
    //happens every time when shown
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        setupLayout()
    }
    
    private func setupLayout() {
        //Look and feel
    }
}

As I mentioned in the topic - I would like to create (I want to learn how to create) different ViewControllers programmatically (coding manually without Visual Designers) and also to add custom transition between them (now I already know the difference between push and pop). And if many people uses Dashboard with ViewControllers which they placed by drag-and drop method, why I cannot make the same structure only without Dashboard ?

  1. Learn constraints - that way you can use Interface Builder to preview your layouts on different devices without having to continually run, check, alter code.

Yes. I learn them and I created my own UIView extension for convenient work with them.
Some wrappers for:
leadingAnchor.constraint(equalTo: leading).isActive = true //left
topAnchor.constraint(equalTo: top).isActive = true //top
trailingAnchor.constraint(equalTo: trailing).isActive = true //right
bottomAnchor.constraint(equalTo: bottom).isActive = true //bottom

It seems like your questions are more about app design rather than how to use the Swift language, which is what these groups are about.

You might get better answers in Apple's developer discussion groups or, even, on Stack Overflow.

1 Like


Hi everyone. Thank you for your help and patience.
I found answers on my questions:

  1. How to create a complex navigation with loop (reference on the first screen - root ViewController) - I couldn't understand how the Navigation Stack works in this situation - now I understood - each layer of the Navigation Stack is a new instance of the classes.

  2. When I created such a "loop" navigation I obtained the Stack Overflow Error - Error Thread 1: EXC_BAD_ACCESS (code=2,…) since a new instance was a member of the previous instance.

The simplest way to fix this is to make nextScreen initialized lazily "on demand" by replacing this line:
lazy var nextScreen = RootVC()
Or to create nextScreen variable right before the transition

  1. I learned and understood the difference between .present() and .pushViewController()

  2. Custom navigation transitions - very complex things but now I know about UIViewControllerTransitioningDelegate, UINavigationControllerDelegate

  3. Sorry please when I asked this question I didn't understand the complexity of the question. Thank you for your patience and understanding

Terms of Service

Privacy Policy

Cookie Policy