简体   繁体   中英

What is the best way to segue between UIViewController behind a presented UIViewController

I want to present a fullscreen ViewController "A" to cover our loading process across ViewControllers "B" AND "C", or in other words, 1) I present ViewController A from ViewController B, 2) segue from ViewController B to ViewController C while ViewController A is showing, 3) dismiss the ViewController A into ViewController C that ViewController B segued into.

If I push from the presenter ViewController B, the presented ViewController A will disappear as well. So my question is, what's the best way to change the ViewControllers B and C in the background, while another one (ViewController A) is presented on top of them?

矿石

Thanks.

You can do this in two ways:

1.Using a navigation controller

if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "YourVCName") as? JunctionDetailsVC {
    if let navigator = navigationController {
        navigator.pushViewController(viewController, animated: false)
    }
}

2.Present modally from you initial VC

if let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "YourVCName") as? LoginVC
{
    present(vc, animated: false, completion: nil)
}

Remember to not animate because you said that users shouldn't notice the transition.

Without knowing anything else about your app, I think you'd be better off redesigning the flow and User Experience, but here is one approach to do what you want.

  • We start with VC-B as the root VC of a UINavigationController
  • On button-tap, we add a "cover view" to the navigation controller's view hierarchy
  • We initially position that view below the bottom of the screen
  • Animate it up into view
  • Make desired changes to VC-B's view
  • Instantiate and Push VC-C
  • Do what's needed to setup the UI on VC-C
  • Animate the cover view down and off-screen
  • Remove the cover view from the hierarchy

And here's the code. Everything is done via code - even the initial Nav Controller setup - so No Storyboard needed (go to Project General Settings and delete anything in the Main Interface field).


AppDelegate.swift

//
//  AppDelegate.swift
//
//  Created by Don Mag on 8/30/19.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        window = UIWindow(frame: UIScreen.main.bounds)

        // instantiate a UINavigationController
        let navigationController = UINavigationController();

        // instantiate a NavBViewController
        let vcB = NavBViewController();

        // set the navigation controller's first controller
        navigationController.viewControllers = [ vcB ];

        self.window?.rootViewController = navigationController;
        self.window?.makeKeyAndVisible()

        return true

    }

    func applicationWillResignActive(_ application: UIApplication) {
    }
    func applicationDidEnterBackground(_ application: UIApplication) {
    }
    func applicationWillEnterForeground(_ application: UIApplication) {
    }
    func applicationDidBecomeActive(_ application: UIApplication) {
    }
    func applicationWillTerminate(_ application: UIApplication) {
    }
}

ViewControllers.swift - contains CoverView, NavBViewController and NavCViewController classes

//
//  ViewControllers.swift
//
//  Created by Don Mag on 8/30/19.
//

import UIKit

class CoverView: UIView {

    let theSpinner: UIActivityIndicatorView = {
        let v = UIActivityIndicatorView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.style = .whiteLarge
        return v
    }()

    let theLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.textAlignment = .center
        v.textColor = .white
        v.text = "Please Wait"
        return v
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    func commonInit() -> Void {

        backgroundColor = .blue

        // add an Activity Spinner and a label
        addSubview(theSpinner)
        addSubview(theLabel)

        NSLayoutConstraint.activate([
            theLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
            theLabel.centerYAnchor.constraint(equalTo: centerYAnchor),

            theSpinner.centerXAnchor.constraint(equalTo: theLabel.centerXAnchor),
            theSpinner.bottomAnchor.constraint(equalTo: theLabel.topAnchor, constant: -100.0),
            ])

        theSpinner.startAnimating()
    }

}

class NavBViewController: UIViewController {

    // this view will be added or removed while the "coverView" is up
    let newViewToChange: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .red
        v.textColor = .white
        v.textAlignment = .center
        v.text = "A New View"
        return v
    }()

    let theLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.textAlignment = .center
        v.text = "View Controller B"
        return v
    }()

    let theButton: UIButton = {
        let v = UIButton()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.setTitle("Tap Me", for: .normal)
        v.setTitleColor(.blue, for: .normal)
        v.setTitleColor(.lightGray, for: .highlighted)
        return v
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .yellow

        // add a button and a label
        view.addSubview(theButton)
        view.addSubview(theLabel)

        NSLayoutConstraint.activate([

            theButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40.0),
            theButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),

            theLabel.topAnchor.constraint(equalTo: theButton.bottomAnchor, constant: 40.0),
            theLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),

            ])

        theButton.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
    }

    @objc func didTap(_ sender: Any) {

        // get
        //      the neavigation controller's view,
        if let navView = navigationController?.view {
            // create a "cover view"
            let coverView = CoverView()
            coverView.translatesAutoresizingMaskIntoConstraints = false

            // add the coverView to the neavigation controller's view
            navView.addSubview(coverView)

            // give it a tag so we can find it from the next view controller
            coverView.tag = 9999

            // create a constraint with an .identifier so we can get access to it from the next view controller
            let startConstraint = coverView.topAnchor.constraint(equalTo: navView.topAnchor, constant: navView.frame.height)
            startConstraint.identifier = "CoverConstraint"

            // position the coverView so its top is at the bottom (hidden off-screen)
            NSLayoutConstraint.activate([
                startConstraint,
                coverView.heightAnchor.constraint(equalTo: navView.heightAnchor, multiplier: 1.0),
                coverView.leadingAnchor.constraint(equalTo: navView.leadingAnchor),
                coverView.trailingAnchor.constraint(equalTo: navView.trailingAnchor),
                ])

            // we need to force auto-layout to put the coverView in the proper place
            navView.setNeedsLayout()
            navView.layoutIfNeeded()

            // change the top constraint constant to 0 (top of the neavigation controller's view)
            startConstraint.constant = 0

            // animate it up
            UIView.animate(withDuration: 0.3, animations: ({
                navView.layoutIfNeeded()
            }), completion: ({ b in
                // after animation is complete, we'll change something in this VC's UI
                self.doStuff()
            }))

        }
    }

    func doStuff() -> Void {

        // if newView is already there, remove it
        // else, add it to the view
        // this will happen *while* the coverView is showing
        if newViewToChange.superview != nil {
            newViewToChange.removeFromSuperview()
        } else {
            view.addSubview(newViewToChange)
            NSLayoutConstraint.activate([
                newViewToChange.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                newViewToChange.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                newViewToChange.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                newViewToChange.heightAnchor.constraint(equalToConstant: 80.0),
                ])
        }

        // simulate it taking a full second
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            // instantiate and push the next VC
            // again, this will happen *while* the coverView is showing
            let vc = NavCViewController()
            self.navigationController?.pushViewController(vc, animated: false)
        }

    }

}

class NavCViewController: UIViewController {

    let theLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.textAlignment = .center
        v.text = "View Controller C"
        return v
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .green

        // add a label
        view.addSubview(theLabel)

        NSLayoutConstraint.activate([

            theLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40.0),
            theLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),

            ])

        // do whatever else needed to setup this VC

        // simulate it taking 1 second to setup this view
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            // get
            //      the neavigation controller's view,
            //      the view with tag 9999 (the "coverView")
            //      the top constraint of the coverView
            if let navView = self.navigationController?.view,
                let v = navView.viewWithTag(9999),
                let c = (navView.constraints.first { $0.identifier == "CoverConstraint" }) {
                    // change the top constant of the coverView to the height of the navView
                    c.constant = navView.frame.height
                    // animate it "down"
                    UIView.animate(withDuration: 0.3, animations: ({
                        navView.layoutIfNeeded()
                    }), completion: ({ b in
                        // after animation is complete, remove the coverView
                        v.removeFromSuperview()
                    }))

            }

        }

    }

}

When you run it, it will look like this:

在此处输入图片说明

Tapping "Tap Me" will slide-up a "cover view" and a new red view will be added (but you won't see it):

在此处输入图片说明

The sample has 2-seconds worth of delay, to simulate whatever your app is doing to set up its UI. After 2-seconds, the cover view will slide down:

在此处输入图片说明

Revealing the pushed VC-C (confirmed by the Back button on the Nav Bar).

Tapping Back takes you back to VC-B, where you see the new red view that was added:

在此处输入图片说明

So, by animating the position of the cover view, we emulate the use of present() and dismiss() , and allow the push to take place behind it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM