Best way of managing View Controllers navigation - ios

I have the follow structure in my App:
When application first launches, it loads my initial view controller (VC A).
Then, it performs if user is logged in. If it's not, it presents another view controller (VC B).
From VC B, user logs in, and if it's an already registered user, it simply goes back to VC A (I call dismiss from VC B). If it's the first time the user logs in, then VC B calls a sequence of other view controllers, responsible for some kind of tutorial (say we have the sequence VC T1, VC T2 and VC T3) one calling the other.
When VC T3 is finished doing what it is supposed to do, it has to go back to VC A.
My question is how is the best way to do this.
Currently I am thinking of creating a segue back to the initial controller VC A directly from VC T3, but is there some kind of memory management problem on this?

The best way I believe is to use UINavigationController which has methods popToViewController(viewController: UIViewController, animated: Bool) and popToRootViewController(animated: Bool) among others
The view controller you need to pass to these methods you can instantiate from storyboard like this
var yourVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "youVCIdentifier")
EDIT: Sorry, forgot to mention that in this case you should of course add UINavigationController to your storyboard:
Editor -> Embed in -> Navigation Controller

Better make this in AppDelegate
if userLogined {
self.window?.rootViewController = loginedController
} else {
self.window?.rootViewController = notLoginedController
}

When you want to set your rootViewController from everywhere use it like:
OperationQueue.main.addOperation {
let tabBarController = self.storyboard?.instantiateViewController(withIdentifier: "TabBarController") as! UITabBarController
UIApplication.shared.keyWindow?.rootViewController = tabBarController
}

Related

assigning a custom destination to popToRootViewController swift 4

I am using navigationController?.popToRootViewController(animated: true) to dismiss my current view to the previous view. My view controller relationship looks like this.
VC1->VC2
VC1->VC3
VC3->VC2
Whenever the client is in VC2 I want to pop the navigation controller back to VC1. This works fine when the rootviewcontroller is set to VC1. However, when the client uses a segue from VC3 to enter VC2, the rootviewcontroller is set to VC3 and the navigation controller pops to VC3.
I tried to change the rootviewcontroller like this.
// set root view controller
let appdelegate = UIApplication.shared.delegate as! AppDelegate
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let VC1 = mainStoryboard.instantiateViewController(withIdentifier: "VC1") as! FirstViewController
appdelegate.window!.rootViewController = VC1
navigationController?.popToRootViewController(animated: true)
But this actually returns the viewcontroller to the root view controller (VC1) even before the line "navigationController?.popToRootViewController(animated: true)" is executed so there is no animation.
Is there any way to set the rootviewcontroller of a navigation controller without presenting the root view controller right away?
If you put appdelegate.window!.rootViewController = VC1, the stack of controllers has died, you only have in the stack an alone Controller, therefore you can't apply popToRootViewController.
If this is your required navigation, maybe, this post helps you:

How to show a specific VC , more like navigate to a stack of VC

So i've implemented universal link in my app and when pressing on a button I open my second app with :
UIApplication.shared.open(aUrl!)
also I'm getting the call in :
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {}
which is an alert.
The question is like the title says..how could I navigate to a specific VC ,(from the FirstApp to the Second which i opened it with universal link), more like mapping a navigation controller stack .
I've writed something like :
if let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MyAdsController") as? MyAdsViewController {
if let window = self.window, let rootViewController = window.rootViewController {
var currentController = rootViewController
while let presentedController = currentController.presentedViewController {
currentController = presentedController
}
currentController.present(controller, animated: true, completion: nil)
}
}
but it's an ugly representation, i need something like the hole navigation stack...
And also , can i make a helper class for this if i want to handle more with universal link? Any suggestion are appreciated.
You need to use the .viewControllers property of your UINavigationController you can do this using setViewControllers(_:animated:) method or modifying directly .viewControllers property where rootViewController will be the viewControllersArray[0] and topViewController will be viewControllersArray[count-1]
This property description and details can be founded in the UINavigationController documentation
viewControllers
Property
The view controllers currently on the navigation stack.
Declaration
SWIFT
var viewControllers: [UIViewController]
Discussion The root view controller is at index 0 in the array, the back view controller is at index n-2, and the top controller is at
index n-1, where n is the number of items in the array.
Assigning a new array of view controllers to this property is
equivalent to calling the setViewControllers:animated: method with the
animated parameter set to false.
example
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let firstViewController = storyboard.instantiateViewController(withIdentifier: "FirstViewController") as? FirstViewController
let secondViewController = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController
let thirdViewController = storyboard.instantiateViewController(withIdentifier: "ThirdViewController") as? ThirdViewController
self.navigationController?.setViewControllers([firstViewController,secondViewController,thirdViewController], animated: false)
Alright so this is less of a Universal Link question and more of a navigation preference. When you are passed a universal link in your continueUserActivity function you should parse it and navigate accordingly from your app delegate. You should really switch your Universal Link handling to Branch since they will handle this for you for free and they pass metadata through a callback instead of link parameters which will make your links more powerful. Anyways...
Start with a UINavigationViewController
This consists of having a rootViewController which should be an instance of your main page. Store this navigation view controller as an instance variable so your app is only dealing with one Navigation view controller on launch. If you're using a storyboard then you can just just get reference to that navigation view controller:
self.nav = self.window?.rootViewController as? UINavigationViewController
Pushing vs presenting a View Controller onto Nav
When you receive a link and determined that you need to bring that user to a new page, you should decide whether to push or present the VC.
Pushing
Pushing is necessary if you would like to maintain that view as part of a stack. For example, if you want to show the user a pair of shoes, you may want to take them into the shoes category, then present them with the shoe detail controller. In this case you could take the user to the home page and push the proper navigation stack onto your UINavigationViewController.
self.nav.popToRootViewController(animated: true)
self.nav.pushViewController(firstController, animated: true)
self.nav.pushViewController(secondViewController, animated: true)
This way your users UINavigationController stack will look like root > firstVC > secondVC and they will have bread crumbs to be able to traverse back through the flow.
Presenting
In the case that you do not want to push a view onto the stack. Maybe you want to present a popup regardless of where they are at in the app and you don't want to mess up their location, you should use present a VC. This will simply present a ViewController over the entire NavigationController without being added to the NavigationController's stack.
self.nav.presentViewController(modalVC, animated:true, completion: nil)
From within that modalVC you can call
self.dismiss(animated: true, completion: nil)

Swipe Right to Dismiss ViewControlelr without using Storyboard Swift

How to dismiss any View Controller without using Storyboard ? and do I have to use UINavigationController to achieve this ? if not how then ?
if someone is still struggling to dissmiss on swipe (left,right,bottom,top)
i came across a very elegant and decent cocapod panslip
with a very simple usage
just call this method in viewDidLoad()
For viewController embedded in navigation Controller
let viewControllerVC = self.navigationController
viewControllerVC!.ps.enable(slipDirection: .leftToRight){
//anything you want to do after dissming
}
For Simple View Controller
let viewControllerVC = self
viewControllerVC!.ps.enable(slipDirection: .leftToRight){
//anything you want to do after dissming
}
For TableView embedded in View Controller
let viewControllerVC = self.YourTableview.parentContainerViewController()
viewControllerVC!.ps.enable(slipDirection: .leftToRight){
//anything you want to do after dissminn
}
The way to dismiss a view controller is to call a dismiss method on the presenting view controller. If you presented your child controller from a parent by calling self.present(_:animated:) then you can call self.dismiss(animated:). If you used a navigationController and called navigationController.push(_:animated:) then you need to tell the navigation controller to dismiss with navigationController.popViewController(animated:)
Method names might not be exact, but you should be able to figure it our with autocomplete.
If you want to swipe right is not a dismiss, but I think that what you want it to embed the UIViewController inside a UINavigationController.
For example:
if let vc = UIStoryboard(name: "YourStoryboard", bundle: nil).instantiateViewController(withIdentifier: "YourViewController") as? YourViewController {
let navigation = UINavigationController(rootViewController: vc)
navigationController?.pushViewController(vc, animated: true)
}

Log out and clear VCs below current VC

We are looking to change the way a user logs out of our app. In order to do that, we want to dismiss all the VCs below the current VC and put another VC on top as the root VC. Right now we are doing this which I believe does not dismiss any VC below from memory.
let viewController = storyboard?.instantiateViewController(withIdentifier: "SignIn")
if let unwrappedViewController = viewController {
self.present(unwrappedViewController, animated: true, completion: {})
}
The problem is that the VC that we want to put on top is not embedded in a Navigation Controller or tab bar controller. How would we dismiss the VCs and set the new VC as the main VC as if the user was opening the app for the first time without having previously logged in? We also do want the transition to be animated with whatever animation is normal for that event (modal animation is fine). I have read a bunch of different ways on doing it but I want to know which way is best practice and should be implemented specifically dismissing all VCs and putting a new VC that isn't in a Nav controller on top.
If you can access the UIWindow of the app, you can set its rootViewController property to your sign-in view controller, effectively removing all current view controllers and adding the sign-in view controller instead. Here's an example:
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
// Should remove all subsequent view controllers from memory.
appDelegate.window?.rootViewController.dismiss(animated: true, completion: nil)
// Set the root view controller to a new instance of the sign in view controller.
appDelegate.window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SignIn")

How to move to existing viewcontroller from another viewcontroller

When I start the app my initial view is MainViewController.
Then on button click I move to AnotherViewController.
performSegueWithIdentifier("myFirstSegue", sender: self)
On AnotherViewController I have a button that should return me back to MainViewController but I don't want to instantiate new but to use existing instance.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var story = storyboard.instantiateViewControllerWithIdentifier("MainViewController") as! ViewController
let navigationController = UINavigationController(rootViewController: story)
self.showViewController(navigationController, sender: nil)
With this code I create new so I lost data that I stored on that view.
How can I get existing instance and just move to it?
UPDATE
To explain issue better if on first view I have UISwitch and move to another ViewController and then get back to first I want to UISwitch stay same not "reset".
if you are using a navigation controller, doing this will pop to your previous controller. navigationController.popViewControllerAnimated(true)
you can use this one
self.dismissViewControllerAnimated(false, completion: nil);
Navigation controller maintains the stack of viewcontrollers. just pop your viewcontroller and you will be back to your previous view controller.
navigationController.popViewControllerAnimated(true)
or use unwind segue from storyboard.
What are Unwind segues for and how do you use them?

Resources