LearnCustomizing the design of UIPopoverController


writes on January 18, 2012

Popovers are very common within the iPad user interface but you were restricted to the design provided by Apple. With iOS 5 came a little known class called UIPopoverBackgroundView which allows you to provide a custom border and arrow for the popover.

The popover

The UIPopoverController is the class that facilitates the popover view. It takes a custom view controller and then displays it with a neat border and arrow, where the arrow points to its origin. It is ideal for displaying contextual information. The popover user interface is essentially made of three main parts: the border, content, and arrow.



Before we look at the UIPopoverBackgroundView you will need two images to customize your popover. One image for the border and the other for the arrow.

Dissecting the background image

When designing the background image it is important to note that the image will be stretched. UIImage allows you to create a stretchable image by defining cap insets. These caps define portions of the image that will not be rescaled whereas the rest of the image is easily tiled when stretched. As seen in the image below the dark area is what will be tiled and the colored corners will not.

Subclassing UIPopoverBackgroundView

The UIPopoverBackgroundView is an abstract class which has no implementation. We need to subclass it and provide implementations for all its methods and properties. You can read the Apple documentation on all its properties and methods. What is not in the documentation is how to layout the border and arrow using the method layoutSubviews.

First let’s start by creating an Interface and subclassing the UIPopoverBackgroundView.


@interface CustomPopoverBackgroundView : UIPopoverBackgroundView {
    UIImageView *_borderImageView;
    UIImageView *_arrowView;
    CGFloat _arrowOffset;
    UIPopoverArrowDirection _arrowDirection;


Make sure to specify your import statement or else the code will not compile. An explanation for each of the instance variables:

  • _borderImageView: contains the image for the border
  • _arrowView: contains the image for the arrow
  • _arrowOffset: used for the property arrowOffset specified in the Interface for UIPopoverBackgroundView. We will see later how this value is used to calculate the position for the arrow.
  • _arrowDirection: used for the property arrowDirection specified in the Interface for UIPopoverBackgroundView

Let’s fill out the implementation, starting with the designated initializer

#import "CustomPopoverBackgroundView.h"

#define CONTENT_INSET 10.0
#define CAP_INSET 25.0
#define ARROW_BASE 25.0
#define ARROW_HEIGHT 25.0

@implementation CustomPopoverBackgroundView

    if (self = [super initWithFrame:frame]) {
        _borderImageView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:@"popover-bg.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(CAP_INSET,CAP_INSET,CAP_INSET,CAP_INSET)]];
        _arrowView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"arrow.png"]];
        [self addSubview:_borderImageView];
        [self addSubview:_arrowView];
    return self;

Basically we are allocating and initializing the two views and adding them as subviews. Notice how the background image is defined with cap insets. Next, let’s implement all the required methods including the getters and setters for the properties.

- (CGFloat) arrowOffset {    
    return _arrowOffset;    

- (void) setArrowOffset:(CGFloat)arrowOffset {    
    _arrowOffset = arrowOffset;

- (UIPopoverArrowDirection)arrowDirection {    
    return _arrowDirection;    

- (void)setArrowDirection:(UIPopoverArrowDirection)arrowDirection {    
    _arrowDirection = arrowDirection;


    return ARROW_HEIGHT;

    return ARROW_BASE;

The above methods are fairly straightforward with the exception of contentViewInsets. This method determines the thickness of your border. The higher the number the thicker your border. Finally, the method that lays out our two subviews in their appropriate sizes and location.

-  (void)layoutSubviews {
    [super layoutSubviews];

    CGFloat _height = self.frame.size.height;
    CGFloat _width = self.frame.size.width;
    CGFloat _left = 0.0;
    CGFloat _top = 0.0;
    CGFloat _coordinate = 0.0;
    CGAffineTransform _rotation = CGAffineTransformIdentity;

    switch (self.arrowDirection) {
        case UIPopoverArrowDirectionUp:
            _top += ARROW_HEIGHT;
            _height -= ARROW_HEIGHT;
            _coordinate = ((self.frame.size.width / 2) + self.arrowOffset) - (ARROW_BASE/2);
            _arrowView.frame = CGRectMake(_coordinate, 0, ARROW_BASE, ARROW_HEIGHT);            
        case UIPopoverArrowDirectionDown:
            _height -= ARROW_HEIGHT;
            _coordinate = ((self.frame.size.width / 2) + self.arrowOffset) - (ARROW_BASE/2);
            _arrowView.frame = CGRectMake(_coordinate, _height, ARROW_BASE, ARROW_HEIGHT); 
            _rotation = CGAffineTransformMakeRotation( M_PI );
        case UIPopoverArrowDirectionLeft:
            _left += ARROW_BASE;
            _width -= ARROW_BASE;
            _coordinate = ((self.frame.size.height / 2) + self.arrowOffset) - (ARROW_HEIGHT/2);
            _arrowView.frame = CGRectMake(0, _coordinate, ARROW_BASE, ARROW_HEIGHT); 
            _rotation = CGAffineTransformMakeRotation( -M_PI_2 );
        case UIPopoverArrowDirectionRight:
            _width -= ARROW_BASE;
            _coordinate = ((self.frame.size.height / 2) + self.arrowOffset)- (ARROW_HEIGHT/2);
            _arrowView.frame = CGRectMake(_width, _coordinate, ARROW_BASE, ARROW_HEIGHT); 
            _rotation = CGAffineTransformMakeRotation( M_PI_2 );

    _borderImageView.frame =  CGRectMake(_left, _top, _width, _height);

    [_arrowView setTransform:_rotation];



The switch statement determines the direction of the arrow and then calculates the location of the arrow and its rotation. Our default arrow image points upwards so we need to change its rotation using an affine transform which takes in radians. The arrowOffset is calculated and set by the UIPopoverController which essentially tells us the distance of the arrow from the center of content view. We also have to adjust the height and width of our border view to account for the arrow.

Using the CustomPopoverBackgroundView

Now that we have created the CustomPopoverBackgroundView we need to set it when creating an instance of the UIPopoverController

UIPopoverController *popoverController = [[UIPopoverController alloc] initWithContentViewController:contentViewController] ;

popoverController.popoverBackgroundViewClass = [CustomPopoverBackgroundView class];

Note: the above code will work only in iOS 5

Now run your app and marvel at your newly designed popover.

Subclassing UIPopoverBackgroundView


Learning with Treehouse for only 30 minutes a day can teach you the skills needed to land the job that you've been dreaming about.

Get Started

5 Responses to “Customizing the design of UIPopoverController”

  1. Luiz S. S. Baglie on July 23, 2013 at 9:32 am said:

    Thanks for the tutorial, helped me a lot!

    For those who use MonoTouch, note that you MUST implement the static methods and put the attribute [Export(“objc_static_method”)] over them, where objc_static_method is the name of the correspondent Objective-C method.

  2. Petros on July 15, 2013 at 5:13 am said:

    UIPopoverArrowDirectionAny: there is no case that treats that. Try it out with another arrow direction and it should work

  3. WesDearborn on April 3, 2013 at 1:48 pm said:

    In the case for “UIPopoverArrowDirectionLeft”, “_left” and “_width” should be set to “ARROW_HEIGHT” and NOT “ARROW_WIDTH”. When the arrow is rotated by 90 degrees, its initial height becomes its width, which is what the left inset for the backgroundView should be.

  4. Very good timely post. You’re DA MAN! you saved me hours of frustration and digging on doc.
    Thank you.

  5. Well this was really an interesting article. I highly acknowledge your professional approach. I can say that you are doing a fantastic job by posting such informative tutorial. Keep sharing such great posts. 

Leave a Reply

You must be logged in to post a comment.

man working on his laptop

Are you ready to start learning?

Learning with Treehouse for only 30 minutes a day can teach you the skills needed to land the job that you've been dreaming about.

Start a Free Trial
woman working on her laptop