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.
Related
So I have view controller A, B, and C.
From A, I present VC B modally over the current context.
There is a button in VC B from where I present VC C (using segue)
Now on dismissing Viewcontroller C, I am trying to dismiss VC B and from VC A, run some function which again presents VC B based on some logic.
But in this case, I get this error "Attempt to present UIViewController on UIViewController whose view is not in the window hierarchy"
I have tried to dismiss with a completion handler, dispatchQueue(after:)
but those doesn't work.
On the contrary, I have a UIButton attached in VC A, which runs the same logic for presenting VC B and it is running correctly and not showing any error.
What am I missing in this scenario?
I hope these two solutions help you.
I had the same problem these two solutions helped me with this issue.
First:
Post notification in VC C viewDidDisappear or in your button click method and add its receiver on VC B which you want to close it after closing VC C.
Second:
You can create a protocol While clicking the button call your protocol method and pass your data to your viewController.
in the viewDidLoad method i have this code.
When the application is running its not go to the another viewController, its gives me an error:
Warning: Attempt to present <CompleteCountryViewController: 0x7fb971779be0> on <ViewController: 0x7fb97176f3e0> whose view is not in the window hierarchy!
What can i do, that when the application running its will go to another viewController?
You should not present a view controller in the viewDidLoad method of another controller because you cannot show a view controller (present modally or push) when a transition is already occurring (push, pop, present, dismiss).
My suggestion is that you move the code in your code sample to the viewDidAppear: method. At this point, you know for sure that the transition has completed.
You seem to have a slight misunderstanding of the lifecycle of UIViewController if you want to modally present a view controller inside the viewDidLoad of another one.
viewDidLoad gets called in one view controller after it has been instantiated and its view components have been loaded (thus the name). The view of that view controller is about to be displayed, so it doesn't make much sense to instantiate another view controller at this point and present it on the first one.
Let me give you an example with two view controller A and B.
You instantiate A and its viewDidLoad gets called. So, A is about to be displayed! What you are doing in your code now is to instantiate B at this very point and show it on A. iOS doesn't like that and will give you your error.
I had an issue where I was attempting to present a modal view controller within the viewDidLoad method. The solution for me was to move this call to the viewDidAppear: method.
View controller's view is not in the window's view hierarchy at the point that it has been loaded (when the viewDidLoad message is sent), but it is in the window hierarchy after it has been presented (when the viewDidAppear: message is sent).
I have a LoginViewController (UIViewController) that when all the criteria is met and the user hits the Login button, a storyboard segue is run that pushes the ProfileViewController (UIViewController). When this happens, I have a log statement in my LoginViewController's dealloc method to see if it is called and to my disappointment it is never called. My question is whether or not it is supposed to be called? Also, when I log in, sometimes I get a "Received memory warning" and sometimes I do not which I find strange because I am taking the exact same steps in both cases and yet i get a memory warning one time and not with the other.
Anyone can shine some light on this that would be great!
Thanks.
UINavigationController maintains a stack of view controllers. You start with one element, a LoginViewController, on that stack. When you push a ProfileViewController, you now have two elements on the stack. The LoginViewController can't be deallocated until it is removed from the stack.
If you want the ProfileViewController to replace the LoginViewController on the navigation controller's stack, you can write a custom segue class to implement that behavior. See this Q&A.
(You might think you could use the “Replace” or “Show Detail (e.g. Replace)” segue type in your storyboard, but those only work if you are using a UISplitViewController.)
With ARC enabled, when a object is not referenced, it will be released.
In order to display view from ProfileViewController, you instantiate a object of it in LoginViewController, and that's how you still can see the profile view after it is presented. If LoginViewController instance is released, the profile view will also get released(assume no one else references it). For the same reason, the LoginViewController instance is not released because another object is holding a reference to it. Say your views are presented in Window -> ProfileViewController -> ProfileViewController, it's the window that keeps ProfileViewController instance from being released.
If you have two views as I assumed so far, the memory warning should be from somewhere else. Two views cannot cause the issue.
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
I have a UIViewController (MyViewController) and another view controller i'm presenting modally though MyViewController (call it SecondViewController). I want to be able to send a message to MyViewController from SecondViewController by using
[self.parentViewController hideSecondViewController];
But since parentViewController is defined as a UIViewController, and hideSecondViewController isn't a method of UIViewController, I get a warning saying "UIViewController may not respond to 'hideSecondViewController'". It works fine, because it CAN send the message successfully during the program, but since I #import SecondViewController in MyViewController, I can't #import MyViewController in SecondViewController. Any way around this?
When it comes time to dismiss a modal
view controller, the preferred
approach is to let the parent view
controller do the dismissing. In other
words, the same view controller that
presented the modal view controller
should also take responsibility for
dismissing it whenever possible.
Although there are several techniques
for notifying a parent view controller
that it should dismiss its modally
presented child, the preferred
technique is delegation.
In a delegate-based model, the view
controller being presented modally
must define a protocol for its
delegate to implement. The protocol
defines methods that are called by the
modal view controller in response to
specific actions, such as taps in a
Done button. The delegate is then
responsible for implementing these
methods and providing an appropriate
response. In the case of a parent view
controller acting as a delegate for
its modal child, the response would
include dismissing the child view
controller when appropriate.
Read more at the View Controller Programming Guide for iOS.
P.S:
since I #import SecondViewController
in MyViewController, I can't #import
MyViewController in
SecondViewController.
To solve a circular dependency problem you can use a forward declaration.
It would be better to redesign your architecture as albertamg proposed but this should work:
[self
dismissModalViewControllerAnimated:YES];
you can call dismiss on both the presenting and presented view controller and it will do the same thing.