Navigation Controller Loop - ios

I'm making an app which has a shopping cart feature. The shopping cart VC is accessible from several VCs via a button on the navigation bar. So far I only have one Navigation Controller and as shown in the image, each VC takes me to the next one, nothing fancy. The 3rd VC (blue arrow pointing to) is a VC that shows the product/item's details and enables me to add it to the cart.
Problem comes with the Shopping Cart VC. To edit the item, I'd like to re-use the same product/item's details (blue arrow pointing to) VC I used earlier.
Right now, I don't really have an issue but I have noticed that once I created the blue segue, the Navigation Bar of the 3rd VC disappeared in the Storyboard however I was still able to see it when I ran the app.
NOTE:
All the segues in the picture are "Show"
The shopping cart VC doesn't have a button to show itself like the other 3 does. So technically I prevented endless/loop of the shopping cart VC - product/item details VC showing one another.
My questions are:
Is it wrong to design the app that way with a VC going to another VC and that other VC can go back to the first VC? Am I going to face issues down the road, perhaps memory leaks of some sort?
Is there a better way to achieve what I am trying to achieve?
If someone needs further explanation please let me know and I'll edit my question.
EDIT: To clarify, the blue segue is basically a button in a UITableView in the cart VC. If you tap that button, it should open the product/item details VC and lets you edit the item's color, etc. In the item details VC, instead of adding the item as a new item to the cart, I'd show an Edit button which would send the edit request to the API and dismiss the VC back to the shopping cart, or I could use the back button in the navigation controller to get back to the shopping cart.
EDIT2: #beshio
Thanks for the answer. However VC1 is actually my root VC where all the app starts. I didn't get why removed VCs from the Navigation Controller's stack. I would like the Back button to work as intended.
So far I have achieved what I wanted but I'm afraid that having two VCs segue-ing to each other would cause a problem. I have already disabled the Cart button in VC3 in case VC3 was presented from the Cart so loops would be prevented. I am only worried about any memory leaks down the road.

It's possible to achieve this kinda transition.
Here, I describe how to implement this with your chart.
As your chart shows, assume you have VC1, VC2, VC3 (top to bottom) and VCX (w/ blue box). You need to define transitions and associated animation directions (push: right-to-left or pop:left-to-right). As your chart, if you define the transitions and animations as:
VC1 to : VC2(push), VCX(push)
VC2 to : VC3(push), VCX(push)
VC3 to : VCX(push)
VCX to : VC3(pop)
and assume we have all of the view controllers instantiated already, then,
VC1 to VC2 transition
at VC1:
navigationController!.pushViewController(VC2, animated: true)
at VC2:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// remove VC1 from navigation stack (I assume VC1 is not a root VC)
let allControllers = NSMutableArray(array: navigationController!.viewControllers)
allControllers.removeObject(at: allControllers.count - 2)
navigationController!.setViewControllers(allControllers as [AnyObject] as! [UIViewController], animated: false)
}
VC1 to VCX transition
at VC1:
navigationController!.pushViewController(VCX, animated: true)
at VCX: (A)
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let allControllers = NSMutableArray(array: navigationController!.viewControllers)
if (navigationController!.viewControllers[allControllers.count-2] != VC3) {
// if not from VC3, remove VC from stack and put VC3
allControllers.removeObject(at: allControllers.count - 2)
allControllers.insert(VC3, at: allControllers.count - 1)
navigationController!.setViewControllers(allControllers as [AnyObject] as! [UIViewController], animated: false)
}
VC2 to VC3 transition
at VC2:
navigationController!.pushViewController(VC3, animated: true)
at VC3: (B)
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let allControllers = NSMutableArray(array: navigationController!.viewControllers)
if (navigationController!.viewControllers[allControllers.count-2] == VC2) {
// if from VC2, remove it
allControllers.removeObject(at: allControllers.count - 2)
navigationController!.setViewControllers(allControllers as [AnyObject] as! [UIViewController], animated: false)
}
}
VC2 to VCX transition
at VC2:
navigationController!.pushViewController(VCX, animated: true)
at VCX: same as (A)
VCX to VC3 transition
at VCX:
navigationController!.popViewController(animated: true)
at VC3: same as (B)
Note viewDidAppear is called when users swipe (left-to-right) to go back and cancel it on the way (== swipe back to left). So, you need some more small code at viewDidAppear against that situation.
If you want the different animation direction, by manipulating stack and using push/pop, you can easily achieve it. This explains how.

Awesome.
In these scenarios you should use setViewControllers([UIViewController], animated: Bool) to get the desired viewcontrollers in the stack when you get many controllers stacked up in cycles.
Other way is you write your own class derived from UINavigationController having methods, pushToCheckout(animated:Bool), popToEditCart(animated:Bool) , removeIntermediateControllers()

Related

Use UINavigationController inside UITabbarController

I'm trying to Use UINavigationController inside a UITabbarController.
this is my Controllers Structure
-UITabbarController(InitialView)
-tabItemOne-DashboardController
-SomeButtons with StoryboardSegue-To-DifferentViewController
-tabItemTwo-OtherController
-tabItemThree-OtherController
Now I want to show the back button when some StoryboardSegue is performed in DashboardViewController.
Let's say I open the app and can see 4 tabbarItems on UITabbarController, in the first tabbar item I have DashboardViewController, in this DashVC I've 3 4 different buttons to show other viewcontrollers. So far so good, everything is working. but once the child viewcontroller from DashVC is on screen, I want to show a back button on the top as UINavigationController do.
I've tried to put the UINavigationController before UITabbarController but its not showing. i've tried to do embed it in DashboardVC but again its not showing.
Do i need to embed seperate UINavigationController with each of childViewControllers of DashboardVC?
Any help is appreciated.
So what you need is this
-UITabbarController(InitialView)
-NavigationController -tabItemOne-DashboardController
-SomeButtons with StoryboardSegue-To-DifferentViewController
-tabItemTwo-OtherController
-tabItemThree-OtherController
and in DashBoardController you need to add this code in
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.navigationBar.isHidden = true
}
override func viewWillDisappear(_ animated: Bool) {
self.navigationController?.navigationBar.isHidden = false
}
this code is to hide the navigation bar in Dashboard and restoring when pushing another view controller so you back button is not hide.
also if you need the same functionality in the others view controller you should embebed in navigation controllers.
is not exactly your case but all navigation Controller are Childs of the UITabBarController that is the initial viewController.

UINavigationController popToViewController not popping

Been working with UIKit for years now. It's amazing how issues like this seem to pop-up out of the blue.
I have a simple navigation setup:
UINavigationController
HomeViewController [push]
DetailViewController [push]
ModalViewController [modal]
A root navigation controller with 2 children pushed onto the stack. Then a modal presented from the root nav controller.
For some reason, the following snippet of code isn't working as expected:
extension UINavigationController {
func popToViewController(_ vc: UIViewController, animated: Bool, completion: #escaping ([UIViewController]?)->()) {
let popped = popToViewController(viewController, animated: animated)
if let coordinator = self.transitionCoordinator {
coordinator.animate(alongsideTransition: nil) { _ in
completion(popped)
}
}
else {
completion(popped)
}
}
}
using the extension:
navigationController.popToViewController(
homeViewController,
animated: true
)
No errors, warnings or crashes occur. UI is still fully responsive. But the DetailViewController in the stack is never popped. Inspecting the extension's popped variable, results in an empty array - which makes sense as the DetailViewController is clearly not removed from the stack.
What could prevent a navigation controller from popping a valid vc off of it's stack?
Things I've checked:
homeViewController is in the stack already, and I'm asking it to pop to the same instance. i.e. navigationController.viewControllers.contains(homeViewController) == true
I'm on the main thread. i.e. Thread.isMainThread == true
navigationController.viewControllers returns the same array before & after calling popToViewController(_vc:animated:)
Manually figuring out what vc's need to be popped, and calling setViewControllers(_ vcs:animated:) with the vcs I want to keep (in this case, just the HomeViewController instance). This still has the same issue.
I want to say this has something to do with popping view controllers off the stack from behind a modal presentation. But, as far as I know this is an okay thing to do. Plus, I've done it before and have had no issues in the past.

How I could clean UINavigationBar transitions history?

I currently have parental "menu" TableView with UINavigationBar and from each cell there is a segues by reference outlet to 3 similar Views with different information.
In each View there is a buttons to other 2 Views.
With every button's segue opens another View.
The problem:
From every View UINavigationBar's back button returns me to previous View but i tries to make back button to "menu".
Additional Bar Button Item and segue from it makes very close effect but segue animation is not like in UINavigationController.
How I could clean UINavigationBar transitions history in segue to initial View?
You can try pop to root view controller or You can edit navigation controller viewControllers property and remove/add some VC in between.
You can try Unwind Segue mechanism too.
Here are some methods(function) that navigation controller providing for pop operations. They are returning optional UIViewController (intance) from it’s navigation stack, that is popped.
open func popViewController(animated: Bool) -> UIViewController? // Returns the popped controller.
open func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? // Pops view controllers until the one specified is on top. Returns the popped controllers.
open func popToRootViewController(animated: Bool) -> [UIViewController]?
Here is sample code as a solution to your query::
// if you want to back to root of your app
if let rootNavigationController = self.window?.rootViewController as? UINavigationController {
rootNavigationController.popToRootViewControllerAnimated(true)
}
// But if you want to back to root of your current navigation
if let viewcontroller = self.storyboard?.instantiateViewController(withIdentifier: "NewViewController") as? NewViewController { // or instantiate view controller using any other method
viewcontroller.navigationController?.popToRootViewControllerAnimated(true)
}

Push view controller, but go back to another one

Let's say that on my regular flow I have
VC1 -> VC2 -> VC3 -> VC4
But I have a requirement that when I touch a special button on VC1 it needs to go to VC4.
No problem there. The problem is that when I tap the back button on VC4, it needs to go back to VC3 instead of VC1.
I already tried pushing From VC1 -> VC2 and VC2 -> VC3 without animation and then VC3 -> VC4 with animation, but you can see a quick glimpse of VC3 which looks awful.
Any ideas?
I think in this specific case, you can insert a view controller on the navigation controllers stack after presenting the 4th viewController
if let navigationController = navigationController {
navigationController.pushViewController(vc4, animated: true)
let vc3Index = navigationController.viewControllers.count - 1
navigationController.viewControllers.insert(vc3, atIndex: vc3Index)
}
This should place VC3 next in line when the user presses back from VC4. Untested code, btw.
You can modify the UINavCon's viewControllers to achieve the order you want.
func specialButtonInVc1() {
self.navigationController?.pushViewController(fourth, animated: true)
self.navigationController?.viewControllers = [self, second, third,fourth]
}

dismiss two controllers Swift

I have this situation :
I have a first view controller , when tap on button in it I open in modal mode another view controller , in this view controller when I tap another button I open in modal view another view controller and in it there is a button and when I tap on it I want to go to first view controller without re-initialize it.
How do I do it?
This is the perfect situation for an unwind segue.
Put this in your first viewController (the one you want to return to):
#IBAction func backFromVC3(_ segue: UIStoryboardSegue) {
print("We are back in VC1!")
}
Then in the Storyboard in your 3rd viewController, control-drag from your button to the exit icon at the top of the viewController and choose backFromVC3 from the pop-up.
Now, when the user presses the button in VC3, both VC3 and VC2 will be dismissed and you will return to VC1.
If you are not using Storyboards, you can dismiss the viewControllers with code. Here is code for a button's handler to dismiss two levels of viewController:
func doDismiss(_ sender: UIButton) {
// Use presentingViewController twice to go back two levels and call
// dismissViewController to dismiss both viewControllers.
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
}
Thanks all for reply and edited my question :)
I found 2 line code to resolved my problem:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window!.rootViewController?.dismissViewControllerAnimated(true, completion: nil).
And that work well.
Thanks very much

Resources