I have a UINavigationController, in which I push a view controller with a UIModalPresentationPageSheet presentation style.
From within this page sheet's view controller, I present a view controller with UIModalPresentationFormSheet style.
When the user hits a Done button the the form sheet, I want to close out the form sheet and the page sheet.
In the action on the Done button:
-(IBAction)onDone:(id)sender
{
if(self->delegate && [self->delegate respondsToSelector:self->actionSelector])
{
[self->delegate performSelector:self->actionSelector withObject:[NSString stringWithString:self.textView.text]];
}
[self dismissViewControllerAnimated:YES completion:nil];
}
The delegate is the page sheet's view controller, and in the selector, I dismiss the page sheet:
[self dismissViewControllerAnimated:YES completion:nil];
When I run it, I get:
Warning: Attempt to dismiss from view controller <UINavigationController: 0xa9381d0> while a presentation or dismiss is in progress!
I can see why this is happening - because the selector is called before the form view is dismissed, but I don't know the best way around this.
I have tried removing the dismiss in onDone, and call dismiss for both in the selector call (with animated:NO for the form sheet), and it seems to function, but I don't know if this is the way that I should approach fixing it.
Try just calling dismissViewControllerAnimated:completion: on the page sheet. According to Apple's docs:
"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."
If that's not exactly the behavior you want, then you should use the completion handler argument in dismissViewControllerAnimated:completion: to pass in a block, then dismiss the other view controller from the completion handler, eg:
[formSheetViewController dismissViewControllerAnimated:YES completion:^{
[pageSheetViewController dismissViewControllerAnimated:YES competion:nil];
}
Although really, I think just dismissing the page sheet should do the trick for you. It's still good to understand how completion handlers work. It lets you do some work after the operation is done--very handy.
Related
I have created a custom UIWebView to present a login page. I am able to get this to show by adding it as a subview. But I would like to present this modally and programatically without the use of a storyboard!
Put it in an otherwise empty view controller, and then call
[self presentViewController: animated: completion:]
in the view controller you're wanting to present it from. When you're done, you can call
[self dismissViewControllerAnimated: completion:]
to get rid of it again. This message can be sent to either the presenter or the presented view controller, though in either case it ultimately is the presenter which responds.
I'm trying to create a small game where the user moves over an object and displays another view controller to display information. Upon pressing a button on the recently presented view controller, the view gets dismissed and shows the view controller I started off with. I've done this like this:
ViewController.m
OtherViewController *other = [self.storyboard instantiateViewControllerWithIdentifier:#"other"];
[self presentViewController:other animated:YES completion:nil];
OtherViewController.m
[self dismissViewControllerAnimated:YES completion:nil];
but when i dismiss it, the user starts from the beginning again. is there a way to save where the user is and continue from there?
You're presenting the new view controller as a modal. When you dismiss it, the previous view controller should be uncovered in it's previous state.
If it's not, then you need to look at the logic of your ViewController class.
You want your one time setup code in your viewDidLoad method. If you have code in your viewWillAppear:animated method, or your viewDidAppear:animated method that resets the state of your view controller then that is the problem.
I have a view controller which displays a carousel control (iCarousel). The view is rendered correctly and the carousel is displayed. Right after that a modal is displayed which allows the user to agree to certain terms. I want that once they agree I refresh the viewcontroller which contains the carousel control. Basically, I want to rotate the carousel to some random index.
- (IBAction)accept:(id)sender
{
NewsViewController *newsViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"NewsStoryboard"];
[newsViewController loadNews];
[newsViewController.view setNeedsDisplay];
[self dismissViewControllerAnimated:YES completion:nil];
}
The above code does call the loadNews and fetches it but the view is never refreshed.
What happens to the carousel should really be up to the view controller that manages it, not the modal view controller. Make the modal controller do its thing and return whatever data it collects to its parent. The parent (in this case, the carousel's controller) can then look at that data and decide what it needs to do next (refresh, for example).
The problem is this line:
NewsViewController *newsViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"NewsStoryboard"];
That is not the old view controller; it is a new, unused copy of that view controller. You need to create a line of communication from the modal view controller back to the existing view controller.
The typical way to do this is through a delegate, which you set when creating the modal view controller. If you look at the Xcode Utility template, you will see that it illustrates this architecture. The original view controller sets itself as the modal view controller's delegate, and the modal view controller is thus able to talk back to the original view controller as it is dismissed.
This is such an important thing to be able to do that I talk about it at length in my book:
http://www.apeth.com/iOSBook/ch19.html#_presented_view_controller
When a UIViewController presents another view controller the simplest way for the presented view controller to dismiss itself when it is done under iOS 5 is to call:
[[self presentingViewController] dismissViewControllerAnimated:YES completion:NULL];
On the other hand, Apple's View Controller Programming Guide says:
When it comes time to dismiss a presented view controller, the preferred approach is to let the presenting view controller dismiss it. In other words, whenever possible, the same view controller that presented the view controller should also take responsibility for dismissing it. Although there are several techniques for notifying the presenting view controller that its presented view controller should be dismissed, the preferred technique is delegation.
This has led some answers here to suggest sticking with making a new protocol and delegation even when only a very simple view controller is being presented. Why is this the documentation's "preferred technique" as opposed to the single line above? Is there any offsetting advantages to downside of a large increase in code written with the delegate/protocol technique? Obviously if there is information from the presented view controller that needs to be passed back to the presenting view controller delegation is a good technique. However, the information is the reason for delegation, not simply cleanly removing the presented view controller from the screen.
The same behavior could by achieved by [self dismissViewControllerAnimated:YES completion:nil] (before iOS 5 [self dismissModalViewControllerAnimated:YES]), as there's always at most one view controller presented (modally) at a time.
However, the point of the delegation pattern is that a single view controller could be presented in different ways such as modally or by being push to the navigation stack. That view controller doesn't know how it was presented (well, it could figure it out, but it should not care). The only thing it is supposed to do is to notify it's parent, i.e. the delegate, that its work is done. The delegate then decides how to remove the view controller (dismiss modal or pop from navigation stack etc.) or that the child should stay because the results of its work are insufficient. So the main idea is reusability of view controllers.
[[self presentingViewController] dismissViewControllerAnimated:YES completion:NULL];
That may be the simplest, but it's often not very useful.
Modal views typically need to return some information to their caller; that's why they're modal. In more traditional SDKs modal windows block their caller until the modal window is dismissed. The result of the modal window is then returned to the caller. E.g.:
int result = ShowModalDialog("Do you want to continue?");
if (result == kYes)
{
doSomething();
}
else
{
return;
}
In UIKit, -presentModalViewController: does not block, so you need some other mechanism for the modal view controller to return information to the presenting view controller. Typically that's done with delegation, though there are other ways (such as having the presenting controller handle the left and right UINavigationBar buttons).
If the modal view controller needs to return a value to its presenting view controller then that's done via delegation, and in that case it makes sense for the presenting controller to dismiss the modal controller after it has received the result. That's the original pattern.
I have a modal view which is launched from the current view controller, as
[self presentModalViewCOntroller:modalViewController animated:TRUE];
The modal view controller dismisses itself when someone hits a button.
[self dismissModalViewControllerAnimated:TRUE];
A couple of screens later, I attempt to swap the root view within the window. I do this all the time with no trouble. But in a certain case, when switching the one view within the window, the picker delegate method is being called on the modal view controller even thought it was dismissed a while ago.
This is very strange because the modal view controller is usually deallocated when dismissModalViewController is called.
Why is a view from the modal view controller being invoked?
It appears that someone, probably the window still has a reference. Are you supposed to do something else in addition to dismissModalViewController?
Thanks
DismissModalViewController should be enough. It does seem like you have a problem with some reference hanging around that you don't intend. Without seeing more code, I can't point to anything specific.