问题
We have common views that we use in our application in many locations inside of UINavigationControllers
. Occasionally the UINavigationController
s are inside of popover views. Now the views we put into the nav controllers modify their navigation controller's toolbar buttons and, in some cases, use custom buttons that we've created. We need to be able to figure out from the UIViewcontroller
itself if the view is inside of a popoverview so we can display the correctly colored buttons.
We can easily get the Navigation controller reference from the UIViewController, using UIViewController.navigationController
, but there doesn't seem to be anything for finding a UIPopoverController
.
Does anyone have any good ideas for how to do this?
Thanks!
回答1:
I was recently looking for a way to determine wether or not a view was being displayed in a popover. This is what I came up with:
UIView *v=theViewInQuestion;
for (;v.superview != nil; v=v.superview) {
if (!strcmp(object_getClassName(v), "UIPopoverView")) {
NSLog(@"\n\n\nIM IN A POPOVER!\n\n\n\n");
}
Basically you climb the view's superview tree looking to see if any of its superviews is a UIPopoverView. The one caveat here is that the class UIPopoverView is an undocumented private class. I'm relying on the fact that the class name won't change in the future. YMMV.
In your case:
theViewInQuestion = theViewControllerInQuestion.view;
I'd be interested to see if anyone else comes up with a better solution.
回答2:
As Artem said we have UIPopoverPresentationController
since iOS8. To determine if view is in popover you can use its .arrowDirection
property for example.
Check it in viewWillApear()
of presented view controller:
// get it from parent NavigationController
UIPopoverPresentationController* popoverPresentationVC = self.parentViewController.popoverPresentationController;
if (UIPopoverArrowDirectionUnknown > popoverPresentationVC.arrowDirection) {
// presented as popover
} else {
// presented as modal view controller (on iPhone)
}
回答3:
Here's another solution; define a protocol (e.g. PopoverSensitiveController) that has only one method:
#import "Foundation/Foundation.h" @protocol PopoverSensitiveController -(void) setIsInPopover:(BOOL) inPopover; @end
A view controller that wants to know if it is in a popover then defines a property isInPopover; for example:
#import #import "PopoverSensitiveController.h" #pragma mark - #pragma mark Interface @interface MyViewController : UIViewController { } #pragma mark - #pragma mark Properties @property (nonatomic) BOOL isInPopover; #pragma mark - #pragma mark Instance Methods ...other stuff... @end
Finally, in the splitView delegate (the assumption is that your app uses a split view controller):
#import "MySplitViewControllerDelegate.h" #import "SubstitutableDetailViewController.h" #import "PopoverSensitiveController.h" #pragma mark - #pragma mark Implementation @implementation MySplitViewControllerDelegate #pragma mark - #pragma mark UISplitViewControllerDelegate protocol methods -(void) splitViewController:(UISplitViewController *) splitViewController willHideViewController:(UIViewController *) aViewController withBarButtonItem:(UIBarButtonItem *) barButtonItem forPopoverController:(UIPopoverController *) pc { // Keep references to the popover controller and the popover button, and tell the detail view controller to show the button popoverController = [pc retain]; popoverButtonItem = [barButtonItem retain]; if ([[splitViewController.viewControllers objectAtIndex:1] respondsToSelector:@selector(showRootPopoverButtonItem:)]) { UIViewController *detailViewController = [splitViewController.viewControllers objectAtIndex:1]; [detailViewController showRootPopoverButtonItem:barButtonItem]; } if ([[splitViewController.viewControllers objectAtIndex:1] respondsToSelector:@selector(showRootPopoverButtonItem:)]) { UIViewController *detailViewController = [splitViewController.viewControllers objectAtIndex:1]; [detailViewController showRootPopoverButtonItem:barButtonItem]; } // If the view controller wants to know, tell it that it is a popover if ([aViewController respondsToSelector:@selector(setIsInPopover:)]) { [(id) aViewController setIsInPopover:YES]; } // Make sure the proper view controller is in the popover controller and the size is as requested popoverController.contentViewController = aViewController; popoverController.popoverContentSize = aViewController.contentSizeForViewInPopover; } -(void) splitViewController:(UISplitViewController *) splitViewController willShowViewController:(UIViewController *) aViewController invalidatingBarButtonItem:(UIBarButtonItem *) barButtonItem { // Tell the detail view controller to hide the button. if ([[splitViewController.viewControllers objectAtIndex:1] respondsToSelector:@selector(invalidateRootPopoverButtonItem:)]) { UIViewController *detailViewController = [splitViewController.viewControllers objectAtIndex:1]; [detailViewController invalidateRootPopoverButtonItem:barButtonItem]; } // If the view controller wants to know, tell it that it is not in a popover anymore if ([aViewController respondsToSelector:@selector(setIsInPopover:)]) { [(id) aViewController setIsInPopover:NO]; } // Now clear out everything [popoverController release]; popoverController = nil; [popoverButtonItem release]; popoverButtonItem = nil; } -(void) setPopoverButtonForSplitViewController:(UISplitViewController *) splitViewController { // Deal with the popover button UIViewController *detailViewController = [splitViewController.viewControllers objectAtIndex:1]; [detailViewController showRootPopoverButtonItem:popoverButtonItem]; // If the view controller wants to know, tell it that it is a popover (initialize the controller properly) if ([[splitViewController.viewControllers objectAtIndex:0] respondsToSelector:@selector(setIsInPopover:)]) { [(id) [splitViewController.viewControllers objectAtIndex:0] setIsInPopover:YES]; } }
Then where ever in the view controller you want to know if you are in a popover, simply use the isInPopover property.
回答4:
In iOS8 you can use popoverPresentationController property of UIViewController to check if it is contained in a popover presentation controller. From documentation, it returns: "The nearest ancestor in the view controller hierarchy that is a popover presentation controller. (read-only)"
回答5:
Modification of the accepted answer for iOS5.1 and newer:
for (UIView *v = self.view; v.superview != nil; v=v.superview) {
if ([v isKindOfClass:[NSClassFromString(@"_UIPopoverView") class]]) {
NSLog(@"\n\n\nIM IN A POPOVER!\n\n\n\n");
}
}
** NOTE **
See comments about the reliability of this code.
回答6:
My approach for this: (available with iOS 8 or greater)
- (BOOL)isContainedInPopover
{
UIPopoverPresentationController* popoverPresentationVC = self.parentViewController.popoverPresentationController;
return (popoverPresentationVC != nil);
}
Parent view controller will be the navigation controller which if inside a popover, will have a non-nil popoverPresentationController
property.
回答7:
By working with SpareTime's code I came to this, which works as expected. Nice code, nice solution:
Using the standard UISplitViewController example.
/* MasterViewController.h */
#import "UIPopoverViewDelegate.h"
@interface masterViewController : UITableViewController <UIPopoverViewDelegate>
@property (nonatomic) BOOL isInPopover;
@end
/* MasterViewController.m */
#import "MasterViewController.h"
@implementation MasterViewController
@synthesize isInPopover = _isInPopover;
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.isInPopover)
{
// Code for appearing in popover
}
else
{
// Code for not appearing in popover
}
}
@end
/* DetailViewController.h */
#import "UIPopoverViewDelegate.h"
@interface detailViewController : UIViewController <UISplitViewControllerDelegate>
@end
/* DetailViewController.m */
#import "DetailViewController.h"
@implementation detailViewController
- (void)splitViewController:(UISplitViewController *)splitController willHideViewController:(UIViewController *)viewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)popoverController
{
/* This method is called when transitioning to PORTRAIT orientation. */
UIViewController *hiddenViewController = [(UINavigationController *)viewController childViewControllers].lastObject;
if ([hiddenViewController respondsToSelector:@selector(setIsInPopover:)])
[(id <UIPopoverViewDelegate>)hiddenViewController setIsInPopover:YES];
}
- (void)splitViewController:(UISplitViewController *)splitController willShowViewController:(UIViewController *)viewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
/* This method is called when transitioning to LANDSCAPE orientation. */
UIViewController *shownViewController = [(UINavigationController *)viewController childViewControllers].lastObject;
if ([shownViewController respondsToSelector:@selector(setIsInPopover:)])
[(id <UIPopoverViewDelegate>)shownViewController setIsInPopover:NO];
}
@end
/* UIPopoverViewDelegate.h */
@protocol UIPopoverViewDelegate
@required
-(void)setIsInPopover:(BOOL)inPopover;
@end
回答8:
In case that someone else is still looking for a solution i came up with one good enough for me.
Just override this method
func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
(controller.presentedViewController as? YourViewControler).isPopover = false
return controller.presentedViewController
}
Here is an example of YourViewController
class AdvisorHomeFilterViewController: UIViewController {
// MARK: - Properties
var isPopover = true
}
If it is popover it will not call 'viewControllerForAdaptivePresentationStyle' method and it will stay true, in case it is not popover it will set it to false.
回答9:
I wanted to put up a button in the view if the view wasn't displayed in a popover. I know the width of the popover because I just set it. So i can test whether I'm on an iPad and if the width of the frame is the same as what I set.
- (void)viewWillAppear:(BOOL)animated {
[self setContentSizeForViewInPopover:CGSizeMake(400, 500)];
NSInteger frameWidth = self.view.frame.size.width;
//Let you go back to the game if on an iPod.
if ( ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) && !(frameWidth == 400) ) { ---code to display a button --}
回答10:
All these 'Exact Classname Matching Approaches' are very prone to fail and break at even the slightest changes Apple will make. Also doing one-char-vars and cryptic for-loops is not exactly a solution fitting to my style.
I use followingpiece of code:
- (BOOL) isInPopOver {
UIView *currentView = self.view;
while( currentView ) {
NSString *classNameOfCurrentView = NSStringFromClass([currentView class]);
NSLog( @"CLASS-DETECTED: %@", classNameOfCurrentView );
NSString *searchString = @"UIPopoverView";
if( [classNameOfCurrentView rangeOfString:searchString options:NSCaseInsensitiveSearch].location != NSNotFound ) {
return YES;
}
currentView = currentView.superview;
}
return NO;
}
回答11:
All the solutions above seems a little bit complicated. I'm using a variable called isInPopover
which I set to true if the view controller is presented in a popover. In the view controller in popoverControllerDidDismissPopover
or in viewWillDisappear
I set the boolean value to false. It does work and is very simple.
回答12:
Since self.popoverPresentationController
is created lazily in most recent iOS versions, one should check for nil-ness of self.popoverPresentationController.presentingViewController
, if not nil this would mean self
is currently presented in a popover.
回答13:
Swift 4 version (function can be added in extension UIViewController
):
func isInPopover() -> Bool {
guard UIDevice.current.userInterfaceIdiom == .pad else { return false }
var checkingVC: UIViewController? = self
repeat {
if checkingVC?.modalPresentationStyle == .popover {
return true
}
checkingVC = checkingVC?.parent
} while checkingVC != nil
return false
}
来源:https://stackoverflow.com/questions/4191840/determine-if-a-view-is-inside-of-a-popover-view