I am creating a custom layout of the popover that iOS provides. I have subclassed the UIPopoverBackgroundView and got it to draw the background for my popover correctly. My problem is now that UIPopoverController creates an inner shadow on the popover affecting the contentViewController of the popover. I want to remove this inner shadow, so only the content of my contentViewController is displayed.
This is how the popover currently looks, with a UILabel to demonstrate the effect on the contentViewController.
Is there any way to remove this inner shadow?
Support for this was added in ios6.0 with the following call:
+ (BOOL)wantsDefaultContentAppearance
Link to documentation: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIPopoverBackgroundView_class/Reference/Reference.html
Since there is no elegant way to do this and since I do not want to rewrite the entire UIPopoverController just to do this, I've created a simple hack that removes the inner shadow on the popover by traversing the UIView structure. The hack is a category on UIPopoverController and I just put it in the files for my subclass of UIPopoverBackgroundView. So here's the code:
@interface UIPopoverController(removeInnerShadow)
- (void)removeInnerShadow;
- (void)presentPopoverWithoutInnerShadowFromRect:(CGRect)rect
inView:(UIView *)view
permittedArrowDirections:(UIPopoverArrowDirection)direction
animated:(BOOL)animated;
- (void)presentPopoverWithoutInnerShadowFromBarButtonItem:(UIBarButtonItem *)item
permittedArrowDirections:(UIPopoverArrowDirection)arrowDirections
animated:(BOOL)animated;
@end
@implementation UIPopoverController(removeInnerShadow)
- (void)presentPopoverWithoutInnerShadowFromRect:(CGRect)rect inView:(UIView *)view permittedArrowDirections:(UIPopoverArrowDirection)direction animated:(BOOL)animated
{
[self presentPopoverFromRect:rect inView:view permittedArrowDirections:direction animated:animated];
[self removeInnerShadow];
}
- (void)presentPopoverWithoutInnerShadowFromBarButtonItem:(UIBarButtonItem *)item
permittedArrowDirections:(UIPopoverArrowDirection)arrowDirections
animated:(BOOL)animated
{
[self presentPopoverFromBarButtonItem:item permittedArrowDirections:arrowDirections animated:animated];
[self removeInnerShadow];
}
- (void)removeInnerShadow
{
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
for (UIView *windowSubView in window.subviews)
{
if ([NSStringFromClass([windowSubView class]) isEqualToString:@"UIDimmingView"])
{
for (UIView *dimmingViewSubviews in windowSubView.subviews)
{
for (UIView *popoverSubview in dimmingViewSubviews.subviews)
{
if([NSStringFromClass([popoverSubview class]) isEqualToString:@"UIView"])
{
for (UIView *subviewA in popoverSubview.subviews)
{
if ([NSStringFromClass([subviewA class]) isEqualToString:@"UILayoutContainerView"])
{
subviewA.layer.cornerRadius = 0;
}
for (UIView *subviewB in subviewA.subviews)
{
if ([NSStringFromClass([subviewB class]) isEqualToString:@"UIImageView"] )
{
[subviewB removeFromSuperview];
}
}
}
}
}
}
}
}
}
@end
When I want to display my popover I just call the presentPopoverWithoutInnerShadowFromRect: and presentPopoverWithoutInnerShadowFromBarButtonItem: methods instead of the standard ones.
NOTE: Remember to #import <QuartzCore/QuartzCore.h> for the code to work
I juest created my own version for the project I'm working on.
Basically you should use your own custom backgroundClass for popover and in this class you should define:
- (void)willMoveToWindow:(UIWindow *)newWindow {
[super willMoveToWindow:newWindow];
if ([UIPopoverBackgroundView respondsToSelector:@selector(wantsDefaultContentAppearance)])
return;
if (![[self class] wantsDefaultContentAppearance]) {
for (UIView *view in self.superview.subviews) {
for (UIView *subview in view.subviews) {
if (subview.layer.cornerRadius != 0.0) {
subview.layer.cornerRadius = 0.0;
for (UIView *subsubview in subview.subviews) {
if (subsubview.class == [UIImageView class])
subsubview.hidden = YES;
}
}
}
}
}
}
Should be pretty much bulletproof for iOS updates.
Although I agree in principal that the proper way to handle this is to roll your own Popover, in this case it's a non-issue for newer versions of the OS. Do I really want to build and maintain my own popover implementation only to support an OS that will eventually be irrelevant? If you really want to, consider some of the free open source implementations on the web.
Personally I investigated the methods suggested here and came up with my own using this page as a starting point (thanks!). It works for both scenarios (with or without a navigation bar) and is a bit safer in my opinion.
Instead of adding a method to the UIPopoverController, I added a routine to my UIPopoverBackgroundView to look for the offending views using a RELATIVE route, rather than ABSOLUTE. In short, since the code has a direct reference to the UIPopoverBackgroundView (self), it can navigate up (superview) and then down (subviews).
The view trees look like this in both scenarios:
With UINavigationBar:
Without UINavigationBar:
The two views that we are interested in are the UILayoutView and UIImage views that are bold and underlined in each graph. We can get a reference to these starting from the UIPopoverBackgroundView using the code below (assumes ARC). I execute this from layoutSubviews in my UIPopoverBackgroundView implementation.
// Helper method for traversing child views based solely on class types
UIView* (^__unsafe_unretained __block traverseSubviews)(UIView*, NSArray*) = ^(UIView *root, NSArray* nodeTypes) {
NSString *typeName = [nodeTypes objectAtIndex:0];
for (UIView *subView in root.subviews) {
if ([NSStringFromClass([subView class]) isEqualToString: typeName]) {
if (nodeTypes.count == 1)
return subView;
else
return traverseSubviews(subView, [nodeTypes subarrayWithRange:NSMakeRange(1, nodeTypes.count - 1)]);
}
}
return (UIView*)nil;
};
// Find the subviews of interest, taking into account there could be a navigation bar
UIView *layoutView = traverseSubviews([self superview], @[@"UIView", @"UILayoutContainerView"]);
if (traverseSubviews(layoutView, @[@"UINavigationBar"])) {
layoutView = traverseSubviews(layoutView, @[@"UILayoutContainerView"]);
}
UIView *imageView = traverseSubviews(layoutView, @[@"UIImageView"]);
// Remove the default content appearance
layoutView.layer.cornerRadius = 0;
[imageView removeFromSuperview];
I use a block here for doing the traversal of the subviews to keep the code concise. It takes a view as a starting point and an array of class names. The array of classnames is the sequence of view classes that I expect where index 0 is the parent of index 1 and index 1 is the parent of index 2, etc. It returns the view represented by the last item in the array.
I don't believe that there is an elegant/supported way to achieve that using Apple's standard UIPopover. However, you could make your own custom popover class fairly easily. There are quite a few examples of how to do so both here on SO and tutorials on the wider web (even a few ready-to-download solutions). Just put 'custom uipopover' into Google...
For FrankZp
This works fine with popOvers for views, which are not embedded in NavigationControllers. As soon as the ViewController (which is embedded in the NavigationController) is used for the popover the shadow is back again. Is there any solution to this?
Here is the modification for the UINavigationController:
- (void)removeInnerShadow
{
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
for (UIView *windowSubView in window.subviews) {
if ([NSStringFromClass([windowSubView class]) isEqualToString:@"UIDimmingView"] {
for (UIView *dimmingViewSubviews in windowSubView.subviews) {
for (UIView *popoverSubview in dimmingViewSubviews.subviews) {
if([NSStringFromClass([popoverSubview class]) isEqualToString:@"UIView"]) {
for (UIView *subviewA in popoverSubview.subviews) {
if ([NSStringFromClass([subviewA class]) isEqualToString:@"UILayoutContainerView"]) {
subviewA.layer.cornerRadius = 0;
}
for (UIView *subviewB in subviewA.subviews) {
if ([NSStringFromClass([subviewB class]) isEqualToString:@"UILayoutContainerView"]) {
for (UIView * subviewC in subviewB.subviews) {
if ([NSStringFromClass([subviewC class]) isEqualToString:@"UIImageView"] ) {
[subviewC removeFromSuperview];
}
}
}
if ([NSStringFromClass([subviewB class]) isEqualToString:@"UIImageView"] ) {
[subviewB removeFromSuperview];
}
}
}
}
}
}
}
}
}
来源:https://stackoverflow.com/questions/8744824/remove-the-inner-shadow-that-uipopovercontroller-creates