When I try to dismiss my UIImagePickerController, it crashes the app. the error is: "Terminating app due to uncaught exception 'UIApplicationInvalidInterfaceOrientation', reason: 'preferredInterfaceOrientationForPresentation must return a supported interface orientation!'"
I have the preferred interface orientation set in my view controller.
-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationPortrait;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return UIInterfaceOrientationIsPortrait(interfaceOrientation);
}
- (BOOL) shouldAutorotate {
return YES;
}
Here is the method I'm calling to bring up the camera, this works fine for adding the camera, but like I said, crashes when I try to remove the camera.
-(IBAction)addCamera:(id)sender
{
self.cameraController = [[UIImagePickerController alloc] init];
self.cameraController.sourceType = UIImagePickerControllerSourceTypeCamera;
self.cameraController.cameraViewTransform = CGAffineTransformScale(self.cameraController.cameraViewTransform,
1.13f,
1.13f);
self.cameraController.showsCameraControls = NO;
self.cameraController.navigationBarHidden = YES;
self.wantsFullScreenLayout = YES;
ar_overlayView = [[UIView alloc] initWithFrame:CGRectZero];
self.view = ar_overlayView;
[self.cameraController setCameraOverlayView:ar_overlayView];
[self presentViewController:cameraController animated:NO completion:nil];
[ar_overlayView setFrame:self.cameraController.view.bounds];
}
-(IBAction)back:(id)sender
{
[ar_overlayView removeFromSuperview];
[cameraController dismissViewControllerAnimated:NO completion:nil];
}
Alright, found the solution, it was really simple, I just changed my back method to:
[self dismissModalViewControllerAnimated:YES];
Now my camera goes away and I can see my original view when I press the back button.
I still haven't figured out what was causing the original problem as I've gone through the info.plist and the methods for supported orientations, but this accomplishes what I wanted.
I'm still curious as to what was causing the error though if anyone has any ideas.
You can try remove this method:
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
It will fix it.but sometimes it will lead to reduce stateBar height.
You cannot remove a UIViewController's main view from its superview.
Instead of this:
self.view = ar_overlayView;
Try this:
[self.view addSubview:ar_overlayView];
Then you will be able to remove it from the superview correctly.
You should be using the didFinishPickingMedieWithInfo method similar to below and use [self dismissModalViewControllerAnimated:YES];
-(void) imagePickerController:(UIImagePickerController *) picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
if (picker.sourceType == UIImagePickerControllerSourceTypeCamera) {
NSLog(#"Camera");
}
else {
NSLog(#"Album");
}
[self dismissModalViewControllerAnimated:YES];
}
I don't think you need to add ar_overlayView to the current view yourself if it's a camera overlay.
Here's what your code is doing now:
Add ar_overlayView to the current view
Add ar_overlayView as the camera's overlay view
Show the camera view (modal)
At this point, ar_overlayView is being displayed twice. When you send it the removeFromSuperview message on dismissing the camera view, it might be getting confused since it's in two view hierarchies at the same time.
Skipping the self.view = ar_overlayView; or [self.view addSubview:ar_overlayView]; lines should fix the problem.
Also, dismissViewControllerAnimated:completion: should be sent to the same object that presentViewController:animated:completion was called on (self in this case):
-(IBAction)addCamera:(id)sender
{
// -- snip -- //
ar_overlayView = [[UIView alloc] initWithFrame:self.cameraController.view.bounds];
[self.cameraController setCameraOverlayView:ar_overlayView];
[self presentViewController:self.cameraController animated:NO completion:nil];
}
-(IBAction)back:(id)sender
{
[self dismissViewControllerAnimated:NO completion:nil];
}
Related
iOS 7 using UIImagePickerController to take picture twice, at second time will show a static image covered the camera, how to reset the camera.
I'm try to take picture one by one, and keep take 5 pictures.
It works on iOS6.
on iOS7, it works fine at the first time, but when it take picture at second time, it will show a static dark image on screen, how to clear or reset it, although take picture works, but user can't see what will capture with camera.
bool fistTimeToShow = YES;
-(void) viewDidAppear:(BOOL)animated{
if (fistTimeToShow) {
fistTimeToShow = NO;
self.imagePickerController = nil;
[self tackImage];
}
}
-(void)tackImage{
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
self.imagePickerController = [[UIImagePickerController alloc]init];
self.imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
self.imagePickerController.showsCameraControls = YES;
self.imagePickerController.delegate = self;
[self presentViewController:self.imagePickerController animated:NO completion:nil];
}
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
NSLog(#"====== imagePickerController:didFinishPickingMediaWithInfo ======");
[self.imagePickerController dismissViewControllerAnimated:NO completion:nil];
//...deal with the image capture...
self.imagePickerController = nil;
[self tackImage];
}
Update
I change the dismiss function to put the [self tackImage]; in block. And now it always show the fist image took.
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
NSLog(#"====== imagePicker: didFinish ======");
self.imagePickerController = nil;
[self dismissViewControllerAnimated:NO completion: ^{
[self tackImage];
}];
}
I'm trying to find a way to clear the image. But I don't know where the image saved yet.
Update2
use
[self performSelector:#selector(presentCameraView) withObject:nil afterDelay:1.0f];
and function
-(void)presentCameraView{
[self presentViewController:self.imagePickerController animated:NO completion:nil];
}
to replace. [self presentViewController:self.imagePickerController animated:NO completion:nil]; it works on my device anyway, but I don't even know why.
Update3
I have set the userInteractionEnabled to NO when Delay:1.0f to avoid other problems, and may be also need set the navigationBar and tabBar for specifically use.
I had exactly the same issue under iOS 7 using Xamarin.iOS.
What has helped, is adding GC.Collect() in didFinishPickingMediaWithInfo method.
In C# GC.Collect() "cleans up" the memory from unused/disposed objects.
For sure, there's no direct equivalent for that in Obj-C, but that may shed some light on your problem also (it could be memory-related).
I'm working on iOS 6.
My application has a standard navigation controller with embedded a CustomViewController.
In this controller I create a modal view like this:
-(IBAction)presentModalList:(id)sender {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
StationsListViewController *list = [storyboard instantiateViewControllerWithIdentifier:#"StationsListViewController"];
[list setStationsData: [self.stationsData allValues]];
[self presentModalViewController:list animated:YES];
}
The modal controller show perfectly but dismissing generates a warning.
The dismiss method in this controller is:
-(IBAction)backToMap
{
[self dismissModalViewControllerAnimated:YES];
}
The warning generated is Warning:
Attempt to dismiss from view controller < UINavigationController: 0x1ed91620 > while a presentation or dismiss is in progress!
Any clues about that?
Thanks
I realise this is a late answer but maybe this will help someone else looking for a solution to this, here is what I did:
-(IBAction)backToMap
{
if (![[self modalViewController] isBeingDismissed])
[self dismissModalViewControllerAnimated:YES];
}
For me, i found that line of code was being called multiple times, I couldn't find out why so this was the easiest fix.
Thanks JDx for getting me on the right track. I adapted it to form this solution, which will remove the warning without using functions that are deprecated in iOS 6:
-(IBAction)backToMap
{
if (![self.presentedViewController isBeingDismissed]) {
[self dismissViewControllerAnimated:YES completion:^{}];
}
}
Targeting iOS6, this is what worked for me:
if (![self.presentedViewController isBeingDismissed])
[self.presentedViewController dismissViewControllerAnimated:YES
completion:nil];
I found this approach to be unreliable - say one case in five I'd still see the error.
My solution was to use the completion block to set a flag which controls whether or not it's safe to dismiss - that way you don't need to check whether or not the view is being dismissed.
-(IBAction)presentModalView:(id)sender {
:
self.canDismiss = NO;
[self presentViewController:aVC animated:YES completion:^{
self.canDismiss = YES;
}];
:
}
In the bit of code where the dismiss occurs, just check the flag:
-(void)dismisser {
:
if (self.canDismiss) {
[self dismissViewControllerAnimated:YES completion:nil];
}
:
}
Hey presto, no more errors!
You can do whatever you want after the completion of the dismiss method as:
-(IBAction)backToMap
{
[self dismissViewControllerAnimated:YES
completion:^{
//Do something here
}];
}
I have made an app that implements the iPhone's camera.
When the user finishes picking their image, the status bar reappears!
How would I make sure that the status bar stays hidden?
Here is my code:
-(IBAction)pickImage:(id)sender {
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
[self presentModalViewController:picker animated:YES];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[picker dismissModalViewControllerAnimated:YES];
background.image = [info objectForKey:#"UIImagePickerControllerOriginalImage"];
}
If i am doing anything wrong, please point it out!
Thanks,
Rafee
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
You may opt for another animation style if at all.
In iOS 7, there is a method on UIViewController, "prefersStatusBarHidden". To hide the status bar, add this method to your view controller and return YES:
- (BOOL) prefersStatusBarHidden
{
return YES;
}
In this case,We are using 2 steps
In first step:
Add in info.plist: "View controller-based status bar appearance" with value "NO"
In Second step: Use/call this code with delegate of UIImagePickerController
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if([navigationController isKindOfClass:[UIImagePickerController class]])
[[UIApplication sharedApplication] setStatusBarHidden:YES];
}
With iOS 7 and later, you can use the following code to hide and unhide the status bar,
#interface ViewController()
#property (nonatomic, getter=isStatusBarHidden) BOOL statusBarHidden;
#end
#implementation ViewController
... other codes
- (BOOL)prefersStatusBarHidden {
return self.isStatusBarHidden;
}
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation {
return UIStatusBarAnimationFade;
}
- (void)hideStatusBar {
self.statusBarHidden = YES;
[self setNeedsStatusBarAppearanceUpdate];
}
- (void)showStatusBar {
self.statusBarHidden = NO;
[self setNeedsStatusBarAppearanceUpdate];
}
#end
There seems to be a bug in the dismiss mechanism of UIViewController associated with UIImagePicker, with a sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum.
The moment of the call to dismissModalViewController (plus the method with completion:) the UIApplication's status bar hidden property instantly changes from YES to NO, and it is drawn at the moment of stepping over dismiss...
This is only really obvious for apps that use a full-screen view. My current app project does, plus I control the frame of the view controller's view before presenting, so the UIImagePicker is NOT full screen. This made the bug VERY obvious. I spent 4-5 hours determining the cause, and this was the final certain conclusion, and the bug does NOT occur for sourceType Camera nor PhotoLibrary.
So if you want a perfectly full-screen app and want to present a bug-free UIImagePicker, avoid UIImagePickerControllerSourceTypeSavedPhotosAlbum
Grand central dispatch is your friend, using this method you won't see the status bar appear at all when the picker is displayed or afterwards
- (void)hideStatusBar
{
if ([self respondsToSelector:#selector(setNeedsStatusBarAppearanceUpdate)])
{
[self performSelector:#selector(setNeedsStatusBarAppearanceUpdate)];
}
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
}
- (BOOL)prefersStatusBarHidden {
return YES;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self hideStatusBar];
double delayInSeconds = 0.2;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self hideStatusBar];
});
}
I am trying to open up a new view (UnprocessedPhotoViewController) immediately after the delegate function for my UIImagePickerController returns a "didFinishPickingImage".
Unfortunately, it appears that I can either open the UIImagePickerController modal view, or switch to the UnprocessedPhotoViewController as a modal, but not both sequentially.
In the code below, a button press activates the pickPhoto IBAction. This code activates the UIImagePickerController successfully. After the user selects an image, the didFinishPickingImage delegate function is called, which stores the image to a variable, attempts to close the UIImagePickerController modal and open the UnprocessedPhotoViewController in a new modal.
Note: If I comment out the ImagePicker and run "showPhoto" directly, the UnprocessedPhotoViewController shows successfully. Also, if I make a new button to launch either view it works successfully, but I am unable to launch the views sequentially. I would expect that after a user selected the image, the new view would be launched which would allow the user to process the image.
What is the correct way to guarantee that the ImagePicker modal closes and then to open the UnprocessedPhotoViewController?
Thanks!!!
Code:
- (IBAction)pickPhoto:(id)sender{
//TODO: To be replaced with the gallery control launching code
// Load Image Selection Code
self.imgPicker = [[UIImagePickerController alloc] init];
self.imgPicker.delegate = self;
self.imgPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
[self presentModalViewController:self.imgPicker animated:YES];
}
// Dummy function assumes you either picked (or took a picture =D) of Angie and moves you right to the unprocessed viewing screen.
- (void) showPhoto{
// Start new view controller
[self dismissModalViewControllerAnimated:YES];
UnprocessedPhotoViewController *upViewController = [[UnprocessedPhotoViewController alloc] initWithNibName:#"UnprocessedPhotoViewController" bundle:nil];
upViewController.imageView.image = selectedImage;
upViewController.delegate = self;
upViewController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:[upViewController animated:YES]];
[upViewController release];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)img {
selectedImage = img;
[[picker parentViewController] dismissModalViewControllerAnimated:YES];
[self dismissModalViewControllerAnimated:YES];
[self showPhoto];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[[picker parentViewController] dismissModalViewControllerAnimated:YES];
}
Implement viewDidAppear in your root view controller and based on member data decide to call showPhoto or not. The following example simply re-presents the UIImagePicker but any new modal view will work. viewDidAppear is called any time your root view controller’s view appears so you have to make sure the context is known when it is called. But it is the deterministic way to know that the modal view controller is gone.
- (IBAction) showPicker: (id) sender
{
UIImagePickerController* picker = [[[UIImagePickerController alloc] init] autorelease];
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
picker.delegate = self;
picker.allowsEditing = YES;
[self presentModalViewController:picker animated:YES];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
imageChosen = YES;
[self dismissModalViewControllerAnimated:YES];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
imageChosen = NO;
[self dismissModalViewControllerAnimated:YES];
}
- (void) viewDidAppear: (BOOL) animated
{
if ( imageChosen )
{
[self showPicker: self];
}
}
Another way to do this is to wrap the built-in dismissal animation with your own animation, and then catch the animationDidStop "event." This creates a composite animation, so when the built-in animation is done, your (empty) wrapper animation finishes you and alerts you that you're done.
This is slightly cleaner than the other answer here, IMO, as you don't need to keep a state variable or override viewDidAppear: (in my app, the view controller presenting the picker is quite a few objects removed from the utility code that handles picker management, and that would mean any view controller using my shared utility would have to override viewDidAppear:, or else fail to work):
-(void)dismissalAnimationDone:(NSString*)animationID
finished:(BOOL)finished context:(void*)context
{
UIImage* image = (UIImage*) context;
// present controllers as you please
}
-(void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info
{
[UIView beginAnimations:#"dismissal wrapper" context:image];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(dismissalAnimationDone:finished:context:)];
[self.delegate dismissModalViewControllerAnimated:YES];
[UIView commitAnimations];
}
I want to dismiss a FormSheetPresentation modal view controller when the user taps outside the modal view...I have seen a bunch of apps doing this (ebay on ipad for example) but i cant figure out how since the underneath views are disabled from touches when modal views are displayed like this (are they presenting it as a popover perhaps?)...anyone have any suggestions?
I'm a year late, but this is pretty straightforward to do.
Have your modal view controller attach a gesture recognizer to the view's window:
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
[recognizer release];
The handling code:
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
}
}
}
That's about it. HIG be damned, this is a useful and often intuitive behavior.
For iOS 8, you must both implement the UIGestureRecognizer, and swap the (x,y) coordinates of the tapped location when in landscape orientation. Not sure if this is due to an iOS 8 bug.
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// add gesture recognizer to window
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
recognizer.delegate = self;
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded) {
// passing nil gives us coordinates in the window
CGPoint location = [sender locationInView:nil];
// swap (x,y) on iOS 8 in landscape
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"8.0")) {
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
location = CGPointMake(location.y, location.x);
}
}
// convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil]) {
// remove the recognizer first so it's view.window is valid
[self.view.window removeGestureRecognizer:sender];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
#pragma mark - UIGestureRecognizer Delegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return YES;
}
The other apps are not using Modal Views if they allow the view to be dismissed by clicking outside of it. UIModalPresentationFormSheets cannot be dismissed this way. (nor, indeed can any UIModal in SDK3.2). Only the UIPopoverController can be dismissed by clicking outside of the area. It is very possible (though against Apple's iPad HIG) for the app developer to have shaded out the background screen and then displayed the UIPopoverController so that it looks like a UIModalPresentationFormSheets (or other UIModal View).
[...] UIModalPresentationCurrentContext style lets a view controller adopt the presentation style of its parent. In each modal view, the dimmed areas show the underlying content but do not allow taps in that content. Therefore, unlike a popover, your modal views must still have controls that allow the user to dismiss the modal view.
See the iPadProgrammingGuide on the developer site for more information (Page 46 -- "Configuring the Presentation Style for Modal Views")
The code above works great, but I would change the if statement to,
if (!([self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil] || [self.navigationController.view pointInside:[self.navigationController.view convertPoint:location fromView:self.navigationController.view.window] withEvent:nil]))
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
}
This makes sure you can still interact with the navigation bar, otherwise tapping in it dismisses the modal view.
Answer updated for iOS 8
Apparently, in iOS 8, the UIDimmingView has a tap gesture recognizer, which interferes with the initial implementation, so we ignore it and don't require it to fail.
This is the age of speed, so most are probably just copying the code above.. But, I suffer from OCD when it comes to code, unfortunately.
Here is a modular solution that uses Danilo Campos's answer with categories. It also solves an important bug that may occur if you are dismissing your modal through other means, as mentioned.
NOTE: The if statements are there because I use the view controller for both iPhone and iPad, and only the iPad needs to register/unregister.
UPDATE: The gist has been updated, since it didn't work properly with the awesome FCOverlay code, and it didn't allow gestures to be recognized in the presented view. Those issues are fixed.
Using the category is as easy as:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (self.presentingViewController) {
[self registerForDismissOnTapOutside];
}
}
- (void)viewWillDisappear:(BOOL)animated
{
if (self.presentingViewController) {
[self unregisterForDismissOnTapOutside];
}
[super viewWillDisappear:animated];
}
Copy paste this code in your ModalViewController :
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
//Code for dissmissing this viewController by clicking outside it
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
}
}
}
Very important: If you have any other way to close your modal popup window, don't forget to remove the tap gesture recognizer!
I forgot this, and got crazy crashes later on, since the tap recognizer was still firing events.
Accoring to Apple's iOS HIG, 1. the modal view doesn't have that ability to be dismissed without any input on itself; 2. use modal view in the situation that a user input is required.
Use UIPresentationController instead:
- (void)presentationTransitionWillBegin
{
[super presentationTransitionWillBegin];
UITapGestureRecognizer *dismissGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(dismissGestureTapped:)];
[self.containerView addGestureRecognizer:dismissGesture];
[[[self presentedViewController] transitionCoordinator] animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
} completion:nil];
}
- (void) dismissGestureTapped:(UITapGestureRecognizer *)sender{
if (sender.state==UIGestureRecognizerStateEnded&&!CGRectContainsPoint([self frameOfPresentedViewInContainerView], [sender locationInView:sender.view])) {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
}
Modified from LookInside example
This works for me for ios7 an 8 and navigation bar.
If you don't need the nav bar just remove location2 and second condition in the if statement after the pipes.
#MiQUEL this should work for you too
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location1 = [sender locationInView:self.view];
CGPoint location2 = [sender locationInView:self.navigationController.view];
if (!([self.view pointInside:location1 withEvent:nil] || [self.navigationController.view pointInside:location2 withEvent:nil])) {
[self.view.window removeGestureRecognizer:self.recognizer];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
Edit: You may also need to be a gesture recognizer delegate for this and other above solutions to work. Do it like so:
#interface CommentTableViewController () <UIGestureRecognizerDelegate>
set yourself as the delegate for the recognizer:
self.recognizer.delegate = self;
and implement this delegate method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
It is pretty doable.
Take a look here
https://stackoverflow.com/a/26016458/4074557
It is a NavigationController (modal) that auto dismiss for ipad (when you tap outside)
Use your viewcontroller inside of it.
Hope it helps.
I know it's late but consider using CleanModal (tested with iOS 7 and 8).
https://github.com/orafaelreis/CleanModal
You can use MZFormSheetController like this:
MZFormSheetController *formSheet = [[MZFormSheetController alloc] initWithSize:customSize viewController:presentedViewController];
formSheet.shouldDismissOnBackgroundViewTap = YES;
[presentingViewController mz_presentFormSheetController:formSheet animated:YES completionHandler:nil];
In Swift 2/Xcode Version 7.2 (7C68) the following code worked for me.
Attention: this code should be put in the ViewController.swift file of the presented FormSheet or Page Sheet, here: "PageSheetViewController.swift"
class PageSheetViewController: UIViewController, UIGestureRecognizerDelegate {
override func viewDidAppear(animated: Bool) {
let recognizer = UITapGestureRecognizer(target: self, action:Selector("handleTapBehind:"))
recognizer.delegate = self
recognizer.numberOfTapsRequired = 1
recognizer.cancelsTouchesInView = false
self.view.window?.addGestureRecognizer(recognizer)
}
func gestureRecognizer(sender: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
return true
}
func handleTapBehind(sender:UIGestureRecognizer) {
if(sender.state == UIGestureRecognizerState.Ended){
var location:CGPoint = sender.locationInView(nil)
// detect iOS Version 8.0 or greater
let Device = UIDevice.currentDevice()
let iosVersion = Double(Device.systemVersion) ?? 0
let iOS8 = iosVersion >= 8
if (iOS8) {
// in landscape view you will have to swap the location coordinates
if(UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation)){
location = CGPointMake(location.y, location.x);
}
}
if(!self.view.pointInside(self.view.convertPoint(location, fromView: self.view.window), withEvent: nil)){
self.view.window?.removeGestureRecognizer(sender)
self.dismissViewControllerAnimated(true, completion: nil)
}
}
}
}