Customizing UIPopover with UIPopoverBackgroundView

This project uses features only available in iOS SDK 5.0 and later.

UIPopoverBackgroundView class

In order to customize UIPopover with images you need to extend UIPopoverBackgroundView class and set class object to popover controller property named popoverBackgroundViewClass like this:

Example code:

yourPopoverController.popoverBackgroundViewClass
    = [YourCustomPopoverBackgorundView class];

Properties and class methods

In extended class you need to implement all of the UIPopoverBackgroundView class methods and properties (definitions from Apple docs are self explanatory).

Class methods:

  • arrowHeight – the height of the arrow (measured in points) from its base to its tip.
  • arrowBase – the width of the arrow triangle at its base.
  • contentViewInsets - the insets for the content portion of the popover.

Properties:

  • arrowOffset – the distance (measured in points) from the center of the view to the center line of the arrow (arrowOffset has negative value if left from center, positive if right from center)
  • arrowDirection – the direction in which the popover arrow is pointing: UIPopoverArrowDirection(Up / Down / Left / Right / Any / Unknown)

For those who like pictures there is one with comments:

Example code:

+ (CGFloat)arrowBase
{
    return ARROW_WIDTH;
}

+ (CGFloat)arrowHeight
{
    return ARROW_HEIGHT;
}
+ (UIEdgeInsets)contentViewInsets{
    return UIEdgeInsetsMake(TOP_CONTENT_INSET, LEFT_CONTENT_INSET,
                            BOTTOM_CONTENT_INSET, RIGHT_CONTENT_INSET);
}

// Custom setters

// Whenever arrow changes direction or position layout subviews
// will be called in order to update arrow and backgorund images frames

-(void) setArrowOffset:(CGFloat)arrowOffset
{
    _arrowOffset = arrowOffset;
    [self setNeedsLayout];
}

-(void) setArrowDirection:(UIPopoverArrowDirection)arrowDirection
{
    _arrowDirection = arrowDirection;
    [self setNeedsLayout];
}

Additional properties and iVars

There are two properties of UIImageView in my subclass implementation. I use them to layout stretchable background images (arrow + background)

Example code:

    @property (nonatomic, strong) UIImageView *arrowImageView;
    @property (nonatomic, strong) UIImageView *popoverBackgroundImageView;

They are initialized in initWithFrame: method.

Example code:


UIImage *popoverBackgroundImage
    = [[UIImage imageNamed:@"popover-black-bcg-image.png"]
            resizableImageWithCapInsets:UIEdgeInsetsMake(49, 46, 49, 45)];

self.popoverBackgroundImageView
           = [[UIImageView alloc] initWithImage:popoverBackgroundImage];
[self addSubview:self.popoverBackgroundImageView];

self.arrowImageView = [[UIImageView alloc] init];
[self addSubview:self.arrowImageView];

Additionally, in my class implementation file I made private interface with 4 UIImage iVars. One of  images is going to be set as arrowImageView.image later in layoutSubviews method, when I calculate other frames using arrowDirection property.

Example code:

@interface GTPopoverBackgorundView ()
{
    UIImage *_topArrowImage;
    UIImage *_leftArrowImage;
    UIImage *_rightArrowImage;
    UIImage *_bottomArrowImage;
}

@end

UIImage iVars are initialized in initWithFrame: method with appropriate images.

Example code:

_topArrowImage = [UIImage imageNamed:@"popover-black-top-arrow-image.png"];
_leftArrowImage = [UIImage imageNamed:@"popover-black-left-arrow-image.png"];
_bottomArrowImage = [UIImage imageNamed:@"popover-black-bottom-arrow-image.png"];
_rightArrowImage = [UIImage imageNamed:@"popover-black-right-arrow-image.png"];

layoutSubviews

The most important part of subclass is layoutSubviews method. It layouts arrow and background images according to popover size (self.bounds.size), arrow position (arrowOffset) and arrow direction (arrowDirection). Also popover arrow image is changed here for appropriate direction.

Example code:

-(void)layoutSubviews
{
    CGFloat popoverImageOriginX = 0;
    CGFloat popoverImageOriginY = 0;

    CGFloat popoverWidth = self.bounds.size.width;
    CGFloat popoverHeight = self.bounds.size.height;

    CGFloat arrowImageOriginX = 0;
    CGFloat arrowImageOriginY = 0;

    CGFloat arrowImageWidth = ARROW_WIDTH;
    CGFloat arrowImageHeight = ARROW_HEIGHT;

    // Radius value you used to make rounded corners
    // in your popover background image
    CGFloat cornerRadius = 9;

    switch (self.arrowDirection) {

        case UIPopoverArrowDirectionUp:

            popoverImageOriginY = ARROW_HEIGHT - 2;
            popoverImageHeight = self.bounds.size.height - ARROW_HEIGHT;

            // Calculating arrow x position using arrow offset,
            // arrow width and popover width
            arrowImageOriginX = roundf((self.bounds.size.width
                                         - ARROW_WIDTH) / 2 + self.arrowOffset);

            // If arrow image exceeds rounded corner,
            // arrow image x postion is adjusted
            if (arrowImageOriginX + ARROW_WIDTH
                             > self.bounds.size.width - cornerRadius)
            {
                arrowImageOriginX -= cornerRadius;
            }

            if (arrowImageOriginX < cornerRadius)
            {
                arrowImageOriginX += cornerRadius;
            }

            // Setting arrow image for current arrow direction
            self.arrowImageView.image = _topArrowImage;

            break;

        case UIPopoverArrowDirectionDown:

            // Similar like in UIPopoverArrowDirectionUp case

            break;

        case UIPopoverArrowDirectionLeft:

            // Similar like in UIPopoverArrowDirectionUp case

            break;

        case UIPopoverArrowDirectionRight:

            // Similar like in UIPopoverArrowDirectionUp case

            break;

        default:

            // For popovers without arrows (Thanks Martin!)

            popoverImageHeight = self.bounds.size.height - ARROW_HEIGHT + 2;
            break;
    }

    self.popoverBackgroundImageView.frame = CGRectMake(popoverImageOriginX,
             popoverImageOriginY, popoverWidth, popoverHeight);
    self.arrowImageView.frame = CGRectMake(arrowImageOriginX,
             arrowImageOriginY, arrowImageWidth, arrowImageHeight);
}

Result

Download

You can download xcode project from GitHub: https://github.com/Scianski/KSCustomUIPopover. For this example I made images in Photoshop. UICustomPopover.psd is free for commercial or personal use. More detailed and up to date information can be found at apple docs. If you find any mistakes or have interesting ideas please give me a note. Thanks for reading, hope its helpful.

Be Sociable, Share!