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.
Whoa, thanks!
Thanks, this saved my day!
Thanks !
Nice job – thank you!
This is great, thanks. I’ve found other various implementations of the same thing, but yours was the only one that really fit my needs.
Very helpful, thanks. Apple documentation seems a bit lacking on this topic and your example put it all together for me.
Thanks!! Great way to explain this. Thanks for sharing!
Great post and example project. Thanks.
Well done!
I added this to layoutSubviews for popovers without arrows:
….
default:
popoverImageHeight = self.bounds.size.height - ARROW_HEIGHT + 2;
break;
}
…
Hi Chris, thanks a lot for this helpful code. But is your xcode project from GitHub free for commerial or personal use? Andreas
Great post, really enjoyed it!
— Rueben
http://www.bigconceptdesigns.com
Very useful. Thanks!
Wow This is great example about UIPopoverBackgroundView class.
Thanks!
I’m crying with joy. Thank you so much!
Mr. Ściański
Using your solutions I could easly rebuild all my popovers – so now, even on IOS7 the look like in IOS6 style, which is nicer. THANKS !!!!!!!