Files
libreoffice/ios/iosremote/PopoverView.m
Siqi LIU 3c5369840c popover view when no WiFi interface is found for ex
Change-Id: Ib37fa00b58a1c4193b4181699e385dcb6502edac
2013-08-18 15:22:14 +08:00

1050 lines
43 KiB
Objective-C
Executable File

//
// PopoverView.m
// Embark
//
// Created by Oliver Rickard on 20/08/2012.
//
//
#import "PopoverView.h"
#import "PopoverView_Configuration.h"
#import <QuartzCore/QuartzCore.h>
#pragma mark - Implementation
@implementation PopoverView
@synthesize subviewsArray;
@synthesize contentView;
@synthesize titleView;
@synthesize delegate;
#pragma mark - Static Methods
+ (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withText:(NSString *)text delegate:(id<PopoverViewDelegate>)delegate {
PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
[popoverView showAtPoint:point inView:view withText:text];
popoverView.delegate = delegate;
[popoverView RELEASE];
return popoverView;
}
+ (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withText:(NSString *)text delegate:(id<PopoverViewDelegate>)delegate {
PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
[popoverView showAtPoint:point inView:view withTitle:title withText:text];
popoverView.delegate = delegate;
[popoverView RELEASE];
return popoverView;
}
+ (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withViewArray:(NSArray *)viewArray delegate:(id<PopoverViewDelegate>)delegate {
PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
[popoverView showAtPoint:point inView:view withViewArray:viewArray];
popoverView.delegate = delegate;
[popoverView RELEASE];
return popoverView;
}
+ (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withViewArray:(NSArray *)viewArray delegate:(id<PopoverViewDelegate>)delegate {
PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
[popoverView showAtPoint:point inView:view withTitle:title withViewArray:viewArray];
popoverView.delegate = delegate;
[popoverView RELEASE];
return popoverView;
}
+ (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray delegate:(id<PopoverViewDelegate>)delegate {
PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
[popoverView showAtPoint:point inView:view withStringArray:stringArray];
popoverView.delegate = delegate;
[popoverView RELEASE];
return popoverView;
}
+ (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray delegate:(id<PopoverViewDelegate>)delegate {
PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
[popoverView showAtPoint:point inView:view withTitle:title withStringArray:stringArray];
popoverView.delegate = delegate;
[popoverView RELEASE];
return popoverView;
}
+ (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray delegate:(id<PopoverViewDelegate>)delegate {
PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
[popoverView showAtPoint:point inView:view withStringArray:stringArray withImageArray:imageArray];
popoverView.delegate = delegate;
[popoverView RELEASE];
return popoverView;
}
+ (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray delegate:(id<PopoverViewDelegate>)delegate {
PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
[popoverView showAtPoint:point inView:view withTitle:title withStringArray:stringArray withImageArray:imageArray];
popoverView.delegate = delegate;
[popoverView RELEASE];
return popoverView;
}
+ (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withContentView:(UIView *)cView delegate:(id<PopoverViewDelegate>)delegate {
PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
[popoverView showAtPoint:point inView:view withTitle:title withContentView:cView];
popoverView.delegate = delegate;
[popoverView RELEASE];
return popoverView;
}
+ (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withContentView:(UIView *)cView delegate:(id<PopoverViewDelegate>)delegate {
PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
[popoverView showAtPoint:point inView:view withContentView:cView];
popoverView.delegate = delegate;
[popoverView RELEASE];
return popoverView;
}
#pragma mark - View Lifecycle
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
self.titleView = nil;
self.contentView = nil;
showDividerRects = kShowDividersBetweenViews;
}
return self;
}
- (void)dealloc
{
self.subviewsArray = nil;
if (dividerRects) {
[dividerRects RELEASE];
dividerRects = nil;
}
self.contentView = nil;
self.titleView = nil;
[super DEALLOC];
}
#pragma mark - Display methods
// get the screen size, adjusted for orientation and status bar display
// see http://stackoverflow.com/questions/7905432/how-to-get-orientation-dependent-height-and-width-of-the-screen/7905540#7905540
- (CGSize) screenSize
{
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
CGSize size = [UIScreen mainScreen].bounds.size;
UIApplication *application = [UIApplication sharedApplication];
if (UIInterfaceOrientationIsLandscape(orientation))
{
size = CGSizeMake(size.height, size.width);
}
if (application.statusBarHidden == NO)
{
size.height -= MIN(application.statusBarFrame.size.width, application.statusBarFrame.size.height);
}
return size;
}
- (void)showAtPoint:(CGPoint)point inView:(UIView *)view withText:(NSString *)text
{
UIFont *font = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? kSmallTextFontPad : kTextFontPhone;
CGSize screenSize = [self screenSize];
CGSize textSize = [text sizeWithFont:font constrainedToSize:CGSizeMake(screenSize.width - kHorizontalMargin*4.f, 1000.f) lineBreakMode:UILineBreakModeWordWrap];
UILabel *textView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)];
textView.backgroundColor = [UIColor clearColor];
textView.userInteractionEnabled = NO;
[textView setNumberOfLines:0]; //This is so the label word wraps instead of cutting off the text
textView.font = font;
textView.textAlignment = kTextAlignmentLeft;
textView.textColor = kTextColor;
textView.text = text;
[self showAtPoint:point inView:view withViewArray:[NSArray arrayWithObject:[textView AUTORELEASE]]];
}
- (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withText:(NSString *)text
{
UIFont *font = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? kTextFontPad : kTextFontPhone;
CGSize screenSize = [self screenSize];
CGSize textSize = [text sizeWithFont:font constrainedToSize:CGSizeMake(screenSize.width - kHorizontalMargin*4.f, 1000.f) lineBreakMode:UILineBreakModeWordWrap];
UILabel *textView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)];
textView.backgroundColor = [UIColor clearColor];
textView.userInteractionEnabled = NO;
[textView setNumberOfLines:0]; //This is so the label word wraps instead of cutting off the text
textView.font = font;
textView.textAlignment = kTextAlignment;
textView.textColor = kTextColor;
textView.text = text;
[self showAtPoint:point inView:view withTitle:title withViewArray:[NSArray arrayWithObject:[textView AUTORELEASE]]];
}
- (void)showAtPoint:(CGPoint)point inView:(UIView *)view withViewArray:(NSArray *)viewArray
{
UIView *container = [[UIView alloc] initWithFrame:CGRectZero];
float totalHeight = 0.f;
float totalWidth = 0.f;
int i = 0;
//Position each view the first time, and identify which view has the largest width that controls
//the sizing of the popover.
for (UIView *view in viewArray) {
view.frame = CGRectMake(0, totalHeight, view.frame.size.width, view.frame.size.height);
//Only add padding below the view if it's not the last item
float padding = (i == viewArray.count-1) ? 0 : kBoxPadding;
totalHeight += view.frame.size.height + padding;
if (view.frame.size.width > totalWidth) {
totalWidth = view.frame.size.width;
}
[container addSubview:view];
i++;
}
//If dividers are enabled, then we allocate the divider rect array. This will hold NSValues
if (kShowDividersBetweenViews) {
dividerRects = [[NSMutableArray alloc] initWithCapacity:viewArray.count-1];
}
container.frame = CGRectMake(0, 0, totalWidth, totalHeight);
i = 0;
totalHeight = 0;
//Now we actually change the frame element for each subview, and center the views horizontally.
for (UIView *view in viewArray) {
if ([view autoresizingMask] == UIViewAutoresizingFlexibleWidth) {
//Now make sure all flexible views are the full width
view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, totalWidth, view.frame.size.height);
} else {
//If the view is not flexible width, then we position it centered in the view
//without stretching it.
view.frame = CGRectMake(floorf(CGRectGetMinX(boxFrame) + totalWidth*0.5f - view.frame.size.width*0.5f), view.frame.origin.y, view.frame.size.width, view.frame.size.height);
}
//and if dividers are enabled, we record their position for the drawing methods
if (kShowDividersBetweenViews && i != viewArray.count-1) {
CGRect dividerRect = CGRectMake(view.frame.origin.x, floorf(view.frame.origin.y + view.frame.size.height + kBoxPadding*0.5f), view.frame.size.width, 0.5f);
[((NSMutableArray *)dividerRects) addObject:[NSValue valueWithCGRect:dividerRect]];
}
//Only add padding below the view if it's not the last item
float padding = (i == viewArray.count-1) ? 0.f : kBoxPadding;
totalHeight += view.frame.size.height + padding;
i++;
}
self.subviewsArray = viewArray;
[self showAtPoint:point inView:view withContentView:[container AUTORELEASE]];
}
- (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withViewArray:(NSArray *)viewArray
{
UIView *container = [[UIView alloc] initWithFrame:CGRectZero];
//Create a label for the title text.
CGSize titleSize = [title sizeWithFont: UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? kTitleFontPad : kTitleFontPhone];
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.f, 0.f, titleSize.width, titleSize.height)];
titleLabel.backgroundColor = [UIColor clearColor];
titleLabel.font = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? kTitleFontPad : kTitleFontPhone;
titleLabel.textAlignment = UITextAlignmentCenter;
titleLabel.textColor = kTitleColor;
titleLabel.text = title;
//Make sure that the title's label will have non-zero height. If it has zero height, then we don't allocate any space
//for it in the positioning of the views.
float titleHeightOffset = (titleSize.height > 0.f ? kBoxPadding : 0.f);
float totalHeight = titleSize.height + titleHeightOffset + kBoxPadding;
float totalWidth = titleSize.width;
int i = 0;
//Position each view the first time, and identify which view has the largest width that controls
//the sizing of the popover.
for (UIView *view in viewArray) {
view.frame = CGRectMake(0, totalHeight, view.frame.size.width, view.frame.size.height);
//Only add padding below the view if it's not the last item.
float padding = (i == viewArray.count-1) ? 0.f : kBoxPadding;
totalHeight += view.frame.size.height + padding;
if (view.frame.size.width > totalWidth) {
totalWidth = view.frame.size.width;
}
[container addSubview:view];
i++;
}
//If dividers are enabled, then we allocate the divider rect array. This will hold NSValues
if (kShowDividersBetweenViews) {
dividerRects = [[NSMutableArray alloc] initWithCapacity:viewArray.count-1];
}
i = 0;
for (UIView *view in viewArray) {
if ([view autoresizingMask] == UIViewAutoresizingFlexibleWidth) {
//Now make sure all flexible views are the full width
view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, totalWidth, view.frame.size.height);
} else {
//If the view is not flexible width, then we position it centered in the view
//without stretching it.
view.frame = CGRectMake(floorf(CGRectGetMinX(boxFrame) + totalWidth*0.5f - view.frame.size.width*0.5f), view.frame.origin.y, view.frame.size.width, view.frame.size.height);
}
//and if dividers are enabled, we record their position for the drawing methods
if (kShowDividersBetweenViews && i != viewArray.count-1) {
CGRect dividerRect = CGRectMake(view.frame.origin.x, floorf(view.frame.origin.y + view.frame.size.height + kBoxPadding*0.5f), view.frame.size.width, 0.5f);
[((NSMutableArray *)dividerRects) addObject:[NSValue valueWithCGRect:dividerRect]];
}
i++;
}
titleLabel.frame = CGRectMake(floorf(totalWidth*0.5f - titleSize.width*0.5f), 0, titleSize.width, titleSize.height);
//Store the titleView as an instance variable if it is larger than 0 height (not an empty string)
if (titleSize.height > 0) {
self.titleView = titleLabel;
}
[container addSubview:[titleLabel AUTORELEASE]];
container.frame = CGRectMake(0, 0, totalWidth, totalHeight);
self.subviewsArray = viewArray;
[self showAtPoint:point inView:view withContentView:[container AUTORELEASE]];
}
- (void)showAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray
{
NSMutableArray *labelArray = [[NSMutableArray alloc] initWithCapacity:stringArray.count];
UIFont *font = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? kTextFontPad : kTextFontPhone;
for (NSString *string in stringArray) {
CGSize textSize = [string sizeWithFont:font];
UIButton *textButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)];
textButton.backgroundColor = [UIColor clearColor];
textButton.titleLabel.font = font;
textButton.titleLabel.textAlignment = kTextAlignment;
textButton.titleLabel.textColor = kTextColor;
[textButton setTitle:string forState:UIControlStateNormal];
textButton.layer.cornerRadius = 4.f;
[textButton setTitleColor:kTextColor forState:UIControlStateNormal];
[textButton setTitleColor:kTextHighlightColor forState:UIControlStateHighlighted];
[textButton addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside];
[labelArray addObject:[textButton AUTORELEASE]];
}
[self showAtPoint:point inView:view withViewArray:[labelArray AUTORELEASE]];
}
- (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray
{
NSMutableArray *labelArray = [[NSMutableArray alloc] initWithCapacity:stringArray.count];
UIFont *font = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? kTextFontPad : kTextFontPhone;
for (NSString *string in stringArray) {
CGSize textSize = [string sizeWithFont:font];
UIButton *textButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)];
textButton.backgroundColor = [UIColor clearColor];
textButton.titleLabel.font = font;
textButton.titleLabel.textAlignment = kTextAlignment;
textButton.titleLabel.textColor = kTextColor;
[textButton setTitle:string forState:UIControlStateNormal];
textButton.layer.cornerRadius = 4.f;
[textButton setTitleColor:kTextColor forState:UIControlStateNormal];
[textButton setTitleColor:kTextHighlightColor forState:UIControlStateHighlighted];
[textButton addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside];
[labelArray addObject:[textButton AUTORELEASE]];
}
[self showAtPoint:point inView:view withTitle:title withViewArray:[labelArray AUTORELEASE]];
}
- (void)showAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray
{
//Here we do something pretty similar to the stringArray method above.
//We create an array of subviews that contains the strings and images centered above a label.
NSAssert((stringArray.count == imageArray.count), @"stringArray.count should equal imageArray.count");
NSMutableArray* tempViewArray = [self makeTempViewsWithStrings:stringArray andImages:imageArray];
[self showAtPoint:point inView:view withViewArray:tempViewArray];
}
- (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray
{
NSAssert((stringArray.count == imageArray.count), @"stringArray.count should equal imageArray.count");
NSMutableArray* tempViewArray = [self makeTempViewsWithStrings:stringArray andImages:imageArray];
[self showAtPoint:point inView:view withTitle:title withViewArray:tempViewArray];
}
- (NSMutableArray*) makeTempViewsWithStrings:(NSArray *)stringArray andImages:(NSArray *)imageArray
{
NSMutableArray *tempViewArray = [[NSMutableArray alloc] initWithCapacity:stringArray.count];
UIFont *font = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? kTextFontPad : kTextFontPhone;
for (int i = 0; i < stringArray.count; i++) {
NSString *string = [stringArray objectAtIndex:i];
//First we build a label for the text to set in.
CGSize textSize = [string sizeWithFont:font];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)];
label.backgroundColor = [UIColor clearColor];
label.font = font;
label.textAlignment = kTextAlignment;
label.textColor = kTextColor;
label.text = string;
label.layer.cornerRadius = 4.f;
//Now we grab the image at the same index in the imageArray, and create
//a UIImageView for it.
UIImage *image = [imageArray objectAtIndex:i];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
//Take the larger of the two widths as the width for the container
float containerWidth = MAX(imageView.frame.size.width, label.frame.size.width);
float containerHeight = label.frame.size.height + kImageTopPadding + kImageBottomPadding + imageView.frame.size.height;
//This container will hold both the image and the label
UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, containerWidth, containerHeight)];
//Now we do the frame manipulations to put the imageView on top of the label, both centered
imageView.frame = CGRectMake(floorf(containerWidth*0.5f - imageView.frame.size.width*0.5f), kImageTopPadding, imageView.frame.size.width, imageView.frame.size.height);
label.frame = CGRectMake(floorf(containerWidth*0.5f - label.frame.size.width*0.5f), imageView.frame.size.height + kImageBottomPadding + kImageTopPadding, label.frame.size.width, label.frame.size.height);
[containerView addSubview:imageView];
[containerView addSubview:label];
[label RELEASE];
[imageView RELEASE];
[tempViewArray addObject:containerView];
[containerView RELEASE];
}
return [tempViewArray AUTORELEASE];
}
- (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withContentView:(UIView *)cView
{
[self showAtPoint:point inView:view withTitle:title withViewArray:[NSArray arrayWithObject:cView]];
}
- (void)showAtPoint:(CGPoint)point inView:(UIView *)view withContentView:(UIView *)cView {
//NSLog(@"point:%f,%f", point.x, point.y);
self.contentView = cView;
parentView = view;
// get the top view
// http://stackoverflow.com/questions/3843411/getting-reference-to-the-top-most-view-window-in-ios-application/8045804#8045804
topView = [[[[UIApplication sharedApplication] keyWindow] subviews] lastObject];
[self setupLayout:point inView:view];
// Make the view small and transparent before animation
self.alpha = 0.f;
self.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
// animate into full size
// First stage animates to 1.05x normal size, then second stage animates back down to 1x size.
// This two-stage animation creates a little "pop" on open.
[UIView animateWithDuration:0.2f delay:0.f options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.alpha = 1.f;
self.transform = CGAffineTransformMakeScale(1.05f, 1.05f);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.08f delay:0.f options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.transform = CGAffineTransformIdentity;
} completion:nil];
}];
}
- (void)layoutAtPoint:(CGPoint)point inView:(UIView *)view
{
// make transparent
self.alpha = 0.f;
[self setupLayout:point inView:view];
// animate back to full opacity
[UIView animateWithDuration:0.2f delay:0.f options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.alpha = 1.f;
} completion:nil];
}
-(void)setupLayout:(CGPoint)point inView:(UIView*)view
{
CGPoint topPoint = [topView convertPoint:point fromView:view];
arrowPoint = topPoint;
//NSLog(@"arrowPoint:%f,%f", arrowPoint.x, arrowPoint.y);
CGRect topViewBounds = topView.bounds;
//NSLog(@"topViewBounds %@", NSStringFromCGRect(topViewBounds));
float contentHeight = contentView.frame.size.height;
float contentWidth = contentView.frame.size.width;
float padding = kBoxPadding;
float boxHeight = contentHeight + 2.f*padding;
float boxWidth = contentWidth + 2.f*padding;
float xOrigin = 0.f;
//Make sure the arrow point is within the drawable bounds for the popover.
if (arrowPoint.x + kArrowHeight > topViewBounds.size.width - kHorizontalMargin - kBoxRadius - kArrowHorizontalPadding) {//Too far to the right
arrowPoint.x = topViewBounds.size.width - kHorizontalMargin - kBoxRadius - kArrowHorizontalPadding - kArrowHeight;
//NSLog(@"Correcting Arrow Point because it's too far to the right");
} else if (arrowPoint.x - kArrowHeight < kHorizontalMargin + kBoxRadius + kArrowHorizontalPadding) {//Too far to the left
arrowPoint.x = kHorizontalMargin + kArrowHeight + kBoxRadius + kArrowHorizontalPadding;
//NSLog(@"Correcting Arrow Point because it's too far to the left");
}
//NSLog(@"arrowPoint:%f,%f", arrowPoint.x, arrowPoint.y);
xOrigin = floorf(arrowPoint.x - boxWidth*0.5f);
//Check to see if the centered xOrigin value puts the box outside of the normal range.
if (xOrigin < CGRectGetMinX(topViewBounds) + kHorizontalMargin) {
xOrigin = CGRectGetMinX(topViewBounds) + kHorizontalMargin;
} else if (xOrigin + boxWidth > CGRectGetMaxX(topViewBounds) - kHorizontalMargin) {
//Check to see if the positioning puts the box out of the window towards the left
xOrigin = CGRectGetMaxX(topViewBounds) - kHorizontalMargin - boxWidth;
}
float arrowHeight = kArrowHeight;
float topPadding = kTopMargin;
above = YES;
if (topPoint.y - contentHeight - arrowHeight - topPadding < CGRectGetMinY(topViewBounds)) {
//Position below because it won't fit above.
above = NO;
boxFrame = CGRectMake(xOrigin, arrowPoint.y + arrowHeight, boxWidth, boxHeight);
} else {
//Position above.
above = YES;
boxFrame = CGRectMake(xOrigin, arrowPoint.y - arrowHeight - boxHeight, boxWidth, boxHeight);
}
//NSLog(@"boxFrame:(%f,%f,%f,%f)", boxFrame.origin.x, boxFrame.origin.y, boxFrame.size.width, boxFrame.size.height);
CGRect contentFrame = CGRectMake(boxFrame.origin.x + padding, boxFrame.origin.y + padding, contentWidth, contentHeight);
contentView.frame = contentFrame;
//We set the anchorPoint here so the popover will "grow" out of the arrowPoint specified by the user.
//You have to set the anchorPoint before setting the frame, because the anchorPoint property will
//implicitly set the frame for the view, which we do not want.
self.layer.anchorPoint = CGPointMake(arrowPoint.x / topViewBounds.size.width, arrowPoint.y / topViewBounds.size.height);
self.frame = topViewBounds;
[self setNeedsDisplay];
[self addSubview:contentView];
[topView addSubview:self];
//Add a tap gesture recognizer to the large invisible view (self), which will detect taps anywhere on the screen.
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)];
tap.cancelsTouchesInView = NO; // Allow touches through to a UITableView or other touchable view, as suggested by Dimajp.
[self addGestureRecognizer:tap];
[tap RELEASE];
self.userInteractionEnabled = YES;
}
#pragma mark - Activity Indicator
//Animates in a progress indicator, and removes
- (void)showActivityIndicatorWithMessage:(NSString *)msg
{
if ([titleView isKindOfClass:[UILabel class]]) {
((UILabel *)titleView).text = msg;
}
if (subviewsArray && (subviewsArray.count > 0)) {
[UIView animateWithDuration:0.2f animations:^{
for (UIView *view in subviewsArray) {
view.alpha = 0.f;
}
}];
if (showDividerRects) {
showDividerRects = NO;
[self setNeedsDisplay];
}
}
if (activityIndicator) {
[activityIndicator RELEASE];
[activityIndicator removeFromSuperview];
activityIndicator = nil;
}
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.frame = CGRectMake(CGRectGetMidX(contentView.bounds) - 10.f, CGRectGetMidY(contentView.bounds) - 10.f + 20.f, 20.f, 20.f);
[contentView addSubview:activityIndicator];
[activityIndicator startAnimating];
}
- (void)hideActivityIndicatorWithMessage:(NSString *)msg
{
if ([titleView isKindOfClass:[UILabel class]]) {
((UILabel *)titleView).text = msg;
}
[activityIndicator stopAnimating];
[UIView animateWithDuration:0.1f animations:^{
activityIndicator.alpha = 0.f;
} completion:^(BOOL finished) {
[activityIndicator RELEASE];
[activityIndicator removeFromSuperview];
activityIndicator = nil;
}];
}
- (void)showImage:(UIImage *)image withMessage:(NSString *)msg
{
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.alpha = 0.f;
imageView.frame = CGRectMake(floorf(CGRectGetMidX(contentView.bounds) - image.size.width*0.5f), floorf(CGRectGetMidY(contentView.bounds) - image.size.height*0.5f + ((self.titleView) ? 20 : 0.f)), image.size.width, image.size.height);
imageView.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
[contentView addSubview:[imageView AUTORELEASE]];
if (subviewsArray && (subviewsArray.count > 0)) {
[UIView animateWithDuration:0.2f animations:^{
for (UIView *view in subviewsArray) {
view.alpha = 0.f;
}
}];
if (showDividerRects) {
showDividerRects = NO;
[self setNeedsDisplay];
}
}
if (msg) {
if ([titleView isKindOfClass:[UILabel class]]) {
((UILabel *)titleView).text = msg;
}
}
[UIView animateWithDuration:0.2f delay:0.2f options:UIViewAnimationOptionCurveEaseOut animations:^{
imageView.alpha = 1.f;
imageView.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
//[imageView removeFromSuperview];
}];
}
- (void)showError
{
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"error"]];
imageView.alpha = 0.f;
imageView.frame = CGRectMake(CGRectGetMidX(contentView.bounds) - 20.f, CGRectGetMidY(contentView.bounds) - 20.f + ((self.titleView) ? 20 : 0.f), 40.f, 40.f);
imageView.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
[contentView addSubview:[imageView AUTORELEASE]];
if (subviewsArray && (subviewsArray.count > 0)) {
[UIView animateWithDuration:0.1f animations:^{
for (UIView *view in subviewsArray) {
view.alpha = 0.f;
}
}];
if (showDividerRects) {
showDividerRects = NO;
[self setNeedsDisplay];
}
}
[UIView animateWithDuration:0.1f delay:0.1f options:UIViewAnimationOptionCurveEaseOut animations:^{
imageView.alpha = 1.f;
imageView.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
//[imageView removeFromSuperview];
}];
}
- (void)showSuccess
{
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"success"]];
imageView.alpha = 0.f;
imageView.frame = CGRectMake(CGRectGetMidX(contentView.bounds) - 20.f, CGRectGetMidY(contentView.bounds) - 20.f + ((self.titleView) ? 20 : 0.f), 40.f, 40.f);
imageView.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
[contentView addSubview:[imageView AUTORELEASE]];
if (subviewsArray && (subviewsArray.count > 0)) {
[UIView animateWithDuration:0.1f animations:^{
for (UIView *view in subviewsArray) {
view.alpha = 0.f;
}
}];
if (showDividerRects) {
showDividerRects = NO;
[self setNeedsDisplay];
}
}
[UIView animateWithDuration:0.1f delay:0.1f options:UIViewAnimationOptionCurveEaseOut animations:^{
imageView.alpha = 1.f;
imageView.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
//[imageView removeFromSuperview];
}];
}
#pragma mark - User Interaction
- (void)tapped:(UITapGestureRecognizer *)tap
{
CGPoint point = [tap locationInView:contentView];
//NSLog(@"point:(%f,%f)", point.x, point.y);
BOOL found = NO;
//NSLog(@"subviewsArray:%@", subviewsArray);
for (int i = 0; i < subviewsArray.count && !found; i++) {
UIView *view = [subviewsArray objectAtIndex:i];
//NSLog(@"Rect:(%f,%f,%f,%f)", view.frame.origin.x, view.frame.origin.y, view.frame.size.width, view.frame.size.height);
if (CGRectContainsPoint(view.frame, point)) {
//The tap was within this view, so we notify the delegate, and break the loop.
found = YES;
//NSLog(@"Tapped subview:%d", i);
if ([view isKindOfClass:[UIButton class]]) {
return;
}
if (delegate && [delegate respondsToSelector:@selector(popoverView:didSelectItemAtIndex:)]) {
[delegate popoverView:self didSelectItemAtIndex:i];
}
break;
}
}
if (!found && CGRectContainsPoint(contentView.bounds, point)) {
found = YES;
//NSLog(@"popover box contains point, ignoring user input");
}
if (!found) {
[self dismiss:YES];
}
}
- (void)didTapButton:(UIButton *)sender
{
int index = [subviewsArray indexOfObject:sender];
if (index == NSNotFound) {
return;
}
if (delegate && [delegate respondsToSelector:@selector(popoverView:didSelectItemAtIndex:)]) {
[delegate popoverView:self didSelectItemAtIndex:index];
}
}
- (void)dismiss
{
[self dismiss:YES];
}
- (void)dismiss:(BOOL)animated
{
if (!animated)
{
[self dismissComplete];
}
else
{
[UIView animateWithDuration:0.3f animations:^{
self.alpha = 0.1f;
self.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
} completion:^(BOOL finished) {
[self dismissComplete];
}];
}
}
- (void)dismissComplete
{
[self removeFromSuperview];
if (self.delegate && [self.delegate respondsToSelector:@selector(popoverViewDidDismiss:)]) {
[delegate popoverViewDidDismiss:self];
}
}
- (void)animateRotationToNewPoint:(CGPoint)point inView:(UIView *)view withDuration:(NSTimeInterval)duration
{
[self layoutAtPoint:point inView:view];
}
#pragma mark - Drawing Routines
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
// Build the popover path
CGRect frame = boxFrame;
float xMin = CGRectGetMinX(frame);
float yMin = CGRectGetMinY(frame);
float xMax = CGRectGetMaxX(frame);
float yMax = CGRectGetMaxY(frame);
float radius = kBoxRadius; //Radius of the curvature.
float cpOffset = kCPOffset; //Control Point Offset. Modifies how "curved" the corners are.
/*
LT2 RT1
LT1⌜⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⌝RT2
| |
| popover |
| |
LB2⌞_______________⌟RB1
LB1 RB2
Traverse rectangle in clockwise order, starting at LT1
L = Left
R = Right
T = Top
B = Bottom
1,2 = order of traversal for any given corner
*/
UIBezierPath *popoverPath = [UIBezierPath bezierPath];
[popoverPath moveToPoint:CGPointMake(CGRectGetMinX(frame), CGRectGetMinY(frame) + radius)];//LT1
[popoverPath addCurveToPoint:CGPointMake(xMin + radius, yMin) controlPoint1:CGPointMake(xMin, yMin + radius - cpOffset) controlPoint2:CGPointMake(xMin + radius - cpOffset, yMin)];//LT2
//If the popover is positioned below (!above) the arrowPoint, then we know that the arrow must be on the top of the popover.
//In this case, the arrow is located between LT2 and RT1
if (!above) {
[popoverPath addLineToPoint:CGPointMake(arrowPoint.x - kArrowHeight, yMin)];//left side
[popoverPath addCurveToPoint:arrowPoint controlPoint1:CGPointMake(arrowPoint.x - kArrowHeight + kArrowCurvature, yMin) controlPoint2:arrowPoint];//actual arrow point
[popoverPath addCurveToPoint:CGPointMake(arrowPoint.x + kArrowHeight, yMin) controlPoint1:arrowPoint controlPoint2:CGPointMake(arrowPoint.x + kArrowHeight - kArrowCurvature, yMin)];//right side
}
[popoverPath addLineToPoint:CGPointMake(xMax - radius, yMin)];//RT1
[popoverPath addCurveToPoint:CGPointMake(xMax, yMin + radius) controlPoint1:CGPointMake(xMax - radius + cpOffset, yMin) controlPoint2:CGPointMake(xMax, yMin + radius - cpOffset)];//RT2
[popoverPath addLineToPoint:CGPointMake(xMax, yMax - radius)];//RB1
[popoverPath addCurveToPoint:CGPointMake(xMax - radius, yMax) controlPoint1:CGPointMake(xMax, yMax - radius + cpOffset) controlPoint2:CGPointMake(xMax - radius + cpOffset, yMax)];//RB2
//If the popover is positioned above the arrowPoint, then we know that the arrow must be on the bottom of the popover.
//In this case, the arrow is located somewhere between LB1 and RB2
if (above) {
[popoverPath addLineToPoint:CGPointMake(arrowPoint.x + kArrowHeight, yMax)];//right side
[popoverPath addCurveToPoint:arrowPoint controlPoint1:CGPointMake(arrowPoint.x + kArrowHeight - kArrowCurvature, yMax) controlPoint2:arrowPoint];//arrow point
[popoverPath addCurveToPoint:CGPointMake(arrowPoint.x - kArrowHeight, yMax) controlPoint1:arrowPoint controlPoint2:CGPointMake(arrowPoint.x - kArrowHeight + kArrowCurvature, yMax)];
}
[popoverPath addLineToPoint:CGPointMake(xMin + radius, yMax)];//LB1
[popoverPath addCurveToPoint:CGPointMake(xMin, yMax - radius) controlPoint1:CGPointMake(xMin + radius - cpOffset, yMax) controlPoint2:CGPointMake(xMin, yMax - radius + cpOffset)];//LB2
[popoverPath closePath];
//// General Declarations
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = UIGraphicsGetCurrentContext();
//// Shadow Declarations
UIColor* shadow = [UIColor colorWithWhite:0.0f alpha:kShadowAlpha];
CGSize shadowOffset = CGSizeMake(0, 1);
CGFloat shadowBlurRadius = kShadowBlur;
//// Gradient Declarations
NSArray* gradientColors = [NSArray arrayWithObjects:
(id)kGradientTopColor.CGColor,
(id)kGradientBottomColor.CGColor, nil];
CGFloat gradientLocations[] = {0, 1};
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFTYPECAST(CFArrayRef)gradientColors), gradientLocations);
//These floats are the top and bottom offsets for the gradient drawing so the drawing includes the arrows.
float bottomOffset = (above ? kArrowHeight : 0.f);
float topOffset = (!above ? kArrowHeight : 0.f);
//Draw the actual gradient and shadow.
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, shadow.CGColor);
CGContextBeginTransparencyLayer(context, NULL);
[popoverPath addClip];
CGContextDrawLinearGradient(context, gradient, CGPointMake(CGRectGetMidX(frame), CGRectGetMinY(frame) - topOffset), CGPointMake(CGRectGetMidX(frame), CGRectGetMaxY(frame) + bottomOffset), 0);
CGContextEndTransparencyLayer(context);
CGContextRestoreGState(context);
//// Cleanup
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
//Draw the title background
if (kDrawTitleGradient) {
//Calculate the height of the title bg
float titleBGHeight = -1;
//NSLog(@"titleView:%@", titleView);
if (titleView != nil) {
titleBGHeight = kBoxPadding*2.f + titleView.frame.size.height;
}
//Draw the title bg height, but only if we need to.
if (titleBGHeight > 0.f) {
CGPoint startingPoint = CGPointMake(xMin, yMin + titleBGHeight);
CGPoint endingPoint = CGPointMake(xMax, yMin + titleBGHeight);
UIBezierPath *titleBGPath = [UIBezierPath bezierPath];
[titleBGPath moveToPoint:startingPoint];
[titleBGPath addLineToPoint:CGPointMake(CGRectGetMinX(frame), CGRectGetMinY(frame) + radius)];//LT1
[titleBGPath addCurveToPoint:CGPointMake(xMin + radius, yMin) controlPoint1:CGPointMake(xMin, yMin + radius - cpOffset) controlPoint2:CGPointMake(xMin + radius - cpOffset, yMin)];//LT2
//If the popover is positioned below (!above) the arrowPoint, then we know that the arrow must be on the top of the popover.
//In this case, the arrow is located between LT2 and RT1
if (!above) {
[titleBGPath addLineToPoint:CGPointMake(arrowPoint.x - kArrowHeight, yMin)];//left side
[titleBGPath addCurveToPoint:arrowPoint controlPoint1:CGPointMake(arrowPoint.x - kArrowHeight + kArrowCurvature, yMin) controlPoint2:arrowPoint];//actual arrow point
[titleBGPath addCurveToPoint:CGPointMake(arrowPoint.x + kArrowHeight, yMin) controlPoint1:arrowPoint controlPoint2:CGPointMake(arrowPoint.x + kArrowHeight - kArrowCurvature, yMin)];//right side
}
[titleBGPath addLineToPoint:CGPointMake(xMax - radius, yMin)];//RT1
[titleBGPath addCurveToPoint:CGPointMake(xMax, yMin + radius) controlPoint1:CGPointMake(xMax - radius + cpOffset, yMin) controlPoint2:CGPointMake(xMax, yMin + radius - cpOffset)];//RT2
[titleBGPath addLineToPoint:endingPoint];
[titleBGPath addLineToPoint:startingPoint];
[titleBGPath closePath];
//// General Declarations
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = UIGraphicsGetCurrentContext();
//// Gradient Declarations
NSArray* gradientColors = [NSArray arrayWithObjects:
(id)kGradientTitleTopColor.CGColor,
(id)kGradientTitleBottomColor.CGColor, nil];
CGFloat gradientLocations[] = {0, 1};
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFTYPECAST(CFArrayRef)gradientColors), gradientLocations);
//These floats are the top and bottom offsets for the gradient drawing so the drawing includes the arrows.
float topOffset = (!above ? kArrowHeight : 0.f);
//Draw the actual gradient and shadow.
CGContextSaveGState(context);
CGContextBeginTransparencyLayer(context, NULL);
[titleBGPath addClip];
CGContextDrawLinearGradient(context, gradient, CGPointMake(CGRectGetMidX(frame), CGRectGetMinY(frame) - topOffset), CGPointMake(CGRectGetMidX(frame), CGRectGetMinY(frame) + titleBGHeight), 0);
CGContextEndTransparencyLayer(context);
CGContextRestoreGState(context);
UIBezierPath *dividerLine = [UIBezierPath bezierPathWithRect:CGRectMake(startingPoint.x, startingPoint.y, (endingPoint.x - startingPoint.x), 0.5f)];
[[UIColor colorWithRed:0.741 green:0.741 blue:0.741 alpha:0.5f] setFill];
[dividerLine fill];
//// Cleanup
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
}
}
//Draw the divider rects if we need to
{
if (kShowDividersBetweenViews && showDividerRects) {
if (dividerRects && dividerRects.count > 0) {
for (NSValue *value in dividerRects) {
CGRect rect = value.CGRectValue;
rect.origin.x += contentView.frame.origin.x;
rect.origin.y += contentView.frame.origin.y;
UIBezierPath *dividerPath = [UIBezierPath bezierPathWithRect:rect];
[kDividerColor setFill];
[dividerPath fill];
}
}
}
}
//Draw border if we need to
//The border is done last because it needs to be drawn on top of everything else
if (kDrawBorder) {
[kBorderColor setStroke];
popoverPath.lineWidth = kBorderWidth;
[popoverPath stroke];
}
}
@end