How would I dismiss a modal View Controller and also its parent that was pushed?
self.presentingViewController?.dismiss(animated: true, completion: {
self.parent?.navigationController?.popViewController(animated: true)
})
This only dismisses the top modal.
You can go another way.
First, you have UINavigationController in your Home view. So you can write an extension that will allow you to go to the controller, which is in the navigation stack.
I tried making an implementation like this:
extension UINavigationController {
func routingPath(for controller: UIViewController) -> [UIViewController] {
guard viewControllers.contains(controller) else {
return []
}
var result: [UIViewController] = []
for previousController in viewControllers {
result.append(previousController)
if controller === previousController {
break
}
}
return result
}
func performNavigation(toPrevious controller: UIViewController,
shouldDismissModals: Bool = true) {
let previousViewControllers = routingPath(for: controller)
guard !previousViewControllers.isEmpty else { return }
viewControllers = previousViewControllers
if shouldDismissModals, let _ = controller.presentedViewController {
controller.dismiss(animated: true, completion: nil)
}
}
}
Then you can make a special method for UIViewController:
extension UIViewController {
func returnBackIfPossible(to controller: UIViewController? = nil,
shouldDismissModals: Bool = true) {
navigationController?.performNavigation(toPrevious: controller ?? self,
shouldDismissModals: shouldDismissModals)
}
}
Then you need to pass a reference for a Home controller to all of the next controllers (or store it somewhere). Next, when needed, you can call to homeViewController?.returnBackIfPossible() method, which will close all modals and reset navigation stack.
What is non-modal parent exectly?
Is it a view controller pushed by the navigation controller?
If then, you must pop that view controller from navigation controller.
Related
I'm looking for a way to dismiss all presented view controllers, and THEN present a view controller.
In my app, there's a main page, and the user can then click on a button that takes them to another page, and then they can click a button to submit some information. After they click to submit the evidence, I want to close all of the view controllers (so they get to the main page), and then I want to present a "Congratulations" screen. Ideally, this would be what I want to do:
self.view.window?.rootViewController?.dismiss(animated: true, completion: {
let congratsPopup = K.mainStoryBoard.instantiateViewController(withIdentifier: "congratsController") as! CongratsController
self.view.window?.rootViewController!.present(congratsPopup, animated:true, completion:nil)
})
Any ideas?
Cheers,
Josh
You can dismiss all viewcontrollers with below code block. In the completion block you can get the topViewController and you can present new viewController over topViewController. I also wrote down an extension for get the topViewController on the window.
UIApplication.shared.keyWindow?.rootViewController?.dismiss(animated: true, completion: { [weak self] in
// Get Top Controller With Extension
let topController = UIApplication.topViewController()
// Pressent New Controller over top controller
let congratsPopup = K.mainStoryBoard.instantiateViewController(withIdentifier: "congratsController") as! CongratsController
topController?.present(congratsPopup, animated: true, completion: nil)
})
Get Top View Controller Extension
extension UIApplication {
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}
}
I have a custom action sheet viewController. Which is presented modally on the current top view controller. Like this:
//MARK: Static Func
static func initViewController() -> CustomActionSheetViewController {
let customActionSheetViewController = CustomActionSheetViewController(nibName: "CustomActionSheetViewController", bundle: nil)
return customActionSheetViewController
}
func presentViewController<T: UIViewController>(viewController: T) {
DispatchQueue.main.async {
if let topViewController = UIApplication.getTopViewController() {
viewController.modalTransitionStyle = .crossDissolve
viewController.modalPresentationStyle = .overCurrentContext
topViewController.present(viewController, animated: true, completion: nil)
}
}
}
// MARK: UIApplication extensions
extension UIApplication {
class func getTopViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return getTopViewController(base: nav.visibleViewController)
} else if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
return getTopViewController(base: selected)
} else if let presented = base?.presentedViewController {
return getTopViewController(base: presented)
}
return base
}
}
And I am dismissing it like this:
#objc func dismissViewController() {
DispatchQueue.main.async {
if let topViewController = UIApplication.getTopViewController() {
topViewController.dismiss(animated: true, completion: nil)
}
NotificationCenter.default.removeObserver(self)
}
}
It's working perfectly fine. I have added the notification observer in my customTabbarController, to dismiss the action sheet if user tap on some another tabbar button like this:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
// print("Selected view controller", viewController)
// print("index", tabBarController.selectedIndex )
let tabbarNotiKey = Notification.Name(rawValue: "TabbarNotiKey")
NotificationCenter.default.post(name: tabbarNotiKey, object: nil, userInfo: nil)
}
The action sheet is right now presenting on Home tab > Profile (by push) > Action sheet (by modal). So if I tap on Home tab again it will dismiss the action sheet viewController and come back to Home perfectly. But if I tap on some other tabbar button rather than home and come back home, it shows a black screen. What I am missing here? Any suggestions would be highly appreciable.
I guess your code calls dismissViewController() twice.
When you press other tab, it removes the action sheet
And then you click home tab and it calls dismissViewController again, and now it removes the homescreenVC
I have two view controller that opens modally. When the first VC closed then the second should be opened. But when I close the first one, the second one is not displayed at all.
what is the problem?
My code is:
self.dismiss(animated: true) {
let flowVC = LanguageFlowViewController()
self.present(flowVC, animated: true)
}
You need reference of view controller from where you to present first view controller.
For example, you have view controller name as X, from there your first view controller A present. So you need reference of X to present B, because A will not be available in memory.
So when you try to present second view controller using self, it will do nothing.
So, for solution assign reference of X view controller to A. In class A, declare:
var refX: X?
While present A from X, set self to refX. Like:
var aVC = A() // This is temp, you need to instantiate your view controller here.
aVC.refX = self
self.present(aVC, animated: true, completion: nil)
Now, inside view controller A, when dismiss:
var bVC = B() // This is temp, you need to instantiate your view controller here.
self.dismiss(animated: true) {
if self.refX != nil {
self.refX?.present(bVC, animated: true, completion: nil)
}
}
I hope this will help you.
If you want to open modally ThirdViewController after Dismiss SecondViewController then you have to create protocol for it.
Like we have three UIViewController(FirstViewController,SecondViewController and
ThirdViewController)
Step 1: We need to create a protocol in SecondViewController as given below code.
protocol DismissedViewProtocal {
func dismissView()
}
class SecondViewController: UIViewController {
var delegate: DismissedViewProtocal?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func dismissSecondViewAction(_sender : AnyObject) {
dismiss(animated: true) {
self.delegate?.dismissView()
}
}
Step 2: You need to add protocol in FirstViewController as given below
class FirstViewController: UIViewController, DismissedViewProtocal {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func anAction(_sender : AnyObject){
let flowVC = self.storyboard?.instantiateViewController(withIdentifier:"SecondViewController") as? SecondViewController
secondVC?.delegate = self
self.present(secondVC!, animated: true) {
}
}
func dismissView() {
let thirdVC = self.storyboard?.instantiateViewController(withIdentifier:"ThirdViewController")
self.present(thirdVC!, animated: true) {
}
}
}
Your are dismissing the current view controller by calling self.dismiss().
Therefore it is impossible for it to present anything anymore, since it is removed from the view hierarchy. As others have mentioned, try using the self.presentingViewController or self.navigationController (if it is on a navigationController) to present your new view.
However, if you need maximum flexibility create a delegate protocol. Create a protocol with a function presentForChild(viewController: UIViewController). Before your previous view presents the view from which the code in your question belongs to, give it a reference of the protocol.
Example:
protocol ChildPresentDelegate: class {
func presentForChild(vc: UIViewController)
}
class FirstController: UIViewController, ChildPresentDelegate {
func presentForChild(vc: UIViewController) {
present(vc, animated: true, completion: nil)
}
/**
other code
*/
func showControllerAsWasShownInTheQuestion() {
let controller = SecondController()
controller.delegate = self
present(controller, animated: true, completion: nil)
}
}
class SecondController: UIViewController {
weak var delegate: ChildPresentDelegate?
func dismissMySelf() {
self.dismiss(animated: true) {
delegate?.presentForChild(vc: LanguageFlowViewController())
}
}
}
How to present another view controller after dismiss from navigation controller in swift ?
I am using Navigation controller.
ViewController1.swift
func pushTo(viewController: UIViewController) {
let showLocalPwd = self.storyboard?.instantiateViewController(withIdentifier: "LocalPwdVC") as! LocalPwdVC
self.navigationController?.present(showLocalPwd, animated: true, completion: nil)
}
ViewController2.swift
#IBAction func btnVerify(_ sender: Any)
{
self.dismiss(animated: true, completion: {
let vc = self.storyboard.instantiateViewController(withIdentifier: "DataVC") as! DataVC
self.navigationController.pushViewController(vc, animated: true)
})
}
After dismissing the View Controller, it will not goes to next viewcontroller i.e. DataVC
If you want to present the view controller then create a protocol in your dismissViewController
protocol dismissViewController {
func presentCompletedViewController()
}
// Then create a delegate
var delegate = dismissViewController? = nil
// If you are using action to dismiss your ViewController then call delegate
#IBAction func YourButton(_ sender: Any) {
self.dismiss(animated: true) {
self.delegate!.presentCompletedViewController()
}
}
//And then call this in your main or homeViewController
class HomeViewController: UIViewController, dismissViewController {
func presentCompletedViewController(){
// enter code to present view controller
}
// And at last don't forget to call delegate
yourVc.delegate = self
you need to call this delegate in which you are presenting your dismissViewController
I have two views, V1 and V2. I want to "present" V2 when the add button is pressed on V1, and "pop" V2 off when the stop button is pressed, so that the original V1 is the top of the stack.
From what I have read, I need a separate view controller for V2. From the limited information I could find, I need V1's view controller to conform to V2's protocol, V2delegate. This is what I have, but it is not working:
ViewController1 with V1
class HomeController: UICollectionViewController, UICollectionViewDelegateFlowLayout, FormViewControllerDelegate {
let form = FormViewController()
func addTapped() {
form.delegate = self
let nav = UINavigationController(rootViewController: form)
navigationController?.present(nav, animated: true)
}
func popForm() {
navigationController?.popViewController(animated: true)
navigationController?.popToViewController(self, animated: true)
print("popped")
}
}
ViewController2 with V2
class FormViewController: UIViewController {
var delegate: FormViewControllerDelegate?
func stopTapped() {
print("pop it")
delegate?.popForm()
}
}
protocol FormViewControllerDelegate {
func popForm()
}
What am I doing wrong here?
In your VC2, Change to use this code
func stopTapped() {
print("pop it")
self.dismiss(animated: true, completion: nil)
}
Use this in ViewController1 to present FormViewController
func addTapped() {
let nav = UINavigationController(rootViewController: form)
self.present(nav, animated: true)
}
Within FormViewController when want to dismiss use this
func stopTapped() {
self.dismiss(animated: true)
}
You have presented the ViewController not pushed the ViewController, so what you need is to dismiss the Controller instead of pop the controller from navigation stack.
func popForm() {
navigationController?.dismiss(animated: true)
print("popped")
}
Better if you renamed the method name to dissmissForm instead of popForm.
You need to dismiss VC2 instead of pop. in self class as below :
func stopTapped() {
self.dismiss(animated: true, completion: { _ in })
}
When you are presenting any viewcontroller then you must use dismissViewController method to remove presented view controller. popViewController is used when you hqve push any viewcontroller.
When you use present then you have to use dismiss to remove that currently presented class in stack, when you dismiss it, your just next previous class will be in top of the stack. Thats all.. hope, it may helps you.
for pop a UIViewController you nee to push and not present. If you need to present a UIViewVontroller then on click on "X" you need to dismiss that viewController.
For push view controller
func addTapped() {
self.navigationController?.pushViewController(from, animated: true)
}
func stopTapped() {
self.navigationController?.popViewController(animated: true)
}
For presenting a view controller
func addTapped() {
self.present(from, animated: true, completion: nil)
}
func stopTapped() {
self.dismiss(animated: true, completion: nil)
}
You don't need to code for any protocol to push or present a UIViewController