Supposedly I have a UIViewController A and a UIViewController B. From A, I call the method presentViewController:B. When B shows up, what happens to A? Is it removed from the memory? If not, what method should I call to delete it?
If my UI flow is like this, A->B->A->B->A->B->... and so on, how to prevent the memory from increasing accordingly?
When you use the presentViewController:animated:completion: method from controller A to present controller B modally, what happens is that the presentedViewController property of A is set to controller B, and the presentingViewController property of B is set to A. Thus, both controllers are kept in memory while the presentation is taking place.
When you go from B to A, you call dismissViewControllerAnimated:completion: on A via the presentingViewController property of B, like this:
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
(You can also call [self dismissViewControllerAnimated:YES completion:nil] and the system will automatically forward the request to the presenting view controller.)
After that, the presentedViewController property of A will be set to nil and, consequently, it will be subject to memory deallocation by the system, provided that there isn't any other strong pointer pointing to it.
No, A won't be removed from memory.
And if you want to alternate between A and B you can either:
use a UINavigationController and push B, then pop it off again and you're back at A
or make B the new root controller of your window and then A again and add a proper transition
or use a as the root and present B. Then dismiss B and you're back at A.
The UIViewController will stay in memory unless you popped it.
Use push & pop instead of just pushing new UIViewControllers.
Instead of this A->B->A->B->A->B->
You'll get this A<->B
Have a root view controller when ever A completes it's task let your root view is responsible of removing A and and presenting B. Like wise when B finishes its task alert your root view to remove B and show A again.
It won't be released automatically. You can release it manually by using dismissModalViewControllerAnimated.
Here is a very good tutorial to see this : Link
Related
Given:
ViewController A that presents ViewController B
ViewController B has no reference to ViewController A (except implicitly the presentingViewController property)
ViewController B calls dismiss on itself and does nothing else
What I want to achieve:
ViewController A wants to know when ViewController B got dismissed in order to clean up some state
Restrictions:
I do not want to use KVO
I do not want to modify ViewController B or its behavior in any way
What I have found out so far:
dismiss(animated:completion:) according to the documentation forwards the call to its presentingViewController. But as it seems dismiss(animated:completion:) is not called, but rather a private method _performCoordinatedPresentOrDismiss:animated:.
iOS documentation on presentingViewController is misleading. It says "the view controller that was presented has this property set to the view controller that presented it", but that's not true. In iOS 11, this will always point to the root parent VC of the VC that present was called on. Similarly the documentation on presentedViewController is misleading. It says "the view controller that called the method has this property set to the view controller that it presented", that's not the whole story. Every VC in the hierarchy of the VC (all its parent VCs and child VCs) that called present will point to the same presentedViewController.
In your Controller A , kame it as UINavigationControllerDelegate and with navigationController:didShowViewController mark the presentation of Controller B (isControllerBisPresented = true). When viewDidAppear of B , check if isControllerBisPresented is true.
An ugly workaround would be to use a man-in-the-middle which does something in deinit. So A presents M which embeds B as a childVC. When B dismisses itself, M will be dismissed implicitly as well, so it's deinit method should be called. There it can notify A that it was dismissed.
This is fragile, as some reference cycle could prevent M from being deallocated which would result in A not getting notified. So I'd rather want to find a better solution.
How can I go back to a viewController that was already segued from another VC without Nav Controller embed?
Meaning I have viewController A which segued to viewController B. I dismissed viewController B and now back at viewController A. How can I go back to the same instance of viewController B?
I've looked at a lot of SO posts but they all seemed to be about unwinding, which would work if I want to go from B back to A, but not back to B again.
Also, viewController A is in a tabbar. Viewcontoller B is not.
Thanks!
You can't do this with a segue, since segues always instantiate new controllers. Instead of a segue, you should create a property for ViewControllerB in ViewControllerA, and instantiate it only the first time you go to it (which you would do using presentViewController:animated:completion). After coming back to A, when you want to go to B again present the instance you already created. If you're using a storyboard to make your controllers, you would do something like this,
-(void)someMethod {
if (!self.vcB) { // vcB is a strong property
self.vcB = [self.storyboard instantiateViewControllerWithIdentifier:#"VCB"];
}
[self presentViewController:vcB animated:YES completion:nil];
}
You will want to create a reference to VC B:
var VCB:UIViewController = bInstance
Then you will want to dismiss VCB from VCA
VCA.dismissViewController(VCB,animated:false)
Then to bring it back you go:
VCA.presentViewController(VCB,animated:false)
Keep in mind that the new compiler for Xcode uses ARC which does automatic reference counting. You need to keep a reference to the UIViewController to bring it back, otherwise it gets deleted!
My Application flow is as below where A, B , c and D are view controllers.
Arrows mark presenting from and to view controllers.
Now I need home button in B , C and D view controller that navigate back to A.
I am not using storyboard.
Its I am unable to use dismissviewcontroller as it dismisses just once, where in some cases its required 2 or 3 previous view controllers dismiss.
Any suggestions in this regards will be helpful.
It sounds like you should be using A as your root view controller on a UINavigationController. The only thing B, C, and D will need to do is call popToRootViewController.
You'll need to make your login view controller be pushed from A, but you can do it without the user seeing it by putting the code in the AppDelegate (which is likely where you're checking to see if the user needs to log in anyway).
How about using setViewControllers:animated:. Where ever you are, you get the first View controller as firstViewController = [self.navigationController viewController] firstObject], then [self.navigationController setViewControllers:#[firstViewController] animated:YES].
See here: setViewControllers:animated:
In my navigation I have a rootViewController (AController), a second ViewController (BController) and a third one (CController).
I can navigate my app by pushing A --> B --> C.
When I go back from C --> B, in BController:viewdidLoad how can I get I'm navigating from C and not from A?
I tried to use self.presentedViewController.restorationIdentifier but it always return me the identifier of AController, not the CController one.
Should I use the storyboardID property? how can I do that?
Any help is appreciated.
The best practice for such application would be to use a UINavigationController for navigation. If you are navigating using UINavigationController then viewDidLoad will only get called when you go from A to B but will not be called when you go back from C to B controller
Try using delegation to set a property in B When loading C and going back to B again.
Also, as #khawar-ali mentioned, viewDidLoad only gets called when loading the view controller for the first time hence viewDidLoad method won't get called when going back from C. Try ViewWillAppear/viewDidAppear instead? :/
Here is my issue.
The App I am creating has non-linear navigation.
So I am implementing my own back button and doing my own navigation.
However, I am wondering how I should be presenting the next view.
If my navigation was linear, I could do:
-(IBAction)btnBackPressed:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
However that will not work for me since pressing back may not necessarily bring you back to the previous view.
Therefore I am thinking of using for example:
AddTaskViewController *add = [[AddTaskViewController alloc] init];
[self presentViewController:add animated:YES completion:nil];
The reason I am not using Storyboard is because all my UI is made programmatically in code.
The worry I have with this is that I think presentViewController will just push the new view on top of a stack. Thus, if the user presses back, forth, back forth, he will eventually run out of memory.
Given my circumstance that I need non-linear navigation and all my UI is created in code, what should I use to present the next view without wasting memory?
Thanks
If you are still interested in a clean solution using UINavigationController, consider this design.
Situation: Let A and B be types of view controllers. A is the root, and B is a detail view which can push or pop to other B controllers.
Goal: We want to delete any B controllers that are not adjacent to the currently presented view controller, but maintain the hierarchy so we can recreate the views when necessary. Thus, the maximum hierarchy the navigation controller will know about is A--B--B.
Design: Make A the navigation controller's delegate. Give it an array of model objects which represent B controllers enough to recreate the views from them. Add to this array whenever a B controller is pushed, which A will know about from the navigation controller delegate methods. Remove objects from the stack when a B controller is popped.
On pushing a B controller, the A controller will take the navigation controller's view controller stack and (if it exists) remove the B controller directly before the one that was displayed before the push. On popping a B controller, the A controller will (if it exists) recreate the B controller directly before the destination controller and insert it in the stack.
Example: Let's say A has kept track of a hierarchy like this: A--B1--B2--B3--B4. By the system outlined above, the navigation controller only knows about A--B3--B4. When the user pops B4, the A controller will be notified and recreate B2, inserting it before B3. Thus, the new hierarchy is A--B2--B3. When B5 is pushed from B3, B2 is removed to produce A--B3--B5.
I realize this is a fairly complicated system to implement, so I would only recommend it if you had a large number of controllers and required the amenities of UINavigationController. Another solution that occurs to me is UIPageViewController, which allows you to provide view controllers on the fly.
Hope this helps!
Yes you are correct that the user will eventually run out of memory if you keep presenting Modal view controllers using
[self presentViewController:add animated:YES completion:nil];
and
[self dismissViewControllerAnimated:YES completion:nil];
will only dismiss the top most viewController being displayed.
Instead you would be better off building your own container view controller and handle your own navigation. For more information read the "Implementing a Container View Controller" section in the UIViewController Apple documentation.
https://developer.apple.com/library/ios/Documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html