Get back parentViewController - swift

I have a PhoneViewController : UIViewController
let phonePage = storyboard.instantiateViewControllerWithIdentifier("phoneViewController") as! PhoneViewController
I am presenting it from 2 different controllers.
// UIViewController1
self.navigationController?.pushViewController(phonePage, animated: true)
// UIViewController2
self.presentViewController(phonePage.embedInNavController(), animated: true, completion: nil)
I would like to have a have to detect which controller was its parent. How would I be able to do that?

you can try this code:-
if let wd = self.window {
var vc = wd.rootViewController
if(vc is UINavigationController){
vc = (vc as UINavigationController).visibleViewController
}
if(vc is YourViewController){
//your code
}
}

The short answer is :
if let navController = self.navigationController {
return navController.viewControllers[navController.viewControllers.count - 1]
// take care if count <= 1
else {
return self.parent
}
But is this what you really looking for ? What behavior are you trying to implement based on who is his parent ?
I don't have the answer to this question but you should make your code readable. Let me explain by an example :
let phonePage = storyboard.instantiateViewControllerWithIdentifier("phoneViewController") as! PhoneViewController
// Option 1
self.navigationController?.pushViewController(phonePage, animated: true)
phonePage.mode = PhonePageMode.list
// Option 2
self.presentViewController(phonePage.embedInNavController(), animated: true, completion: nil)
phonePage.mode = PhonePageMode.grid
// In your PhoneViewController class
switch self.mode {
case .list: // present as a list
case .grid: // present as a grid
}
is more readable than :
let phonePage = storyboard.instantiateViewControllerWithIdentifier("phoneViewController") as! PhoneViewController
// Option 1
self.navigationController?.pushViewController(phonePage, animated: true)
// Option 2
self.presentViewController(phonePage.embedInNavController(), animated: true, completion: nil)
// In your PhoneViewController class
guard let parent = self.parentViewController else { return }
if parent is ThisClassWhichWantsAList {
// present as list
} else if parent is ThisOtherClassWhichWantsAGrid {
// present as grid
}
If you want to use its parent as a condition to do things differently, you'd rather use an additional attribute. Your futur self will be thankful.

if your'r view controller is pushed from navigationViewController than parent class in parentViewController available and if presented than in presentationController available.
You can check in this way:
if let parentVC = self.navigationController.parentViewController{
//parentVC.className
}
if let presentedVC = self.navigationController?.presentationController{
//presentedVC.className
}

So you basically have to check if the view controller in the navigation controller stack, at the index of count - 1 is the kind of view controller that you are looking for.
Short version:
if navigationController.viewControllers[navigationController.viewControllers.count - 1].isKind(of: TheVCYouAreLooking for.self) {
print("it is")
} else {
print("it is NOT")
}
Long version from a playground:
//These are your two view controllers
class FirstVCClass: UIViewController {}
class SecondVCClass: UIViewController {}
let vc = FirstVCClass()
let secondVC = SecondVCClass()
//Create a navigationcontroller and add the first VC in the stack
let navigationController = UINavigationController()
navigationController.setViewControllers([vc], animated: true)
//Now push the secondVC
vc.navigationController?.pushViewController(secondVC, animated: true)
//The last vc in the stack is the one you've just pushed
print(navigationController.viewControllers.last!)
// Now check
if navigationController.viewControllers[navigationController.viewControllers.count - 1].isKind(of: FirstVCClass.self) {
//The view controller that is before you current vc in the stack is of the class
print("it is")
} else {
print("it is NOT")
}

Related

How to check after popping a ViewController, the ViewController on top of the stack, is a particular one?

Suppose I am currently on BaseViewController . I can approach this view controller class from 2 other view controllers, say A and B. So when I pop BaseViewController how do I check if A or B is present on top of the stack? Here is my code below(inside BaseViewController):
func goToAOrBViewController {
// If after popping viewController is A do this
navigationController.popViewController(animated: true)
// Id after popping viewController is B do this
// Instantiate B and push it
}
There are many methods to do this
First and easiest method
func visibleViewController() -> UIViewController? {
let appDelegate = UIApplication.shared.delegate
if let window = appDelegate!.window {
return window?.visibleViewController
}
return nil
}
func goToAOrBViewController {
// If after popping viewController is A do this
navigationController.popViewController(animated: true)
if visibleViewController() == viewControllerA{
//Do stuff for A
}else{
//Do stuff for B
}
}
Second and obvious method
let topController = UIApplication.sharedApplication().keyWindow?.rootViewController
if topController == viewControllerA {
//Do stuff for A
}else{
//Do stuff for B
}
You can pop and push view controller at the same time.
not tested:
navigationController?.viewControllers.removeLast()
let topVC = navigationController?.viewControllers.last
if topVC == A {
navigationController?.viewControllers.append(A)
navigationController?.setViewControllers(A, animated: false)
} else {
navigationController?.viewControllers.append(B)
navigationController?.setViewControllers(B, animated: false)
}
Inside BaseViewController class:
func goToAOrBViewController() {
guard let navigationController = self.navigationController else { return }
navigationController.popViewController(animated: false)
guard let viewController = navigationController.topViewController else { return }
if viewController is ViewControllerA {
// Do something for A
} else if viewController is ViewControllerB {
// Do Something for B
}
// You need not put else-if case if you are sure BaseVC is reachable from A and B only (just use else)
}

Swift: pass data to first child of NavigationController instantiate with storyboardID

I want to pass data to the first viewController which is embeded in navigationController.
To access this navigation controller it has a storyBoardID, I arrive at instantiate navigationController but I can not pass him data,
Here is my code:
extension UINavigationController {
func dismissAndPresentNavigationController(from storyboard: UIStoryboard?, identifier: String) {
guard let navigationController = storyboard?.instantiateViewController(withIdentifier: identifier) as? UINavigationController else { return }
print("OK")
if let nav = navigationController.navigationController?.viewControllers.first as? ChatBotViewController{
print("OK2")
}
self.dismiss(animated: false, completion: nil)
self.present(navigationController, animated: true, completion: nil)
}
}
The identifier that I put in parameter is the storyBoardID of the navigation controller.
How to transmit data to the first controller of navigationcontroller?
SOLUTION:
extension UINavigationController {
func dismissAndPresentNavigationController(from storyboard: UIStoryboard?, identifier: String, with fittoBottle: FittoBottle) {
guard let navigationController = storyboard?.instantiateViewController(withIdentifier: identifier) as? UINavigationController else { return }
if let nav = navigationController.viewControllers.first as? ChatBotViewController{
nav.fittoBottle = fittoBottle
}
self.dismiss(animated: false, completion: nil)
self.present(navigationController, animated: true, completion: nil)
}
After instantiating the navigation controller from the storyboard, you will be able to access the root view controller via navigationController.viewControllers.first.
guard let navigationController = storyboard?.instantiateViewController(withIdentifier: identifier) as? UINavigationController else { return }
if let chatBotViewController = navigationController.viewControllers.first as? ChatBotViewController {
chatBotViewController.fittoBottle = fittoBottle
}
self.dismiss(animated: false, completion: nil)
self.present(navigationController, animated: true, completion: nil)
To communicate between view controllers in an iOS app, the best way is using protocol (delegate) or Notification. In you case, extending an UINavigationController doesn't sound a good idea, because you shouldn't relay on extension methods to instance view controller and then passing any data to it, and as an extension method, it's not the responsibility for an UINavigationController to take care about ChatBotViewController or any other instanced controllers.
For my suggestion, in anywhere you want to show the ChatBotViewController, in your storyboard, create a presenting modal segue to the ChatBotViewController (which is embedded in an UINavigationController), and use performSegue(withIdentifier:sender:) to initiate the navigation controller, and override prepare(for:sender:) to set the data you want to pass in your ChatBotViewController.
Here's some codes for explaining:
import UIKit
struct FittoBottle {
}
class ChatBotViewController: UIViewController {
var fittoBottle = FittoBottle()
}
class ViewController: UIViewController {
func showChatController() {
/*
If there is any controller is presented by this view controller
or one of its ancestors in the view controller hierarchy,
we will dismiss it first.
*/
if presentedViewController != nil {
dismiss(animated: true) {
self.showChatController()
}
return
}
// Data that will be sent to the new controller
let fittoBottle = FittoBottle()
// ChatBotViewControllerSegue is the segue identifier set in your storyboard.
performSegue(withIdentifier: "ChatBotViewControllerSegue", sender: fittoBottle)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard let navigationController = segue.destination as? UINavigationController else {
return
}
guard let chatBotController = navigationController.viewControllers.first as? ChatBotViewController else {
return
}
// Get the data from sender, if not found, create it there.
// Or if you don't pass it through sender, you can specify it here.
let fittoBottle = sender as? FittoBottle ?? FittoBottle()
chatBotController.fittoBottle = fittoBottle
}
}

Checking multiple conditions in a switch statement

I've got a Swift function that uses a switch statement to act on various enum cases but I also need to check for another condition at the same time. The best solution I can come up with is to use a nested switch (which works) but I was wondering if there was a more elegant (Swifty?) way?
The code is pretty self-explanatory:
func transitionTo(scene: Scene, transition: SceneTransitionType) -> Observable<Void> {
let subject = PublishSubject<Void>()
let viewController = scene.viewController()
switch viewController {
case is UISplitViewController:
switch transition {
case .root:
window.rootViewController = viewController
subject.onCompleted()
default:
fatalError("UISplitViewController can only be exist at the root of the view hierachy")
}
default:
switch transition {
case .root:
window.rootViewController = viewController
subject.onCompleted()
case .push(let animated):
guard let nc = currentViewController as? UINavigationController else {
fatalError("Unab;e to push a view controlled without an existing navigation controller")
}
_ = nc.rx.delegate // one-off sub to be notified when push complete
.sentMessage(#selector(UINavigationControllerDelegate.navigationController(_:didShow:animated:)))
.map { _ in }
.bind(to: subject)
nc.pushViewController(viewController, animated: animated)
currentViewController = SceneCoordinator.topViewControllerInStackWith(root: viewController).first!
case .modal(let animated):
currentViewController.present(viewController, animated: animated) {
subject.onCompleted()
}
currentViewController = SceneCoordinator.topViewControllerInStackWith(root: viewController).first!
}
This is the best possible way as per my knowledge with single switch statement.
func transitionTo(scene: Scene, transition: SceneTransitionType) -> Observable<Void> {
let subject = PublishSubject<Void>()
let viewController = scene.viewController()
switch transition {
case .root:
window.rootViewController = viewController
subject.onCompleted()
case .push(let animated):
if viewController is UISplitViewController {
fatalError("UISplitViewController can only be exist at the root of the view hierachy")
return
}
guard let nc = currentViewController as? UINavigationController else {
fatalError("Unab;e to push a view controlled without an existing navigation controller")
}
_ = nc.rx.delegate // one-off sub to be notified when push complete
.sentMessage(#selector(UINavigationControllerDelegate.navigationController(_:didShow:animated:)))
.map { _ in }
.bind(to: subject)
nc.pushViewController(viewController, animated: animated)
currentViewController = SceneCoordinator.topViewControllerInStackWith(root: viewController).first!
case .modal(let animated):
if viewController is UISplitViewController {
fatalError("UISplitViewController can only be exist at the root of the view hierachy")
return
}
currentViewController.present(viewController, animated: animated) {
subject.onCompleted()
}
currentViewController = SceneCoordinator.topViewControllerInStackWith(root: viewController).first!
}
}

Push a viewcontroller if it is not present in the navigation stack

I want to check whether a viewcontroller is present in a navigation stack or not. If it is present, I need to pop it otherwise I need to push it to the navigation stack. I have tried the following code. If it is not present, control is transfering to the else block but I'm unable to navigate to the screen. Please help me
for aViewController in viewControllers! {
if aViewController is TabProfileViewController {
self.navigationController?.popToViewController(aViewController, animated: true)
}
else {
let lvc = self.storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as? LoginViewController
self.navigationController?.pushViewController(lvc!, animated: true)
}
}
You're checking it in every loop, so if one time the first condition is true maybe it can become false in next iteration, so it will pop and push. try following code:
if let viewController = viewControllers?.first(where: { $0 is TabProfileViewController }) {
navigationController?.popToViewController(viewController, animated: true)
} else {
let lvc = storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as? LoginViewController
navigationController?.pushViewController(lvc!, animated: true)
}
Hope this code is work for you.
if navigationController != nil && !(navigationController?.topViewController is YOURCONTROLLER) {
for aViewController in (navigationController?.viewControllers)!
{
if aViewController is YOURCONTROLLER {
//Your controller found
}else{
//push using navigation
}
}
}
This is Another way to do that:
if arrViewController != nil && !(arrViewController?.topViewController is TabProfileViewController) {
for aViewController in (arrViewController?.viewControllers)! {
if aViewController is Dashboard {
_ = self.navigationController?.popToViewController(aViewController, animated: true)
break
}
}
}else{
let lvc = self.storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as? LoginViewController
self.navigationController?.pushViewController(lvc!, animated: true)
}

Back when current view controller is not presented by segue. Swift

I am trying to present VC2 from VC1 without using segue. It works. Then, I tried to use self.navigationController?.popViewControllerAnimated(true) to back but it does not work. I am wondering what is the code I should use to back from VC2 to VC1. Below code is in appDelegate.
AppDelegate
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let VC2 = storyboard.instantiateViewControllerWithIdentifier("VC2") as! VC2
let navController = UINavigationController(rootViewController: VC2)
self.topViewController()!.presentViewController(navController, animated: false, completion: nil)
func topViewController(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
if let MMDrawers = base as? MMDrawerController {
for MMDrawer in MMDrawers.childViewControllers {
return topViewController(MMDrawer)
}
}
if let nav = base as? UINavigationController {
return topViewController(nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(presented)
}
return base
}
VC2
#IBAction func backButtonTapped(sender: AnyObject) {
print(self.navigationController?.viewControllers) // print([<MyAppName.VC2: 0x12f147200>])
self.navigationController?.popViewControllerAnimated(true)
}
Here is the Flow of your navigation :
Current Screen - Presenting A New Screen (Which itself embed within a navigation controller with vc2) so Popviewcontroller won't work .
If you present any viewcontroller then popviewcontroller wont work rather use dismissviewcontroller to come out previous screen .
Use This :
self.dismissViewControllerAnimated(true, completion: {})
Solved!
Thanks.