How to prevent UIPopoverPresentationController from being dismissed when clicking outside popover? - ios

In my universal iOS 8 app, I am presenting a popover using using UIPopoverPresentationController as seen below from prepareForSegue:
FavoriteNameViewController *nameVC = segue.destinationViewController;
UIPopoverPresentationController *popPC = nameVC.popoverPresentationController;
popPC.delegate = self;
And with this delegate method.
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller {
return UIModalPresentationNone;
}
In this particular case, I'm presenting a view controller that looks like an alert, but isn't.
Now my issue is that the user can click outside of this popover and it gets dismissed. There's no real problem with that except that's not how alerts work and I would like this to emulate an alert.
I see that UIPopoverControllerDelegate had a method called popoverControllerShouldDismissPopover:, but UIPopoverPresentationControllerDelegate doesn't have that method, and I believe I need to use the latter.

You need to set the popover controller's passthroughViews to nil and the view controller's modalInPopover to YES.

Try the following in your view
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
return YES;
}

Related

UIPopoverPresentationControllerDelegate calls on iPhone

I'm attempting a project using the Xcode (version 9.1 9B55) Master-Detail template, using one Storyboard for both iPhone and iPad.
I want to use the built in popover segue, which ideally will show a popover when the size class is appropriate, or a modal view controller when the size class is compact (iPhone in portrait).
What I am finding is that it works just fine for iPad, but when I run it on an iPhone, you can't touch outside the popover to dismiss as I would expect.
When on iPad, popoverPresentationControllerDidDismissPopover is called and all is well.
When on iPhone, the popoverPresentationControllerDidDismissPopover never gets called and you cannot dismiss the popover.
To reproduce, I did this:
Create new Master-Detail App
New - File. Cocoa Touch Class, called MyPopoverViewController
In Storyboard:
Create new view controller, change class to MyPopoverViewController.
On MasterViewController, add bar button item "Popover". Control-drag from this to MyPopoverViewController. Set segue Kind to "Present As Popover". Set Identifier to "thePopover".
In MasterViewController.h, add UIPopoverPresentationControllerDelegate:
#interface MasterViewController : UITableViewController <UIPopoverPresentationControllerDelegate>
In MasterViewController.m:
#import "MyPopoverViewController.h"
In viewDidLoad, comment out two lines which create the "Add Button".
In prepareForSegue:
} else if ([[segue identifier] isEqualToString:#"thePopover"]) {
NSLog(#"MVC prepareForSegue thePopover");
MyPopoverViewController *myPopoverController = segue.destinationViewController;
myPopoverController.popoverPresentationController.delegate = self;
}
Add three UIPopoverPresentationControllerDelegate delegate methods:
- (void) prepareForPopoverPresentation:(UIPopoverPresentationController *)popoverPresentationController {
NSLog(#"MVC prepareForPopoverPresentation");
}
- (void) popoverPresentationControllerDidDismissPopover:(UIPopoverPresentationController *)popoverPresentationController {
NSLog(#"MVC popoverPresentationControllerDidDismissPopover");
}
- (BOOL) popoverPresentationControllerShouldDismissPopover:(UIPopoverPresentationController *)popoverPresentationController {
NSLog(#"MVC popoverPresentationControllerShouldDismissPopover");
return TRUE;
}
I tried this also, but all it does is force popover in portrait mode (which I don't want); doesn't change the lack of popover delegate calls and doesn't allow us to dismiss popover:
-(UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller {
return UIModalPresentationNone;
}
I'm hoping there is something simple I'm missing here. I have uploaded a sample project here, which is exactly what I've described above:
https://github.com/johnstewart/MasterDetailPopoverTestProject
How do I allow iPhone to also dismiss popovers by touching outside the popover?
If I understand your question correctly your problem appears on iPhone 8 Plus in landscape mode.
In this situation, the presented popover actually is not a popover but a normal presented view. Visually it looks like a sheet that appears from the botton of the screen. In order to close such a view, you have to add your own button to do this.
If you want to show a real popover, you must implement:
adaptivePresentationStyleForPresentationController:traitCollection:
to return UIModalPresentationNone. Note the additional parameter traitcollection:. UIAdaptivePresentationControllerDelegate contains two similar methods. In your project you already implemented the method:
adaptivePresentationStyleForPresentationController:
Change this to the former method and everything should work.

Unwind segue doesn't dismiss adaptive popover presentation when not modal

Update for iOS 9 beta: Apple may have fixed this for iOS 9. If you work(ed) around this issue for iOS 8, make sure it also works correctly on iOS 9.
In storyboard, I've created a popover presentation segue to present a navigation and view controller from a button, as well as creating an unwind segue.
In portrait orientation, the modal (fullscreen) presentation is unwound/dismissed, as expected.
In landscape orientation, the unwind segue also gets called, however the popover presentation is not automatically dismissed.
Did I miss hooking something up? Do I have to dismiss the popover presentation myself?
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)__unused sender
{
if ([[segue identifier] isEqualToString:#"showSelectBookChapter"])
{
UINavigationController *navigationController = segue.destinationViewController;
if ([navigationController.topViewController isKindOfClass:[BIBLESelectViewController class]])
{
BIBLESelectViewController *selectViewController = (BIBLESelectViewController *)navigationController.topViewController;
selectViewController.initialBookChapterVerse = self.bookChapterVerse;
}
}
}
- (IBAction)unwindToBIBLEChapterViewController:(UIStoryboardSegue *)segue
{
if ([segue.identifier isEqualToString:#"unwindToBIBLEChapterViewController"]) {
if ([segue.sourceViewController isKindOfClass:[BIBLESelectViewController class]])
{
BIBLESelectViewController *sourceViewController = (BIBLESelectViewController *)segue.sourceViewController;
self.bookChapterVerse = sourceViewController.selectedBookChapterVerse;
[self.tableView reloadData];
}
}
}
Update:
After looking at gabbler's sample code, I've narrowed the problem down to popover dismissing fine in a Single View Application, but not in a Master-Detail Application.
Update 2:
Here's what the hierarchy looks like (omitting navigation controllers for simplicity's sake), in answer to the question Luis asked:
Split view controller
Master view controller
Detail view controller
Chapter view controller (modal page sheet)
Select view controller (the problematic popover that unwinds to chapter view controller, but doesn't dismiss)
As I mentioned in the previous update, I created an new master/detail template, and simply presented a popover directly from (a button in) the detail view. It won't dismiss.
I ran into this problem too. I present a View Controller modally (as a form sheet), from the Master View Controller (UISplitViewController). The problem only occurred on the iPad (probably the iPhone 6+ in landscape mode too, but I didn't check it). I ended up doing the following in my unwind action method (using Swift), and it works good.
if !segue.sourceViewController.isBeingDismissed() {
segue.sourceViewController.dismissViewControllerAnimated(true, completion: nil)
}
If you segue as a popover from a view controller embedded in a navigation controller, the corresponding unwind fails to dismiss the popover.
It's a bug in -[UINavigationController segueForUnwindingToViewController:fromViewController:identifier]. The embedding navigation controller is supposed to supply a segue that will dismiss the popover but it doesn't. The fix then is to override this and supply a working segue, which we can get from the embedded view controller.
Here's a partial solution that will only handle unwinding to the top view controller of the navigation stack:
#implementation MyNavigationController
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController
fromViewController:(UIViewController *)fromViewController
identifier:(NSString *)identifier
{
if (toViewController == self.topViewController && fromViewController.presentingViewController == self)
return [toViewController segueForUnwindingToViewController:toViewController
fromViewController:fromViewController
identifier:identifier];
else
return [super segueForUnwindingToViewController:toViewController
fromViewController:fromViewController
identifier:identifier];
}
#end
It works on iOS 8 for both landscape/portrait iPad and landscape/portrait iPhone. The logic should be robust enough to survive on iOS 9.
It is/must be a behavior of the popOver segue, in normal situations or regularly we need that the popOver keeps in view, if the segue show something important is annoying that we lost that information just because we rotate the device, I guess that that is the reason of that native behavior. So if we want for it to dismiss automaticly we have to make that behaivor by our own, this works:
in the method - (void)viewDidLoadin the detailViewController.m add this:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:[UIDevice currentDevice]];
then create this method:
- (void) orientationChanged:(NSNotification *)note{
UIDevice * device = note.object;
//CGRect rect = [[self view] frame];
switch(device.orientation)
{
default:
[self dismissViewControllerAnimated:YES completion:nil];
break; }}
You said that in a single view happens what you want, but I've never seen that behavior when I used popOvers.
mbeaty's fix is great but as others have pointed out, this bug seems to know be fixed in iOS 9 and it also doesn't work well for universal device design. I have adapted his answer to handle both situations. Here is the code:
#IBAction func yourUnwindSegue(segue: UIStoryboardSegue) {
if #available(iOS 9, *) {
return // bug fixed in iOS 9, just return and let it work correctly
}
// do the fix for iOS 8 bug
// access your SplitViewController somehow, this is one example
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let splitVC = appDelegate.window!.rootViewController as! YourSplitViewController
// if the source isn't being dismissed and the splitView isn't
// collapsed (ie both windows are showing), do the hack to
// force it to dismiss
if !segue.sourceViewController.isBeingDismissed() && splitVC.collapsed == false {
segue.sourceViewController.dismissViewControllerAnimated(true, completion: nil)
}
}
This first checks if iOS 9 is running and just exit as the bug seems to be fixed. This will prevent the multiple views getting dismissed issue. Also to make sure this fix is only done when the splitView is showing two windows (to make it happen only on iPads and iPhone 6 Plus in landscape as well as future devices) I added the check to make sure it is not collapsed.
I have not exhaustively check this but it seems to work. Also not that my app is set for a min of iOS 7, I don't know if this bug existed then so you may need to look into that if you support below iOS 8.

How to call function on UIPopViewController dismiss in iPad

I am using UIPopViewController in iPad application,I can dismiss popviewcontroller using tap on screen.But,I am trying to call function for dismiss,is that possible?How can I call function?Please help me.
If you are trying to dismiss the popover programatically, you can simply call:
[popover dismissPopoverAnimated:YES];
If you are trying to detect whether a user has dismissed a popover by tapping on the screen, you can make use of the UIPopoverControllerDelegate method popoverControllerDidDismissPopover:
First, set the viewController which presents the popover as delegate:
popover.delegate = self;
Then implement the method:
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
{
//Perform necessary action here.
}
Have a look at the UIPopoverControllerDelegate reference here.

iOS presentViewController is not working right

As part of my updating my apps to replace the deprecated presentModalViewController with presentViewController, I did some testing.
What I found was disturbing. Whereas presentModalViewController always works and there is no question about it working, I have found the presentViewController method often will not display my VC at all. There is no animation and it never shows up.
My loadView are called without problems, but the actual view does not appear.
So here is what I am doing:
User taps a button in my main view controller.
In the callback for that tap, I create a new view controller and display it as shown above.
The VC never appears (it is an intermittent problem though) but because this VC begins playing some audio, I know that its loadView was called, which looks like as follows.
My button-pressed callback is as follows:
- (void) buttonTapped: (id) sender {
VC *vc = [[VC alloc] init];
[self presentViewController: vc animated:YES completion: nil];
[vc release];
}
Here is my loadview in the VC class:
- (void) loadView {
UIView *v = [UIView new];
self.view = v;
[v release];
... create and addsubview various buttons etc here ...
}
Thanks.
Make sure the controller that calls the function has its view currently displayed (or is a parent to the one currently displayed) and it should work.

iPad dismiss popover with button within the popover itself

I have a button in my popover controller. I want to use it to dismiss the popover, so I am trying to access a method (dismissPopover) of the presenting view controller (the "root" view controller).
Note: the method to dismiss the popover is already set up and working, in the root VC, which is the delegate. If I call it it will dismiss the popover. I just need to access the method from the popover.
To do this I set up a property in the AppDelegate, and get an instance of the rootVC like this: self.rootController = (ViewController*)self.window.rootViewController;. Then I imported the root VC class and the AppDelegate to the popover's view controller's class, as below. Seems to give me access to the rootVC, and the methods, but the results do not fire the method. Any idea what I am missing here?
#import "ViewController.h"
#import "AppDelegate.h"
Action connected to button:
- (IBAction)dismissPopover:(id)sender {
//Checking the button works, it does:
NSLog(#"dismissPopover, from popover");
//Trying to get an instance of the rootViewController, the "presenting view controller"
ViewController *rootVC = [(AppDelegate *)[[UIApplication sharedApplication] delegate] rootController];
//trying to access the method in the rootVC that dismisses the popover
[rootVC dismissPopover];
//Tried the following code, does nothing:
//[self dismissPopoverAnimated:YES];
}
NOTE: I ended up abandoning the use of a popover for this as it became a bit over complicated. I tried loading my view controller into a UIView (so I could load the contents of a nib to a pop-up view). That also became a bit complicated. So, for now I am just building my desired interface in a UIView programatically. So far works great.
dismissPopoverAnimated: is a method of UIPopoverController class. so, you need a popover controller reference in your 'root' view controller.
MyRootViewController.myPopoverController = thePopover;
the button is in your 'root' view controller, and in it's action method:
[self.myPopoverController dismissPopoverAnimated:YES];
In iOS 8, you can dismiss the popover (if it's coming from a segue, at least) with dismissViewControllerAnimated:completion: from within the popover. Doesn't work in iOS 7 (or below), however.
Popover automatically dismissed when clicking outside it , as you order a button to dismiss it you can simply use the following code inside your dismissPopover method :
[self.popoverController dismissPopoverAnimated:YES];
you don't need all this tedious work !
[self dismissViewControllerAnimated:YES completion:nil];
is the solution;
you just need an IBoutlet or add target to your button and then call above line
I had the same problem
just do in your buttonClickMethod:
[yourPopoverController dismissPopoverAnimated:YES];
hope you help!
cheers

Resources