I am making an iOS app where I want to present a flow of pages like this:
Basically I want to achieve is to have this flow of pages:
PageA
PageB
PageC
PageD, dismiss back to:
PageC
PageD
PageE, dismiss back to:
PageA (starting point, start over again)
I am using ShowViewcontroller to present the pages (modal) and DismissViewcontroller to dismiss.
As per Apple's documentation if I dismiss a VC early in the stack all subsequent UIViewCOntroller are dismissed too (Apple doc).
However I experience that ViewWillAppear and ViewDidAppear are fired on the UIViewController that are dismissed even when they do not appear (e.g. in the example when dismissing back to PageA from PageE then ViewWillAppear is called on PageD, PageC, PageB too).
This does not seem logical to me. Can anyone explain why this is happening? And perhaps correct me if I am approaching this the wrong way.
I am using Xamarin.iOS.
Apple doc:
If you present several view controllers in succession, thus building a stack of presented view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack. When this happens, only the top-most view is dismissed in an animated fashion; any intermediate view controllers are simply removed from the stack. The top-most view is dismissed using its modal transition style, which may differ from the styles used by other view controllers lower in the stack.
The ViewControllers work with a stack. Whenever a new ViewController (of any type) is added to the stack, You lose more and more control of your ViewControllers (especially when using a modal for your ViewControllers). So, say you have 5 ViewControllers in your stack (A, B, C, D, E, as per your example), and assume they are created in the order as stated, in order to return from ViewController E to ViewController A, you'd have to go through the entire stack. That means that every ViewController in your way needs to be displayed first, in order to dismiss is (since you already have ViewController E displayed, this doesn't occur here).
I hope this helps you. Good luck!
Love and regards,
Björn
Related
I have two method to jump from one viewcontroller to another
For presentViewController
[self presentModalViewController:view animated:YES];
For pushViewControlle should use
[self.navigationController pushViewController:view animated:YES];
Which Will be best approach ?
Which will cause more memory leak?
Which one is use if Our Design is like
Introduction view (bunch of slides )-> login -> signUp-> HomeActivityScreen-> Then Bunch of tab bar in it
If you use pushViewController you will automatically get a "Back" button in the navigation bar. If you use presentModalViewController you do not, and generally will have to implement your own controls and/or callbacks to handle dismissing the controller.
Conceptually the modal presentation style is generally used for atomic tasks that you cannot navigate away from (i.e. you either complete the task, or you cancel, and you cannot do anything else within the app until you do one or the other).
If you're wondering why have the difference in the first place, I can't say. Personally I think frameworks that provide a unified API for moving from one controller to another (like cocos2d, or Android) make a lot more sense.
You use modal view controllers to focus the user's attention on a Task. When you push, the user is in some kind of navigation flow, but still has the total application at their fingertips. They might decide to go forward or backward, switch to a different tab in the middle, whatever. When they get a modal view controller, they can't do any of that until the task is completed or canceled out of (the modal view is dismissed)
When you present a modal view controller, the system creates a parent-child relationship between the view controller that did the presenting and the view controller that was presented. Specifically, the view controller that did the presenting updates its modalViewController property to point to its presented (child) view controller. Similarly, the presented view controller updates its parentViewController property to point back to the view controller that presented it.
Modal view controllers provide interesting ways to manage the flow of your application. Most commonly, applications use modal view controllers as a temporary interruption in order to obtain key information from the user. However, you can also use modally presented view controllers to implement alternate interfaces for your application at specific times.
So, from my understanding this one is best option.
[self.navigationController pushViewController:view animated:YES];
Apple handles the memory for both of these through Automatic Reference Counting. Although pushing a view controller might require more memory than presenting the same, the allocated memory is released when popping or dismissing the ViewController. ARC releases the said memory if no strong references are made from within the popped ViewController, or in other terms, the ReferenceCount is 0. So care must be taken so that no strong references are retained to the ViewContoller's objects.
Now whether to present or push a ViewController depends solely on your app design. Read this to get an outline on when to present a ViewController, .
They are two approaches to navigate from one view controller to other view controller.
By default:
- pushviewcontroller: you will have a back button to return to last visited page. your viewcontroller is put on top of the navigation stack. Think like a webpage when you navigate between pages.
- presentmodalviewcontroller: you have nothing to comeback to last visited page. It is usually used for: a pop-up or to change to new branch of navigation.
The use of pushviewcontroller & presentmodalviewcontroller does not produce memory leaks.
For your application flow, I suppose that HomeActivitiesScreen is one of viewcontroller in your "bunch of tab bar". If so, create a viewcontroller that contains that tabbar and make it as root of your application (named RootViewController for example). Then:
When application is launched, you show the RootViewController
And immediately, present the modal view to your "introduction pages", then login/signup. This modal view start with a Navigation view controller structure.
When the user is connected, dismiss your modal view, return back to your HomeActivitiesScreen and refresh contain if need.
Like this, you do not keep the reference to your login/signup screen when you do not need.
Given the following view controller layout.
We build a stack of modal view controllers by first presenting B on A and then presenting C on B. According to the Apple documentation on dismiss(animated:completion:), calling it on A should actually dismiss the topmost view controller (C in this case) in an animated fashion and all intermediate view controllers without animation. What happens though is that C gets dismissed without animation and B is dismissed in an animated fashion.
I put up an Xcode project on GitHub that replicates that behaviour. Am I missing something or am I misunderstanding the documentation here?
After poking around the web and trying out various 'solutions' it is clear this is an actual bug within iOS. It has been present since iOS 8... and is still present in iOS 10. It was originally reported in iOS 8, but the solution was never validated and Apple automatically closed the radar due to inactivity.
I have filed a new radar as this is in direct contradiction to the documentation for dismissViewController
If you present several view controllers in succession, thus building a
stack of presented view controllers, calling this method(means
-[UIViewController dismissViewControllerAnimated:completion]) on a view controller lower in the stack dismisses its immediate child view
controller and all view controllers above that child on the stack.
When this happens, only the top-most view is dismissed in an animated
fashion; any intermediate view controllers are simply removed from the
stack.
Clear visualization of the issue, both expected and actual results. Credit to Boris Survorov for the test project and visualizations.
I've experienced the same issue and here is what I've found to be a viable workaround. When you need to dismiss the whole stack, execute this code in A:
viewControllerB.view.isHidden = true
viewControllerC.dismiss(animated: true) // or viewControllerB.dismiss(animated:true) - it should produce the same result: dismiss viewControllerC
dismiss(animated: false) // dismisses viewControllerB
This should result with the expected behavior.
I am guessing that your segue from A to B is modal as well? In that case the dismiss function called from A wants to dismiss the view, which is immediately on top of A, which is B. C just gets hidden in order to show you the animated hiding of B. In that sense you cannot stack views via modal segues and dismiss the top one with the dismiss function as you described if you go that far back. The dismiss would work as intended if called from B to dismiss C though.
From an initial ViewController I've modally presented a second ViewController using a ShowDetail segue in the storyboard and a performSegueWithIdentifier: method call. The problem is when I dismiss this modal ViewController with the method dismissViewControllerAnimated: the initial ViewController is reinstantiated calling the viewDidLoad again.
I've tried using a Push segue instead of the Show Detail and the initial ViewController keeps allocated in the background as it should.
What might be going on? The initial ViewController never even calls the memory warning method.
Have you tried unwindSegues?
***** Long explanation ahead, skip to solution if you want the quick way *****
First of all, if it is a ShowDetail, it is not a modal view. Do try to see which is your case.
Modal segues can carry information backwards, but are a bit more complicated than push ones.
If you are modally presenting it, you should use Present Modally instead of a ShowDetail.
A modal presentation will always take the top view position in the stack, and Show Detail does as well, depending in how your views are set. For instance, if you have a detail view in stack, IT will be replaced rather than the stack top view.
Try choosing up to a specific segue, I particularly recommend modal assuming you need more than simple pushes (Or the Show would have closed the problem, being the equivalent to the previous deprecated push. If you only need something simple, Show is the way)
Now we've cleared this, what probably is happening is that the view is being removes since Show Detail replaces views instead of pushing them, and it has to perform init again.
***** Solution: *****
The solution then should be not to lose the view when replacing, and reinitializing it, what dismissViewControllerAnimated: does. If you use unwind segues, though, the view should be replaced BUT retained by ARC.
The following link has the best explanation all over the net about how to use it:
What are Unwind segues for and how do you use them?
I have an iOS app using iOS 5 and Xcode 4.3.2 that is made up of 7 view controllers. VC1 links to VC2, VC2 can link to VC3-VC7 and each of those controllers can link to each other (think of it as a side bar navigation). If I use segues the views are repeatedly added to the stack and if a user goes back and forth it can use a large amount of memory. How can I implement this navigation where I release the previous controller? They are all small controllers so loading them takes little time/processor/memory. Can I presentViewController and then release the presentingViewController somehow? Thanks.
If you implement a UINavigationController, you can use the push and pop view controller methods to go back and forth. popToViewController:animated: is described here, along with 3 other helpful methods.
Well seems like there should be no problem from VC1 to VC2. For the VC3 - VC7 you could:
Present as modalViewController instead of pushing that to the stack.
Or:
- Use the popToViewController:animated: function of your UINavigationController if the Controller is already present in the stack of controllers, otherwise push it. Like
// Assuming u need to push VC6
for(UIViewController *controller in [urNavController viewControllers]){
if([controller isKindOfClass:[VC6 class]])
{
[urNavController popToViewController:controller animated:YES];
}
else{
VC6 *VC6controller = [[VC6 alloc] init];
[urNavController pushViewController:VC6controller];
}
}
You could use UINavigationController's - (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated method to remove any view controllers below the topmost one. Since the navigation controller's viewControllers array is an immutable one, you could not use any NSMutableArray's removeObject... methods directly on the viewControllers array. You would have to make a mutableCopy into a mutable array, remove any (hidden) view controllers you wish to discard from the mutable array, and pass the resulting slimmed-down stack of view controllers to the above method. Since your topmost view controller would be unchanged, there would be no transition animation in your case (see discussion below), so you could also set the viewControllers property directly without bothering with the animated: argument.
From Apple's documentation:
Discussion
You can use this method to update or replace the current view controller stack without pushing or popping each controller explicitly. In addition, this method lets you update the set of controllers without animating the changes, which might be appropriate at launch time when you want to return the navigation controller to a previous state.
If animations are enabled, this method decides which type of transition to perform based on whether the last item in the items array is already in the navigation stack. If the view controller is currently in the stack, but is not the topmost item, this method uses a pop transition; if it is the topmost item, no transition is performed. If the view controller is not on the stack, this method uses a push transition. Only one transition is performed, but when that transition finishes, the entire contents of the stack are replaced with the new view controllers. For example, if controllers A, B, and C are on the stack and you set controllers D, A, and B, this method uses a pop transition and the resulting stack contains the controllers D, A, and B.
What is the difference beetween calling presentModalViewController and pushViewController, when :
animation is set to NO (even if yes, that's just an animation style that can be changed).
a navigation controller is defined when presenting the modal view, so it can be navigable too, with a call stack, ....
Is this just to be able to go back from the first pushed view ? Woooaaaaaa.....
I guess the difference is elsewhere and deeper. No ?
Ignoring transitions/animations and how things are structured behind the scenes (which aleph_null's alswer provides a good discussion of), the only user-facing difference is the ability to return to the preceding view automatically using the navigation bar.
If you use pushViewController you will automatically get a "Back" button in the navigation bar. If you use presentModalViewController you do not, and generally will have to implement your own controls and/or callbacks to handle dismissing the controller.
Conceptually the modal presentation style is generally used for atomic tasks that you cannot navigate away from (i.e. you either complete the task, or you cancel, and you cannot do anything else within the app until you do one or the other).
If you're wondering why have the difference in the first place, I can't say. Personally I think frameworks that provide a unified API for moving from one controller to another (like cocos2d, or Android) make a lot more sense.
The most important difference is about semantics. Modal view controllers typically indicate that the user has to provide some information or do something. This link explains it more in depth: http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html
Here's another, less abstract difference they talk about:
"When you present a modal view controller, the system creates a parent-child relationship between the view controller that did the presenting and the view controller that was presented. Specifically, the view controller that did the presenting updates its modalViewController property to point to its presented (child) view controller. Similarly, the presented view controller updates its parentViewController property to point back to the view controller that presented it."
Also see this thread: why "present modal view controller"?
Take a look into the viewControllers in the image
The top 2 viewControllers(login & submit) at the top left are disconnected from the tabBarController & NavigationController
The rest of the viewControllers are embedded in a NavigationController. They somehow belong to the natural flow of the app.
Now you have to ask yourself
Do I need to always show login + submit page every time? It would be pain in the neck for the user to each time go to login even if they logged in last time. These 2 screen really don't fit the natural flow of the screens. So what do we do? We just add them modally using presentViewController
However for the rest of the viewControllers we want to keep them inside 2 navigation so we can easily go back and forth so we use pushViewController
For more information I recommend you to see this video
The image was also picked from this great answer. It's worthy of a look.
This is what my experience says,if you want to manage a hierarchy of views,better go for pushViewController in the navigation controller. It works like a stack of view-controllers in the navigation controller. If however the requirement is just to show a view on executing some actions on the parent view controller then the best way is presenting it modally.
If you need a complex push pop logic always prefer a pushViewController.
UINavigationController are used when you want to have some sort of hierarchal representation of your data (ie drill down). They work using a stack of UIViewController subclasses. Every time you “drill down”, you simply add another view controller to the stack. Then, the “back” logic is simply a matter of popping view controllers off of a stack.
You can check out this link:
http://www.icodeblog.com/2011/10/11/back-to-basics-an-introduction-to-view-controllers/