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.
UINavigationController
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.