How to navigate through the app - ios

I have an iOS app which has the following internal structure:
So R is the root view controller (sort of main menu screen), user can proceed to settings screen (S) and then to either A or B view controllers.
The task sounds rather simple: when push notification comes (and user tapped on it), application must show A view controller to the user.
I can think of two ways how to handle it.
The first is to find out the currently active view controller, and then use logic like this:
If A is the currently visible view controller, then nothing needs to be done.
If B is the current, then it need to be dismissed (thus routing to S) and then A must be presented immediately. And user must not see that S appears even for a short time.
And so on.
The second way it to dismiss/pop everything until getting to the root R. And then push S and present A.
Do my thoughts make sense, and what would you suggest?
Particularly, I am interested in
how to realize which view controller is "the current" at given moment
how to perform routing B->dismiss->S->present A quickly and without showing S.

Answers :-
1- To know what is the current active viewController keep a shared userDefault boolean value called isAactive by default it's false in viewDidLoad make it true and in viewDidDisappear make it false again and same for all like isBactive and so on , check it wherever you are
2- When notification comes while B is active use delagate or NotificationCenter to inform viewController S (listener) to colse B if active and present A like that
dismiss(animated: false, completion: nil)
// present A make animation false
or If you make it also push from S to A and B
so you will pop the current if it's B and push A
note: you can use setViewControllers method of navigationController to set array of viewControllers anywhere which removes the current stack of viewControllers and replace them with new array that you set , that will keep your head high from checking current and achieve the goal

I do mine like this when receiving notifications for our calendar events. I negate the call, so it only fires when the user /isn't/ on the Calendar view. Also, this is a push onto an existing nav stack, which may not be the behavior you want. If you want to rebuild the nav stack, you'd want to call navController.popToRootViewController(animated: false) before the push.
AppDelegate
func pushCalendarView() {
let navController = self.window?.rootViewController as! UINavigationController
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let calendarView = storyboard.instantiateViewController(withIdentifier: "CalendarView") as! CalendarViewController
// !negated
if let controller = navController.visibleViewController, !controller.isKind(of: CalendarViewController.self) {
navController.pushViewController(calendarView, animated: true)
}
}

Related

How to detect back button is pressed in the next viewController in a navigationController

I want to detect if back button is pressed in the next viewController in a navigationController.
Let's say I have VC_A and VC_B viewControllers in a navigationController. I know how to detect if back button is pressed in a current view controller but I do not know how to detect it in a previous viewController.
Edit:
I go from VC_A to VC_B and when I press back button in VC_B then I want to call a function in VC_A.
You could use notification center. This link has a nice tutorial: https://learnappmaking.com/notification-center-how-to-swift/
I want to detect if back button is pressed in the next viewController in a navigationController.
I'm not sure I understand this exactly, but it really doesn't matter much: in essence, you're talking about some view controller (call it controllerA), whose views aren't currently visible, finding out about a change that affects some other view controller (controllerB). The usual reason for needing such a thing is so that controllerA can update some data that it manages.
A better way to handle that is to have both controllers share a common data model. Any application state that's affected by something like a view controller being dismissed is shared data that should be part of the data model. controllerA really shouldn't care about whether controllerB's back button was tapped or not... that event is only the business of controllerB (and arguably the navigation controller that manages it). What controllerA should care about is updating its own views according to whatever changes happened while it was off screen, and those changes should be recorded in the model by controllerB and any other view controllers that might have been active along the way.
I'm suggesting you to do that with Notification Center like AglaiaZ suggested you. But if you're not feeling comfortable with using Notification Center, then try this more basic solution with viewWillAppear delegate method in viewController from which you're tracking are you back from B VC. So, let's go.
Set this variable in your current view controller class where you want to trigger method when the back button is pressed on the specific view controller, let's call that specific view controller B VC.
let isFromBViewController = false
Then in code block where you're triggering the transition to B VC set this variable to true.
func goToBViewController() { // This method is triggering transition from A VC to B VC
isFromBViewController = true }
And then in viewWillAppear delegate method check did current VC from which we triggered the transition to B VC have appeard from B VC.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if isFromBViewController {
// code for doing something when you got back from B VC
isFromBViewController = false
}}
And that's it.
But, again I'm suggesting you to use the notification center as #AglaiaZ suggested, the tutorial is easy, and with that tutorial I've also learned how to use Notification Center and how to create and use custom notifications.
Good luck.
If I understood correctly, you want to do something when the back button in the navigation bar at the current view controller is pressed, and the user is going back from the current B view controller to A view controller.
Put this line of code in the view controller in which you want to track when the user has pressed the back button.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isMovingToParent {
//your code when back button is pressed
}
}

How to access previous view controller from a dismiss command

Throughout my app I use a navigation controller to push and pop my view controllers. When I pop from one ViewController, I check to see if I can reload the data in the previous one with this:
_ = self.navigationController?.popViewController(animated: true)
if let previousViewController = self.navigationController?.viewControllers.last as? AnimalsVC {
previousViewController.tableView.reloadData()
}
This works every time, but now I need to do the same with another view, but it's not apart of the navigation controller because I modally present (instead of pushing it to the navigation controller). Now there's no way I can access the previous ViewController like before and I can not figure out how to access the previous ViewController.
How can I access the previous ViewController to reload the tableview like the example code without accessing the navigation controller?
I know this is possible with notifications, but I prefer not to use that method if possible!
First of all, It's not necessary to access the previous ViewController to reload tableview(or any other func)
I recommend you to use Delegate to achieve the same feature.
Holding a reference to the previous viewController in the way you mentioned will make your app very hard to maintain when your app gets more complicated.
You can call tableview.reloadData() in viewWillAppear method in the controller that you present modally

How to pop from 3rd ViewController to the initial ViewController without NavigationControllers?

I know this is probably an easy task but I can't get my head around how to solve it. I'm not using a NavigationController by the way. Basically I have 3 view controllers in my app:
-LoginVC (this has a register button. when tapped, it goes to the SignupVC)
-SignupVC (if user signs up, it will push to the MainAppVC)
-MainAppVC (has a logout button)
For the transitions, I use the method: present(viewController, animated:, completion:)
When the user logs in via the LoginVC, he'll be presented the MainAppVC as expected. When he logs out, I'll dismiss the current VC (which is the MainAppVC) to send him back to the LoginVC.
Now here is the case where I have questions about. When the user does not have an account and signs up, this is the VCs he will pass through (LoginVC > SignupVC > MainAppVC). Once he registers successfully, he'll be presented the MainAppVC. Now once he logs out, he'll be transitioned from the MainAppVC to the SignupVC because I used the same dismiss method.
What I want to do is to send the user back to the LoginVC from the MainAppVC. How do I accomplish that without using a navigation controller in my project? How do popular apps (Facebook, Twitter, Instagram, etc) handle this in their apps?
One way I can think of is to perform a segue from 3rd to 1st VC but I think that's a dirty way since it just adds to the stack, instead of popping it off which is a potential issue.
There are three solutions.
1.Use window root view controller.Set root view controller between login and main view controller.
let loginSb = UIStoryboard(name: "Login", bundle: nil)
let loginVc = loginSb.instantiateInitialViewController()
window!.rootViewController = loginVc
2.Dismiss the sign up view controller the first time you present main view controller.And present the main view controller from login view controller.
3.Try to dismiss the sign up view controller in the completion block when you dismiss the main view controller.
I think the first one is best.
If are forced to NOT use a rootViewController (with UINavigationController), you should check in MainAppVC which one is the previous VC (Pseudocode in the example) and do:
Note: Swift 3 Code.
// If previous VC is LoginVC
dismiss(animated: true)
// else if previous VC is SignupVC
// here's what you want
presentingViewController?.presentingViewController?.dismiss(animated: true)
Hope that helped.
Another option is to use unwind segues.
In the ViewController that you want to go back to, implement a method as follows
#IBAction func unwindToLogin(segue: UIStoryboardSegue) {
}
Then in Storyboard, from the last ViewController right click and drag from the button (for example) to Exit and choose unwindToLoginWithSegue.
Note that you can also do this programatically. Here is a good tutorial.
Best approach is :
Dismiss SignUpVC once User Register successfully, Then from LoginVC present MainVC.
In SignupVC :
self.dismissViewControllerAnimated(true, completion: {
self.LoginVC!.present(MainVC, animated: true, completion:nil)
})
This approach is used mostly.

Navigate with NavigationViewController

I would like to know how to properly navigate in an application using a NavigationController, to not instantiate a ViewController each time I call it for example:
In all know applications such as Instagram, when you click on a Toolbar Item, you access a viewController, but the state of this one is saved, if you were on a photo, it still is on this photo after you went to settings, post photo etc. And in my application, my NavigationController instantiate a new ViewController each time I want to access it, and it is time Consuming when you load data in ViewDidLoad for example.
Is there someone who could help me please ?
If you do not want to create a new controller - do not create it!
Create your controllers once and then reuse them. For example you can declare your controllers as a singletons:
let vc1 = MyViewController1()
let vc2 = MyViewController1()
let vc3 = MyViewController1()
And when you need to push some controller do not create a new one but reuse already created:
navigationVC.pushViewController(vc2, animated: true)
UINavigationController will throw an error if you will try to push a controller which is already in a navigation stack. So you will have to remove it from the navigation stack first:
// vc2 - is a controller that could be in the navigation stack and should be pushed once more
var controllers = navigationVC.viewControllers
if let index = controllers.indexOf(vc2){
controllers.removeAtIndex(index)
}
navigationVC.setViewControllers(controllers, animated: false)
then you can push vc2 as usual. If it is possible in your app you should also check a case if vc2 should be pushed right after vc2.

How to initiate a viewcontroller without presenting it?

I have a tab bar controller and a few other viewcontrollers outside the tab bar controller. I have this viewcontroller called "X" which is a part of the tab bar controller. I have another viewcontroller called "Y" which is not a part of the tab bar controller. Now i want to initiate X when im inside Y upon tapping a button without actually presenting it. I want X to become active and fire its viewdidload so that i can access X whenever i chose to do so. Is this possible. Im sorry if im not clear in explaining my quiestion. let me know if you need any other additional information.
Old question at this point, but I was looking for an answer myself just yesterday and managed to get it figured out.
Instantiate the ViewController, then call loadViewIfNeeded().
Example:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let exampleVC = storyboard.instantiateViewController(withIdentifier: "ExampleViewController") as! ExampleViewController
exampleVC.loadViewIfNeeded()
If you want, you can then check if the view is loaded with:
exampleVC.isViewLoaded
The ViewController is now all set up and ready for display when you decide to present it.
I want X to become active and fire its viewdidload so that i can access X whenever i chose to do so.
UIViewController uses lazy loading for the view property. You can just call:
[myViewController view];
This will trigger the loadView and/or viewDidLoad methods, if implemented.
However, you may wish to consider moving the relevant logic from viewDidLoad to init (or initWithCoder: if using a storyboard/xib). This way you won't have to call -view.
If I understand right, you want X to be initialised. So you can perform all you initialisation actions on your init constructor. viewDidLoad will only be called by the framework when you perform some presentation, either by pushViewController or addSubview. The reason for that is that because the framework wants to avoid getting instances of views on the memory without being used. So you can initialise all you want from your controllers but the views won't be loaded.

Resources