Changing background color tint of UIPopover using UIPopoverBackgroundView

Starting at 5.0 iOS supports custom backgrounds for popovers, but a custom class implementation of UIPopoverBackgroundView is needed for that to work. At the time of writing this, there is absolutely no way to tint a popover with UIAppearance or tint color; a custom image must be used. Apple doesn't provide sample code and documentation is very limited.

Here is how to do it:

aPopoverController.popoverBackgroundViewClass = [SamplePopoverBackgroundView class];

Then add these two images to your project, one for arrow and one as strechable background:

 

And finally add the following interface:

 

 

    //

    //  SamplePopoverBackgroundView.h

    //  poc

    //

    //  Created by Andrew Kolesnikov on 11/22/11.

    //  Copyright (c) 2011 Isobar. All rights reserved.

    //

 

 

#import <UIKit/UIKit.h>

#import <UIKit/UIPopoverBackgroundView.h>

    @interface SamplePopoverBackgroundView : UIPopoverBackgroundView {

 

      UIImageView *_imageView;

      UIImageView *_arrowView;

    }

    /* 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&rsquo;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 the implementation:

 

    //

    //  SamplePopoverBackgroundView.m

    //  poc

    //

    //  Created by Andrew Kolesnikov on 11/22/11.

    //  Copyright (c) 2011 Isobar. All rights reserved.

    //

 

    #import "SamplePopoverBackgroundView.h"

 

    @implementation SamplePopoverBackgroundView

 

    @synthesize arrowOffset, arrowDirection;

 

    -(id)initWithFrame:(CGRect)frame{

      if (self = [super initWithFrame:frame]) {

        _imageView = [[[UIImageView alloc] initWithImage:[[UIImage imageNamed:@"bg-popover.png"] resizableImageWithCapInsets: UIEdgeInsetsMake(40.0, 10.0, 30.0, 10.0)]] autorelease];

        _arrowView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"bg-popover-arrow.png"]] autorelease];

 

        self.backgroundColor =  _arrowView.backgroundColor =  _imageView.backgroundColor = [UIColor clearColor];

 

        [self addSubview:_imageView];

        [self addSubview:_arrowView];

      }

      return self;

    }

 

    - (void)drawRect:(CGRect)rect {

 

    }

 

    -(void)layoutSubviews{

 

      if (arrowDirection == UIPopoverArrowDirectionUp) {  

      _imageView.frame = CGRectMake(0, [SamplePopoverBackgroundView arrowHeight], self.superview.frame.size.width, self.superview.frame.size.height - [SamplePopoverBackgroundView arrowHeight]);

 

      _arrowView.frame = CGRectMake(self.superview.frame.size.width / 2 + arrowOffset - [SamplePopoverBackgroundView arrowBase] / 2, 2, [SamplePopoverBackgroundView arrowBase], [SamplePopoverBackgroundView arrowHeight]);

      }

 

      if (arrowDirection == UIPopoverArrowDirectionRight) {  

 

        _imageView.frame = CGRectMake(0, 0, self.superview.frame.size.width - [SamplePopoverBackgroundView arrowHeight], self.superview.frame.size.height);

 

        _arrowView.image = [[UIImage alloc] initWithCGImage: _arrowView.image.CGImage scale: 1.0 orientation: UIImageOrientationRight];

 

        _arrowView.frame = CGRectMake(self.superview.frame.size.width - [SamplePopoverBackgroundView arrowHeight] - 1, self.superview.frame.size.height / 2 + arrowOffset - [SamplePopoverBackgroundView arrowBase] / 2, [SamplePopoverBackgroundView arrowHeight], [SamplePopoverBackgroundView arrowBase]);

      }

    }

 

    +(UIEdgeInsets)contentViewInsets{

      return UIEdgeInsetsMake(5, 5, 5, 5);

    }

 

    +(CGFloat)arrowHeight{

      return 21.0;

    }

 

    +(CGFloat)arrowBase{

      return 35.0;

    }

 

    @end

 

Blurry text on iOS

iOS supports NSFloat coordinates, and thus text antialiasing can make it appear blurry.

Solution: Run your UIView.frame through CGRectIntegral function to convert a CGRect to integral values. This will take care of your blurry text, short and sweet.