So my setup is very simple I have:
Controller A
Controller B
Controller A segue's to Controller B, and it's a modal display.
When Controller B's viewDidLoad fires, I instantiate the NSURLConnection with request and start immediately.
If there's no network connection, then the didFailWithError gets fired.
From within here, i call dismissViewController, but when I do, I receive:
"Attempt to dismiss from view controller while a presentation or dismiss is in progress!"
What could possibly be causing this error?
I also have a button on Controller B, that when clicked also does the dismissViewController, but it does it correctly.
Could this be a race condition where Controller B has not finished animating/displaying once it hits viewDidLoad, and the URL connection spawning and failing immediately?
If this is the case, what's the correct way to fix this?
Thanks.
You can't dismiss a view controller until it has been completely presented. If you're still in viewDidLoad, this certainly isn't going to be the case, and I think this mostly will not be the case in viewWillAppear. viewDidAppear is the earliest possible state you can guarantee the view controller's presentation is complete and ready to be dismissed.
With that said... it's better from a UI perspective to instead try to start the NSURLConnection in the background and if you have a good network connection, then you present view controller B, and if not, simply never present it, rather than present and immediately dismiss.
Seems to be race condition. Can you try doing this in failToLoad delegate and see?
//Goes into the failed delegate
dispatch_async(dispatch_get_main_queue(), ^{
//dismiss the View Controller here??
});
Related
In MainViewController, in viewDidLoad() I'm calling a function which in turn tests if Auth.auth().currentUser == nil; If the condition is met then the statement to execute presents another view controller.
In the if statement, why does the statement to execute need to be preceded by DispatchQueue.main.async (if I don't write DispatchQueue.main.async then the view controller doesn't present and it's just stuck on MainViewController).
Because at the time viewDidLoad is called your view controller has not yet been added to the view hierarchy. A view controller that is not part of the view hierarchy can't present another view controller. You should get a log message in the console saying something similar to that when you try to present the other view controller without async dispatch.
Putting the call in the DispatchQueue.main.async causes the presentation to be delayed until the next runloop cycle which happens to be enough that your view controller has been added to the view hierarchy once it gets called.
A better solution would be to put your current user check in a more appropriate place, possibly viewDidAppear.
Dispatch.main.async is used for reason being that all the UI related have to be performed on the mainQueue. Since UIViewController presentation is a UI task, hence performed on mainQueue
Also, to answer why it's not presenting when Dispatch.main is not used is perhaps you are doing it on a thread which isn't main.
I am facing a strange problem -
I have a navigation Controller(NV) which presents the Root Controller(NVV1). NVV1 pushes another view controller on top of it called NVV2.
NVV2 presents a modal controller (MV1). MV1 presents another modal controller (MV2).
On a user action in MV2, it dismisses itself by calling self.presentingViewController dimissViewControllerAnimated. After dismiss, MV2 calls a method of MV1. That method in MV1 does a network call and now (tries to) dismiss itself by calling self.presentingViewController dismissViewControllerAnimated to show NVV2 and calls a method of NVV2 as well.
The problem I am facing MV1 is not getting dismissed. Am I missing something? The error that I am getting -
attempt to dismiss modal view controller whose view does not currently appear. self = <MV1: 0xaebbfc0> modalViewController = <UINavigationController: 0x1976ac10>
I think your problem is that you are calling dismissViewControllerAnimated on the presentingViewController instead of self. Normally, this would be fine, but if you look at the Discussion section for dismissViewControllerAnimated a presented view controller will automatically forward to it's presenting view controller.
In this case, you may have found a minor bug where multiple presented view controllers don't check if they are also a presenting view controller before forwarding the message. If you change the calls to self, you might be able to prevent MV1 from forwarding the message to NVV2.
Seems like you're attempting to dismiss the MV1 view before MV2 has been dismissed. Either try adding a slight delay before dismissing MV2:
[self performSelector: #selector(dismissMV1) withObject:nil afterDelay: 0.1f];
Or better yet, you could try calling the dismissMV1 method from the MV2 method by using a completion block (available in iOS 5.0 and above) to trigger dismissMV1 once the dismissal is complete:
[self.presentingViewController dismissViewControllerAnimated:YES completion: ^{
[self.presentingViewController dismissMV1];
}];
I solved this problem by directly calling dismissViewController on NVV2, that is in MV2, I called [self.presentingViewController.presentingViewController dismissControllerAnimated].
That solved my problem in this case as I wanted to move from MV2 to NVV2 directly. Ideally, I would have liked to move to MV1, do some network operation and indicate the user that a network operation is ongoing on MV1, but in my case showing that on NVV2 also works fine.
The original question, though, still remains unanswered - how to move from MV2 to MV1 and then to NVV2 directly. I think this might be a minor bug because according to the docs, if you call dimissControllerAnimated on a view it sends the message to its parent as well - essentially, the call going all the way to NVV2 which is undesirable. I do not yet have a solution for this.
How can I elegantly dismiss view if at the moment of dismissing I don't know if that view is fully created ?
Here is my dilemma. The view is showing some data that is requested from the server. Therefore I asynchronously send server request from viewDidLoad and wait in two observers if request failed or not.
For "dismissing" view I use exit segue (storyboard).
So there are two scenarios:
Server-side error occurred after viewDidAppear was called. Then I can call performSegue or self.navigationController.pop... and everything is nice.
Server-side error occurred before viewDidAppear was called. How can I navigate user to previous view in this case ?
For now I just put server request to viewDidAppear but it's very uncool because user looks at empty view for significant amount of time :(
How would you deal with this ?
UPDATED
Make a flag, i.e.
#property (nonatomic, assign) BOOL requestFailedOrViewDidAppear;
set it to NO at start of viewDidLoad or in init method. Then in your server-side error handler do something like this:
if (requestFailedOrViewDidAppear) {
//return to previous view
}
else {
requestFailedOrViewDidAppear = YES;
}
Put the same code into viewDidAppear, too.
That is more elegant in terms of implementation. However if you want it to be elegant in terms of UI/UX, replace "//return to previous view" with code that creates and shows a UIAlertView explaining the error. Then, when user dismisses that alert view, you can return to the previous view.
Assuming that your user will expect some info to be shown on the view that's being presented, If the request fails before viewDidAppear, I would wait until view appears, show the error, and dismiss the view. Else if request fails after viewDidAppear, I would just show the error and dismiss the view on the request observing method. That would be my way of dealing with this problem.
Turned out there is int flag in UIViewController that is called _appearState. When it's 2 that means view has appeared.
We have a container view controller and want to be able to call "PerformSelector" on one of the "sub" view controllers in that container, right after starting a transition, i.e.
[self navigateSubViewControllerTo:newSubViewController];
... some time later, elsewhere in the stack, a selector will be performed on the top visible VC
[subViewController performSelector:#selector(foo)];
The call to transitionFromViewController happens in navigateSubViewController.Unfortunately, since transitionFromViewController happens asynchronously, we are finding that the performSelector call gets applied to the "before" sub view controller, not newSubViewController. I.e. it is happening before the transition happens.
Any thoughts on how to have performSelect not happen until the sub view controller transition happens?
You can just call performSelector in the completion block of
transitionFromViewController:toViewController:duration:options:animations:completion:
UIViewController provides a callback beginAppearanceTransition:animated: which is exactly for this purpose. Simply implement it in your subviewcontrollers and your good to go ;)
I need to save my data by calling a method I already have when a viewController is popped using the back button created by the UINavigationController.
Is there a way to get a delegate callback or a notification I didn't see anything in the documentation?
In your viewWillDisappear method, you can check the property:
[self isMovingFromParentViewController]
to find out if the view is disappearing as a result of being popped off the stack or not.
You will be notified that the view will be disappearing, with the view controller method viewWillDisappear:, however, this will be called each time the view is moved offscreen, whether that means the controller is popped or a new controller is pushed, or whatever else may cause your view to disappear.
Perhaps a better design would be to save your data in your controllers dealloc method. Normally, a navigation controller is the owner of a view pushed into it's stack, so popping it usually causes it to deallocate. This isn't always the case though and depends on how you've written your app.