logo

UIPopoverBackgroundView

With iOS5 a number of new features abound for us programmers including the ability to easily subclass and restyle the standard UI functions.  I spent some time today and learned how to style a UIPopoverController and it took a bit of work because the documentation seemed incomplete to me *EDIT* This is because you HAVE to include the correct header file*EDIT* so here is my solution.

When you create your popoverviewcontroller You must set it’s popoverBackgroundViewClass to your own custom class as such:

if ([popoverController respondsToSelector:@selector(popoverBackgroundViewClass)]) {
popoverController.popoverBackgroundViewClass = [CustomPopoverBackgroundView class];
}

Then your subclass CustomPopoverBackgroundView must conform to a couple of classes, and include a custom drawn image for the new UIPopoverController Background.  I have added a UIImageView just for that.  So your .h file looks like this:


#import <UIKit/UIKit.h>#import <UIKit/UIPopoverBackgroundView.h> UIKIT_CLASS_AVAILABLE(5_0)
@interface ComicPopoverBackgroundView : UIPopoverBackgroundView {
UIImageView *_imageView;
}
/* The arrow offset represents how far from the center of the view the center of the arrow should appear. For `UIPopoverArrowDirectionUp` and `UIPopoverArrowDirectionDown`, this is a left-to-right offset; negative is to the left. For `UIPopoverArrowDirectionLeft` and `UIPopoverArrowDirectionRight`, this is a top-to-bottom offset; negative to toward the top.This method is called inside an animation block managed by the `UIPopoverController`.
*/
@property (nonatomic, readwrite) CGFloat arrowOffset;

/* `arrowDirection` manages which direction the popover arrow is pointing. You may be required to change the direction of the arrow while the popover is still visible on-screen.
*/
@property (nonatomic, readwrite) UIPopoverArrowDirection arrowDirection;

/* These methods must be overridden and the values they return may not be changed during use of the `UIPopoverBackgroundView`. `arrowHeight` represents the height of the arrow in points from its base to its tip. `arrowBase` represents the the length of the base of the arrow’s triangle in points. `contentViewInset` describes the distance between each edge of the background view and the corresponding edge of its content view (i.e. if it were strictly a rectangle). `arrowHeight` and `arrowBase` are also used for the drawing of the standard popover shadow.
*/
+ (CGFloat)arrowHeight;
+ (CGFloat)arrowBase;
+ (UIEdgeInsets)contentViewInsets;

@end

Then in your .m file:


#import "ComicPopoverBackgroundView.h"
@implementation [email protected] arrowOffset, arrowDirection;-(id)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
UIImageView *_imageView = [[[UIImageView alloc] initWithImage:[[UIImage imageNamed:@”UIPopoverControllerBackground.png”] stretchableImageWithLeftCapWidth:50.0 topCapHeight:380.0]];
[self addSubview:_imageView] autorelease];
}
return self;
}-(void)layoutSubviews{
_imageView.frame = CGRectMake(0, 0, self.superview.frame.size.width+10, self.superview.frame.size.height+10);
}

+(UIEdgeInsets)contentViewInsets{
return UIEdgeInsetsMake(0, 10, 0, 0);
}

+(CGFloat)arrowHeight{
return 30.0;
}

+(CGFloat)arrowBase{
return 42.0;
}

@end

The only issue I have so far is that in the _contentViewFrame method I am needing 602 and 600 which is the width and height of my popovercontroller. I am sure there is a way to access that dynamically I just haven’t looked into that yet. EDIT* You can set this dynamically if you take UIEdgeInsets and ArrowHeight into account.

The important part is to implement the -(CGRect)_contentViewFrame; method which the documentation does not mention so I was repeatedly getting this error:
-[CustomPopoverBackgroundView _contentViewFrame]: unrecognized selector sent to instance 0xc547b80

EDIT* if you import: #import

You can then subclass UIPopoverBackgroundView properly rather than UIView and remove the nasty _contentViewFrame view.

Code has been updated to reflect these changes

  1. Tharit

    Thanks for your post – especially the part about the _contentViewFrame method missing in the documentation.

    There appears to be one more issue however (at least for me): When adding an UINavigationController to a popover with custom style, the shadows around the contentview get drawn directly through the UINavigationBar. Seems the layout routine somehow tries to draw it under the bar (as the default appearance does, where the bar is integrated into the popover title), but get’s the bars height wrong. Did you also encounter that problem?

  2. admin

    I haven’t tried using a UINavigationController on it. I would imagine the UINavigationController would appear under the shadow, you’re saying it’s appearing above the shadow? You should be able to change what’s delivered in the _contentViewFrame to create the correct height, a screenshot would greatly help if you wanted to attach it.

  3. Tharit

    Okay, I figured it out..
    The problem is that in your example, you are extending UIView instead of UIPopoverBackgroundView. Did not notice that when I copied your code 😉
    So I just included the right header, and changed the superclass to the correct one.
    The _contentViewFrame method is then unnecessary because that is handled automatically, and the Navigation Controllers work just fine.

  4. admin

    What’s the right header? UIPopoverBackgroundView doesn’t seem to be recognized in code.

  5. drunknbass

    Looks like your imageview would leak.

  6. admin

    haha of course. I forgot to post the dealloc. This was a hack and copy/paste job.

  7. Andrew

    Can you post your UIPopoverControllerBackground.png?

  8. admin

    I can’t, I wish I could but I am using whole new background graphics from my designer.

  9. Random Jeffrey

    Please can you use a mono-spaced font in your code formatting? It would make the code easier to decipher.

  10. JR

    can you please attach your image that you used. would be nice so i know how to make mine too look. thanks!

Leave a Reply

*

captcha *