I am an experienced iOS developer, but I've stumbled upon an issue that I'm not entirely sure how to solve.
Lets say for example I have 3 UIViewControllers, A, B, and C respectively. A is the root and B is presented on top of A and C is presented on top of B.
B and C are presented using presentViewController:animated:completion:
So the presentation stack looks like this.
[A] -> [B] -> [C]
In my program, I want to dismiss B and only B. The docs for
dismissViewControllerAnimated:completion: says that if I dismiss B, it will ask its A to dismiss B and then will also dismiss C as well. I do not want that. I want to dismiss B only such that A will choose C as its presentedViewController like so
[A] -> [C]
Is this possible?
--------Notes-------
I realize that this seems more suited for another presentation pattern where A,B,C would be siblings under a common parentViewController. But I May not use that.
The presentedViewController/presentingViewController pattern is just like a doublylinked list. However, since these properties are read-only, I can not do any node manipulation.
Could you use a navigation controller with the navigation bar hidden and push the controllers rather than present them (using a push and pop animation that mimics that of presentation).
Then use setViewControllers to replace the ABC stack with an AC stack?
Related
In my app I have a navigation controller with four controllers. The user navigate from A -> B -> C -> D -> A_1 -> B_2 -> ... etc. It's one way and every ViewController is always a new ViewController.
This cycle is intended. But in ViewController A and C I initialize GoogleMaps which is using a lot of memory. So after 15 loops (and 30 inits of GoogleMaps) my app crashes because of a memory leak.
Now I see different possibilities to solve this problem.
1) I do not init a new ViewController rather I reuse my VCs. So Google Maps just initialize two times.
I dislike this, because my VCs could have different states. A clean init would be more comfortable and a smaller source of errors
2) I remove the stack at the right time, because the navigation is just one way. When the transition from A -> B is done I could throw A away. Same thing for C -> D.
3) I deinit Google Maps after transitions. I don't know how to do this yet but I'm quite sure that I will figure it out.
I read about setViewControllers by which I can replace view controllers.
What is the best practice? What recomments Apple?
Start with the 3rd and the easiest option:
Use override func viewWillDisappear(){} to deinit google maps. You could use this method for 2nd option as well.
But I think, you should use delegates when you go back to A from D. What do you change on A, when you reach it again?
You mentioned that navigation is just one way. If so you can just replace UINavigationController's viewControllers array. In other words, you may not need navigation stack if a user does not go backward in the stack.
Alternatively, I got a similar issue in my app. When I am pushing a controller in the UINavigationController it checks the existing viewControllers and modifies it. For example, if an identical controller is already there, pop to that copy.
You can navigate view controllers from A -> B -> C -> D -> A_1 -> B_2 -> ... etc. using two method.
Method 1:
When you are going to D controller then pop to a specific controller(A controller) and again navigate cycle A to D with the push view controller.
self.navigationController?.popToViewController(A, animated: true)
Method 2:
When you are going to D controller then the present controller (A controller) and dismiss presented cycle flow when you again reached at D controller
viewController.present(A, animated:true, completion: nil)
Assume that we have 3 view controllers: A, B, and C. A is the root view controller, which has presented B, and B has in turn presented C. How can we perform a custom unwind segue from C to A that does not reveal B?
A - B - C
A UIStoryboardSegue has a reference to its source view controller as well as its destination view controller, but what about the view controllers in between (B in our example)? We can perform whatever animations we want on C, but I don't see how we can affect B in any way.
The goal is simply to dismiss all but the root (B and C) to the right while having A come in from the left, so both source and destination are swiping horizontally next to each other. B should not be visible at any point of the animation.
My example dismisses only two views, but I hope to find a solution that would apply to an arbitrary number of views. Furthermore, I am not interested in solving this using a UINavigationController. I have tried simply dismissing B, which does indeed dismiss C as well, but you can still see both B and C during the animation.
You can dissmiss your controller as below :
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil) // you need to track viewcontroller's stack hierarchy
Consider this modal stack
A <-> B <-> C
\--------/\
A can present B or C.
Whenever I need to leave C I need to end up on B,
which means if I got to C from view controller B I can just dismiss.
If I got to C directly from A then I need to unwind back to A and present B.
Bonus points if the latter case can be animated as C -> B transition bypassing the show of controller A.
This is a common problem and for everybody who still doesn't know the correct way how to deal with that, here is the scenario.
Modally presented view controllers A -> B -> C
Now you want to dismiss C with B at one smooth animation landing to view controller A. So apple documentation says that you only need to perform:
[A dismissViewConttollerAnimated:YES completion:nil];
And C with B should be nicely gone. This is really often needed scenario and I'm really said and disappointed that the common use case is not working properly. Why the top view controller C disappears in a moment and B view controller appears with dismissing animation instead of C to be visible for the whole animation process? I would really except to see only C view controller's dismission.
The best solution to use in this case is Unwind Segue. You can directly move switch to any view controller in the heirarchy.
The dismissal of B can't be done in parallel with dismissal of C. This happens sometimes in iOS when the two animations are related somehow - I don't know the exact details.
If you put a breakpoint in C's dismissal completion block, you'll probably see the finished parameter being false, indicating C's animation has been interrupted.
I think the correct solution is to dismiss B only. C will be gone automatically since it was presented by B.
I have three view controllers A, B and C.
C is the target.
From A, I have a segue named showCFromA to view controller C.
From B, I have another segue named showCFromB to view controller C.
Now, when C is displayed (shown from B), I tap on the "Back" button, but at this stage, it shows A, and not B as I expected.
How can I fix that?
Don't mess with Back - it makes for a disjointed app experience. You end up at a place you don't expect to be and navigation just doesn't feel right.
If you have a B on the stack, you can just
popToViewController:animated:
to return to the specific view controller (B) that you want to see.
If you have gone from A directly to C with no intervening stop at B, you can't go there with back. Instead you should just push a B.
If your B should be on the stack - i.e. you went from B to C - then B is where you should end up if you simply go back. If that is currently not the case you need to post some more details to help diagnose the problem.
What is confusing right now is what you actually have on the stack. You mention being at A and going to C, then being at B and going to C, but the order in which you do this (and if these are two separate cases) affects the outcome.
After checking the source again, I found the issue is not properly handling navigation vc stack. When push new VC from B, when viewWillDisapper is called (B), I added "popViewController". Therefore, navigation view controller array has count of 2 (the middle element is removed). This makes strange behavior: From view controller C, I cannot go back.