PopoverView.m 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
  1. //
  2. // PopoverView.m
  3. // Embark
  4. //
  5. // Created by Oliver Rickard on 20/08/2012.
  6. //
  7. //
  8. #import "PopoverView.h"
  9. #import "PopoverView_Configuration.h"
  10. #import <QuartzCore/QuartzCore.h>
  11. #pragma mark - Implementation
  12. @implementation PopoverView
  13. @synthesize subviewsArray;
  14. @synthesize contentView;
  15. @synthesize titleView;
  16. @synthesize delegate;
  17. #pragma mark - Static Methods
  18. + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withText:(NSString *)text delegate:(id<PopoverViewDelegate>)delegate {
  19. PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
  20. [popoverView showAtPoint:point inView:view withText:text];
  21. popoverView.delegate = delegate;
  22. [popoverView RELEASE];
  23. return popoverView;
  24. }
  25. + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withText:(NSString *)text delegate:(id<PopoverViewDelegate>)delegate {
  26. PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
  27. [popoverView showAtPoint:point inView:view withTitle:title withText:text];
  28. popoverView.delegate = delegate;
  29. [popoverView RELEASE];
  30. return popoverView;
  31. }
  32. + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withViewArray:(NSArray *)viewArray delegate:(id<PopoverViewDelegate>)delegate {
  33. PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
  34. [popoverView showAtPoint:point inView:view withViewArray:viewArray];
  35. popoverView.delegate = delegate;
  36. [popoverView RELEASE];
  37. return popoverView;
  38. }
  39. + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withViewArray:(NSArray *)viewArray delegate:(id<PopoverViewDelegate>)delegate {
  40. PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
  41. [popoverView showAtPoint:point inView:view withTitle:title withViewArray:viewArray];
  42. popoverView.delegate = delegate;
  43. [popoverView RELEASE];
  44. return popoverView;
  45. }
  46. + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray delegate:(id<PopoverViewDelegate>)delegate {
  47. PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
  48. [popoverView showAtPoint:point inView:view withStringArray:stringArray];
  49. popoverView.delegate = delegate;
  50. [popoverView RELEASE];
  51. return popoverView;
  52. }
  53. + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray delegate:(id<PopoverViewDelegate>)delegate {
  54. PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
  55. [popoverView showAtPoint:point inView:view withTitle:title withStringArray:stringArray];
  56. popoverView.delegate = delegate;
  57. [popoverView RELEASE];
  58. return popoverView;
  59. }
  60. + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray delegate:(id<PopoverViewDelegate>)delegate {
  61. PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
  62. [popoverView showAtPoint:point inView:view withStringArray:stringArray withImageArray:imageArray];
  63. popoverView.delegate = delegate;
  64. [popoverView RELEASE];
  65. return popoverView;
  66. }
  67. + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray delegate:(id<PopoverViewDelegate>)delegate {
  68. PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
  69. [popoverView showAtPoint:point inView:view withTitle:title withStringArray:stringArray withImageArray:imageArray];
  70. popoverView.delegate = delegate;
  71. [popoverView RELEASE];
  72. return popoverView;
  73. }
  74. + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withContentView:(UIView *)cView delegate:(id<PopoverViewDelegate>)delegate {
  75. PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
  76. [popoverView showAtPoint:point inView:view withTitle:title withContentView:cView];
  77. popoverView.delegate = delegate;
  78. [popoverView RELEASE];
  79. return popoverView;
  80. }
  81. + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withContentView:(UIView *)cView delegate:(id<PopoverViewDelegate>)delegate {
  82. PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
  83. [popoverView showAtPoint:point inView:view withContentView:cView];
  84. popoverView.delegate = delegate;
  85. [popoverView RELEASE];
  86. return popoverView;
  87. }
  88. #pragma mark - View Lifecycle
  89. - (id)initWithFrame:(CGRect)frame
  90. {
  91. self = [super initWithFrame:frame];
  92. if (self) {
  93. // Initialization code
  94. self.backgroundColor = [UIColor clearColor];
  95. self.titleView = nil;
  96. self.contentView = nil;
  97. showDividerRects = kShowDividersBetweenViews;
  98. }
  99. return self;
  100. }
  101. - (void)dealloc
  102. {
  103. self.subviewsArray = nil;
  104. if (dividerRects) {
  105. [dividerRects RELEASE];
  106. dividerRects = nil;
  107. }
  108. self.contentView = nil;
  109. self.titleView = nil;
  110. [super DEALLOC];
  111. }
  112. #pragma mark - Display methods
  113. // get the screen size, adjusted for orientation and status bar display
  114. // see http://stackoverflow.com/questions/7905432/how-to-get-orientation-dependent-height-and-width-of-the-screen/7905540#7905540
  115. - (CGSize) screenSize
  116. {
  117. UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
  118. CGSize size = [UIScreen mainScreen].bounds.size;
  119. UIApplication *application = [UIApplication sharedApplication];
  120. if (UIInterfaceOrientationIsLandscape(orientation))
  121. {
  122. size = CGSizeMake(size.height, size.width);
  123. }
  124. if (application.statusBarHidden == NO)
  125. {
  126. size.height -= MIN(application.statusBarFrame.size.width, application.statusBarFrame.size.height);
  127. }
  128. return size;
  129. }
  130. - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withText:(NSString *)text
  131. {
  132. UIFont *font = kTextFont;
  133. CGSize screenSize = [self screenSize];
  134. CGSize textSize = [text boundingRectWithSize:CGSizeMake(screenSize.width - kHorizontalMargin*4.f, 1000.f) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : font} context:nil].size;
  135. UILabel *textView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)];
  136. textView.backgroundColor = [UIColor clearColor];
  137. textView.userInteractionEnabled = NO;
  138. [textView setNumberOfLines:0]; //This is so the label word wraps instead of cutting off the text
  139. textView.font = font;
  140. textView.textAlignment = kTextAlignment;
  141. textView.textColor = kTextColor;
  142. textView.text = text;
  143. [self showAtPoint:point inView:view withViewArray:[NSArray arrayWithObject:[textView AUTORELEASE]]];
  144. }
  145. - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withText:(NSString *)text
  146. {
  147. UIFont *font = kTextFont;
  148. CGSize screenSize = [self screenSize];
  149. CGSize textSize = [text boundingRectWithSize:CGSizeMake(screenSize.width - kHorizontalMargin*4.f, 1000.f) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : font} context:nil].size;
  150. UILabel *textView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)];
  151. textView.backgroundColor = [UIColor clearColor];
  152. textView.userInteractionEnabled = NO;
  153. [textView setNumberOfLines:0]; //This is so the label word wraps instead of cutting off the text
  154. textView.font = font;
  155. textView.textAlignment = kTextAlignment;
  156. textView.textColor = kTextColor;
  157. textView.text = text;
  158. [self showAtPoint:point inView:view withTitle:title withViewArray:[NSArray arrayWithObject:[textView AUTORELEASE]]];
  159. }
  160. - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withViewArray:(NSArray *)viewArray
  161. {
  162. UIView *container = [[UIView alloc] initWithFrame:CGRectZero];
  163. float totalHeight = 0.f;
  164. float totalWidth = 0.f;
  165. int i = 0;
  166. //Position each view the first time, and identify which view has the largest width that controls
  167. //the sizing of the popover.
  168. for (UIView *view in viewArray) {
  169. view.frame = CGRectMake(0, totalHeight, view.frame.size.width, view.frame.size.height);
  170. //Only add padding below the view if it's not the last item
  171. float padding = (i == viewArray.count-1) ? 0 : kBoxPadding;
  172. totalHeight += view.frame.size.height + padding;
  173. if (view.frame.size.width > totalWidth) {
  174. totalWidth = view.frame.size.width;
  175. }
  176. [container addSubview:view];
  177. i++;
  178. }
  179. //If dividers are enabled, then we allocate the divider rect array. This will hold NSValues
  180. if (kShowDividersBetweenViews) {
  181. dividerRects = [[NSMutableArray alloc] initWithCapacity:viewArray.count-1];
  182. }
  183. container.frame = CGRectMake(0, 0, totalWidth, totalHeight);
  184. i = 0;
  185. totalHeight = 0;
  186. //Now we actually change the frame element for each subview, and center the views horizontally.
  187. for (UIView *view in viewArray) {
  188. if ([view autoresizingMask] == UIViewAutoresizingFlexibleWidth) {
  189. //Now make sure all flexible views are the full width
  190. view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, totalWidth, view.frame.size.height);
  191. } else {
  192. //If the view is not flexible width, then we position it centered in the view
  193. //without stretching it.
  194. 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);
  195. }
  196. //and if dividers are enabled, we record their position for the drawing methods
  197. if (kShowDividersBetweenViews && i != viewArray.count-1) {
  198. 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);
  199. [((NSMutableArray *)dividerRects) addObject:[NSValue valueWithCGRect:dividerRect]];
  200. }
  201. //Only add padding below the view if it's not the last item
  202. float padding = (i == viewArray.count-1) ? 0.f : kBoxPadding;
  203. totalHeight += view.frame.size.height + padding;
  204. i++;
  205. }
  206. self.subviewsArray = viewArray;
  207. [self showAtPoint:point inView:view withContentView:[container AUTORELEASE]];
  208. }
  209. - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withViewArray:(NSArray *)viewArray
  210. {
  211. UIView *container = [[UIView alloc] initWithFrame:CGRectZero];
  212. //Create a label for the title text.
  213. CGSize titleSize = [title sizeWithAttributes:@{NSFontAttributeName : kTitleFont}];
  214. UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.f, 0.f, titleSize.width, titleSize.height)];
  215. titleLabel.backgroundColor = [UIColor clearColor];
  216. titleLabel.font = kTitleFont;
  217. titleLabel.textAlignment = UITextAlignmentCenter;
  218. titleLabel.textColor = kTitleColor;
  219. titleLabel.text = title;
  220. //Make sure that the title's label will have non-zero height. If it has zero height, then we don't allocate any space
  221. //for it in the positioning of the views.
  222. float titleHeightOffset = (titleSize.height > 0.f ? kBoxPadding : 0.f);
  223. float totalHeight = titleSize.height + titleHeightOffset + kBoxPadding;
  224. float totalWidth = titleSize.width;
  225. int i = 0;
  226. //Position each view the first time, and identify which view has the largest width that controls
  227. //the sizing of the popover.
  228. for (UIView *view in viewArray) {
  229. view.frame = CGRectMake(0, totalHeight, view.frame.size.width, view.frame.size.height);
  230. //Only add padding below the view if it's not the last item.
  231. float padding = (i == viewArray.count-1) ? 0.f : kBoxPadding;
  232. totalHeight += view.frame.size.height + padding;
  233. if (view.frame.size.width > totalWidth) {
  234. totalWidth = view.frame.size.width;
  235. }
  236. [container addSubview:view];
  237. i++;
  238. }
  239. //If dividers are enabled, then we allocate the divider rect array. This will hold NSValues
  240. if (kShowDividersBetweenViews) {
  241. dividerRects = [[NSMutableArray alloc] initWithCapacity:viewArray.count-1];
  242. }
  243. i = 0;
  244. for (UIView *view in viewArray) {
  245. if ([view autoresizingMask] == UIViewAutoresizingFlexibleWidth) {
  246. //Now make sure all flexible views are the full width
  247. view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, totalWidth, view.frame.size.height);
  248. } else {
  249. //If the view is not flexible width, then we position it centered in the view
  250. //without stretching it.
  251. 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);
  252. }
  253. //and if dividers are enabled, we record their position for the drawing methods
  254. if (kShowDividersBetweenViews && i != viewArray.count-1) {
  255. 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);
  256. [((NSMutableArray *)dividerRects) addObject:[NSValue valueWithCGRect:dividerRect]];
  257. }
  258. i++;
  259. }
  260. titleLabel.frame = CGRectMake(floorf(totalWidth*0.5f - titleSize.width*0.5f), 0, titleSize.width, titleSize.height);
  261. //Store the titleView as an instance variable if it is larger than 0 height (not an empty string)
  262. if (titleSize.height > 0) {
  263. self.titleView = titleLabel;
  264. }
  265. [container addSubview:[titleLabel AUTORELEASE]];
  266. container.frame = CGRectMake(0, 0, totalWidth, totalHeight);
  267. self.subviewsArray = viewArray;
  268. [self showAtPoint:point inView:view withContentView:[container AUTORELEASE]];
  269. }
  270. - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray
  271. {
  272. NSMutableArray *labelArray = [[NSMutableArray alloc] initWithCapacity:stringArray.count];
  273. UIFont *font = kTextFont;
  274. for (NSString *string in stringArray) {
  275. CGSize textSize = [string sizeWithAttributes:@{NSFontAttributeName : font}];
  276. UIButton *textButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)];
  277. textButton.backgroundColor = [UIColor clearColor];
  278. textButton.titleLabel.font = font;
  279. textButton.titleLabel.textAlignment = kTextAlignment;
  280. textButton.titleLabel.textColor = kTextColor;
  281. [textButton setTitle:string forState:UIControlStateNormal];
  282. textButton.layer.cornerRadius = 4.f;
  283. [textButton setTitleColor:kTextColor forState:UIControlStateNormal];
  284. [textButton setTitleColor:kTextHighlightColor forState:UIControlStateHighlighted];
  285. [textButton addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside];
  286. [labelArray addObject:[textButton AUTORELEASE]];
  287. }
  288. [self showAtPoint:point inView:view withViewArray:[labelArray AUTORELEASE]];
  289. }
  290. - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray
  291. {
  292. NSMutableArray *labelArray = [[NSMutableArray alloc] initWithCapacity:stringArray.count];
  293. UIFont *font = kTextFont;
  294. for (NSString *string in stringArray) {
  295. CGSize textSize = [string sizeWithAttributes:@{NSFontAttributeName : font}];
  296. UIButton *textButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)];
  297. textButton.backgroundColor = [UIColor clearColor];
  298. textButton.titleLabel.font = font;
  299. textButton.titleLabel.textAlignment = kTextAlignment;
  300. textButton.titleLabel.textColor = kTextColor;
  301. [textButton setTitle:string forState:UIControlStateNormal];
  302. textButton.layer.cornerRadius = 4.f;
  303. [textButton setTitleColor:kTextColor forState:UIControlStateNormal];
  304. [textButton setTitleColor:kTextHighlightColor forState:UIControlStateHighlighted];
  305. [textButton addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside];
  306. [labelArray addObject:[textButton AUTORELEASE]];
  307. }
  308. [self showAtPoint:point inView:view withTitle:title withViewArray:[labelArray AUTORELEASE]];
  309. }
  310. - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray
  311. {
  312. //Here we do something pretty similar to the stringArray method above.
  313. //We create an array of subviews that contains the strings and images centered above a label.
  314. NSAssert((stringArray.count == imageArray.count), @"stringArray.count should equal imageArray.count");
  315. NSMutableArray* tempViewArray = [self makeTempViewsWithStrings:stringArray andImages:imageArray];
  316. [self showAtPoint:point inView:view withViewArray:tempViewArray];
  317. }
  318. - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray
  319. {
  320. NSAssert((stringArray.count == imageArray.count), @"stringArray.count should equal imageArray.count");
  321. NSMutableArray* tempViewArray = [self makeTempViewsWithStrings:stringArray andImages:imageArray];
  322. [self showAtPoint:point inView:view withTitle:title withViewArray:tempViewArray];
  323. }
  324. - (NSMutableArray*) makeTempViewsWithStrings:(NSArray *)stringArray andImages:(NSArray *)imageArray
  325. {
  326. NSMutableArray *tempViewArray = [[NSMutableArray alloc] initWithCapacity:stringArray.count];
  327. UIFont *font = kTextFont;
  328. for (int i = 0; i < stringArray.count; i++) {
  329. NSString *string = [stringArray objectAtIndex:i];
  330. //First we build a label for the text to set in.
  331. CGSize textSize = [string sizeWithAttributes:@{NSFontAttributeName : font}];
  332. UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)];
  333. label.backgroundColor = [UIColor clearColor];
  334. label.font = font;
  335. label.textAlignment = kTextAlignment;
  336. label.textColor = kTextColor;
  337. label.text = string;
  338. label.layer.cornerRadius = 4.f;
  339. //Now we grab the image at the same index in the imageArray, and create
  340. //a UIImageView for it.
  341. UIImage *image = [imageArray objectAtIndex:i];
  342. UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
  343. //Take the larger of the two widths as the width for the container
  344. float containerWidth = MAX(imageView.frame.size.width, label.frame.size.width);
  345. float containerHeight = label.frame.size.height + kImageTopPadding + kImageBottomPadding + imageView.frame.size.height;
  346. //This container will hold both the image and the label
  347. UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, containerWidth, containerHeight)];
  348. //Now we do the frame manipulations to put the imageView on top of the label, both centered
  349. imageView.frame = CGRectMake(floorf(containerWidth*0.5f - imageView.frame.size.width*0.5f), kImageTopPadding, imageView.frame.size.width, imageView.frame.size.height);
  350. 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);
  351. [containerView addSubview:imageView];
  352. [containerView addSubview:label];
  353. [label RELEASE];
  354. [imageView RELEASE];
  355. [tempViewArray addObject:containerView];
  356. [containerView RELEASE];
  357. }
  358. return [tempViewArray AUTORELEASE];
  359. }
  360. - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withContentView:(UIView *)cView
  361. {
  362. [self showAtPoint:point inView:view withTitle:title withViewArray:[NSArray arrayWithObject:cView]];
  363. }
  364. - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withContentView:(UIView *)cView {
  365. //NSLog(@"point:%f,%f", point.x, point.y);
  366. self.contentView = cView;
  367. parentView = view;
  368. // get the top view
  369. // http://stackoverflow.com/questions/3843411/getting-reference-to-the-top-most-view-window-in-ios-application/8045804#8045804
  370. topView = [[[[UIApplication sharedApplication] keyWindow] subviews] lastObject];
  371. [self setupLayout:point inView:view];
  372. // Make the view small and transparent before animation
  373. self.alpha = 0.f;
  374. self.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
  375. // animate into full size
  376. // First stage animates to 1.05x normal size, then second stage animates back down to 1x size.
  377. // This two-stage animation creates a little "pop" on open.
  378. [UIView animateWithDuration:0.2f delay:0.f options:UIViewAnimationOptionCurveEaseInOut animations:^{
  379. self.alpha = 1.f;
  380. self.transform = CGAffineTransformMakeScale(1.05f, 1.05f);
  381. } completion:^(BOOL finished) {
  382. [UIView animateWithDuration:0.08f delay:0.f options:UIViewAnimationOptionCurveEaseInOut animations:^{
  383. self.transform = CGAffineTransformIdentity;
  384. } completion:nil];
  385. }];
  386. }
  387. - (void)layoutAtPoint:(CGPoint)point inView:(UIView *)view
  388. {
  389. // make transparent
  390. self.alpha = 0.f;
  391. [self setupLayout:point inView:view];
  392. // animate back to full opacity
  393. [UIView animateWithDuration:0.2f delay:0.f options:UIViewAnimationOptionCurveEaseInOut animations:^{
  394. self.alpha = 1.f;
  395. } completion:nil];
  396. }
  397. -(void)setupLayout:(CGPoint)point inView:(UIView*)view
  398. {
  399. CGPoint topPoint = [topView convertPoint:point fromView:view];
  400. arrowPoint = topPoint;
  401. //NSLog(@"arrowPoint:%f,%f", arrowPoint.x, arrowPoint.y);
  402. CGRect topViewBounds = topView.bounds;
  403. //NSLog(@"topViewBounds %@", NSStringFromCGRect(topViewBounds));
  404. float contentHeight = contentView.frame.size.height;
  405. float contentWidth = contentView.frame.size.width;
  406. float padding = kBoxPadding;
  407. float boxHeight = contentHeight + 2.f*padding;
  408. float boxWidth = contentWidth + 2.f*padding;
  409. float xOrigin = 0.f;
  410. //Make sure the arrow point is within the drawable bounds for the popover.
  411. if (arrowPoint.x + kArrowHeight > topViewBounds.size.width - kHorizontalMargin - kBoxRadius - kArrowHorizontalPadding) {//Too far to the right
  412. arrowPoint.x = topViewBounds.size.width - kHorizontalMargin - kBoxRadius - kArrowHorizontalPadding - kArrowHeight;
  413. //NSLog(@"Correcting Arrow Point because it's too far to the right");
  414. } else if (arrowPoint.x - kArrowHeight < kHorizontalMargin + kBoxRadius + kArrowHorizontalPadding) {//Too far to the left
  415. arrowPoint.x = kHorizontalMargin + kArrowHeight + kBoxRadius + kArrowHorizontalPadding;
  416. //NSLog(@"Correcting Arrow Point because it's too far to the left");
  417. }
  418. //NSLog(@"arrowPoint:%f,%f", arrowPoint.x, arrowPoint.y);
  419. xOrigin = floorf(arrowPoint.x - boxWidth*0.5f);
  420. //Check to see if the centered xOrigin value puts the box outside of the normal range.
  421. if (xOrigin < CGRectGetMinX(topViewBounds) + kHorizontalMargin) {
  422. xOrigin = CGRectGetMinX(topViewBounds) + kHorizontalMargin;
  423. } else if (xOrigin + boxWidth > CGRectGetMaxX(topViewBounds) - kHorizontalMargin) {
  424. //Check to see if the positioning puts the box out of the window towards the left
  425. xOrigin = CGRectGetMaxX(topViewBounds) - kHorizontalMargin - boxWidth;
  426. }
  427. float arrowHeight = kArrowHeight;
  428. float topPadding = kTopMargin;
  429. above = YES;
  430. if (topPoint.y - contentHeight - arrowHeight - topPadding < CGRectGetMinY(topViewBounds)) {
  431. //Position below because it won't fit above.
  432. above = NO;
  433. boxFrame = CGRectMake(xOrigin, arrowPoint.y + arrowHeight, boxWidth, boxHeight);
  434. } else {
  435. //Position above.
  436. above = YES;
  437. boxFrame = CGRectMake(xOrigin, arrowPoint.y - arrowHeight - boxHeight, boxWidth, boxHeight);
  438. }
  439. //NSLog(@"boxFrame:(%f,%f,%f,%f)", boxFrame.origin.x, boxFrame.origin.y, boxFrame.size.width, boxFrame.size.height);
  440. CGRect contentFrame = CGRectMake(boxFrame.origin.x + padding, boxFrame.origin.y + padding, contentWidth, contentHeight);
  441. contentView.frame = contentFrame;
  442. //We set the anchorPoint here so the popover will "grow" out of the arrowPoint specified by the user.
  443. //You have to set the anchorPoint before setting the frame, because the anchorPoint property will
  444. //implicitly set the frame for the view, which we do not want.
  445. self.layer.anchorPoint = CGPointMake(arrowPoint.x / topViewBounds.size.width, arrowPoint.y / topViewBounds.size.height);
  446. self.frame = topViewBounds;
  447. [self setNeedsDisplay];
  448. [self addSubview:contentView];
  449. [topView addSubview:self];
  450. //Add a tap gesture recognizer to the large invisible view (self), which will detect taps anywhere on the screen.
  451. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)];
  452. tap.cancelsTouchesInView = NO; // Allow touches through to a UITableView or other touchable view, as suggested by Dimajp.
  453. [self addGestureRecognizer:tap];
  454. [tap RELEASE];
  455. self.userInteractionEnabled = YES;
  456. }
  457. #pragma mark - Activity Indicator
  458. //Animates in a progress indicator, and removes
  459. - (void)showActivityIndicatorWithMessage:(NSString *)msg
  460. {
  461. if ([titleView isKindOfClass:[UILabel class]]) {
  462. ((UILabel *)titleView).text = msg;
  463. }
  464. if (subviewsArray && (subviewsArray.count > 0)) {
  465. [UIView animateWithDuration:0.2f animations:^{
  466. for (UIView *view in subviewsArray) {
  467. view.alpha = 0.f;
  468. }
  469. }];
  470. if (showDividerRects) {
  471. showDividerRects = NO;
  472. [self setNeedsDisplay];
  473. }
  474. }
  475. if (activityIndicator) {
  476. [activityIndicator RELEASE];
  477. [activityIndicator removeFromSuperview];
  478. activityIndicator = nil;
  479. }
  480. activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
  481. activityIndicator.frame = CGRectMake(CGRectGetMidX(contentView.bounds) - 10.f, CGRectGetMidY(contentView.bounds) - 10.f + 20.f, 20.f, 20.f);
  482. [contentView addSubview:activityIndicator];
  483. [activityIndicator startAnimating];
  484. }
  485. - (void)hideActivityIndicatorWithMessage:(NSString *)msg
  486. {
  487. if ([titleView isKindOfClass:[UILabel class]]) {
  488. ((UILabel *)titleView).text = msg;
  489. }
  490. [activityIndicator stopAnimating];
  491. [UIView animateWithDuration:0.1f animations:^{
  492. activityIndicator.alpha = 0.f;
  493. } completion:^(BOOL finished) {
  494. [activityIndicator RELEASE];
  495. [activityIndicator removeFromSuperview];
  496. activityIndicator = nil;
  497. }];
  498. }
  499. - (void)showImage:(UIImage *)image withMessage:(NSString *)msg
  500. {
  501. UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
  502. imageView.alpha = 0.f;
  503. 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);
  504. imageView.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
  505. [contentView addSubview:[imageView AUTORELEASE]];
  506. if (subviewsArray && (subviewsArray.count > 0)) {
  507. [UIView animateWithDuration:0.2f animations:^{
  508. for (UIView *view in subviewsArray) {
  509. view.alpha = 0.f;
  510. }
  511. }];
  512. if (showDividerRects) {
  513. showDividerRects = NO;
  514. [self setNeedsDisplay];
  515. }
  516. }
  517. if (msg) {
  518. if ([titleView isKindOfClass:[UILabel class]]) {
  519. ((UILabel *)titleView).text = msg;
  520. }
  521. }
  522. [UIView animateWithDuration:0.2f delay:0.2f options:UIViewAnimationOptionCurveEaseOut animations:^{
  523. imageView.alpha = 1.f;
  524. imageView.transform = CGAffineTransformIdentity;
  525. } completion:^(BOOL finished) {
  526. //[imageView removeFromSuperview];
  527. }];
  528. }
  529. - (void)showError
  530. {
  531. UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"error"]];
  532. imageView.alpha = 0.f;
  533. imageView.frame = CGRectMake(CGRectGetMidX(contentView.bounds) - 20.f, CGRectGetMidY(contentView.bounds) - 20.f + ((self.titleView) ? 20 : 0.f), 40.f, 40.f);
  534. imageView.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
  535. [contentView addSubview:[imageView AUTORELEASE]];
  536. if (subviewsArray && (subviewsArray.count > 0)) {
  537. [UIView animateWithDuration:0.1f animations:^{
  538. for (UIView *view in subviewsArray) {
  539. view.alpha = 0.f;
  540. }
  541. }];
  542. if (showDividerRects) {
  543. showDividerRects = NO;
  544. [self setNeedsDisplay];
  545. }
  546. }
  547. [UIView animateWithDuration:0.1f delay:0.1f options:UIViewAnimationOptionCurveEaseOut animations:^{
  548. imageView.alpha = 1.f;
  549. imageView.transform = CGAffineTransformIdentity;
  550. } completion:^(BOOL finished) {
  551. //[imageView removeFromSuperview];
  552. }];
  553. }
  554. - (void)showSuccess
  555. {
  556. UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"success"]];
  557. imageView.alpha = 0.f;
  558. imageView.frame = CGRectMake(CGRectGetMidX(contentView.bounds) - 20.f, CGRectGetMidY(contentView.bounds) - 20.f + ((self.titleView) ? 20 : 0.f), 40.f, 40.f);
  559. imageView.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
  560. [contentView addSubview:[imageView AUTORELEASE]];
  561. if (subviewsArray && (subviewsArray.count > 0)) {
  562. [UIView animateWithDuration:0.1f animations:^{
  563. for (UIView *view in subviewsArray) {
  564. view.alpha = 0.f;
  565. }
  566. }];
  567. if (showDividerRects) {
  568. showDividerRects = NO;
  569. [self setNeedsDisplay];
  570. }
  571. }
  572. [UIView animateWithDuration:0.1f delay:0.1f options:UIViewAnimationOptionCurveEaseOut animations:^{
  573. imageView.alpha = 1.f;
  574. imageView.transform = CGAffineTransformIdentity;
  575. } completion:^(BOOL finished) {
  576. //[imageView removeFromSuperview];
  577. }];
  578. }
  579. #pragma mark - User Interaction
  580. - (void)tapped:(UITapGestureRecognizer *)tap
  581. {
  582. CGPoint point = [tap locationInView:contentView];
  583. //NSLog(@"point:(%f,%f)", point.x, point.y);
  584. BOOL found = NO;
  585. //NSLog(@"subviewsArray:%@", subviewsArray);
  586. for (int i = 0; i < subviewsArray.count && !found; i++) {
  587. UIView *view = [subviewsArray objectAtIndex:i];
  588. //NSLog(@"Rect:(%f,%f,%f,%f)", view.frame.origin.x, view.frame.origin.y, view.frame.size.width, view.frame.size.height);
  589. if (CGRectContainsPoint(view.frame, point)) {
  590. //The tap was within this view, so we notify the delegate, and break the loop.
  591. found = YES;
  592. //NSLog(@"Tapped subview:%d", i);
  593. if ([view isKindOfClass:[UIButton class]]) {
  594. return;
  595. }
  596. if (delegate && [delegate respondsToSelector:@selector(popoverView:didSelectItemAtIndex:)]) {
  597. [delegate popoverView:self didSelectItemAtIndex:i];
  598. }
  599. break;
  600. }
  601. }
  602. if (!found && CGRectContainsPoint(contentView.bounds, point)) {
  603. found = YES;
  604. //NSLog(@"popover box contains point, ignoring user input");
  605. }
  606. if (!found) {
  607. [self dismiss:YES];
  608. }
  609. }
  610. - (void)didTapButton:(UIButton *)sender
  611. {
  612. NSUInteger index = [subviewsArray indexOfObject:sender];
  613. if (index == NSNotFound) {
  614. return;
  615. }
  616. if (delegate && [delegate respondsToSelector:@selector(popoverView:didSelectItemAtIndex:)]) {
  617. [delegate popoverView:self didSelectItemAtIndex:index];
  618. }
  619. }
  620. - (void)dismiss
  621. {
  622. [self dismiss:YES];
  623. }
  624. - (void)dismiss:(BOOL)animated
  625. {
  626. if (!animated)
  627. {
  628. [self dismissComplete];
  629. }
  630. else
  631. {
  632. [UIView animateWithDuration:0.3f animations:^{
  633. self.alpha = 0.1f;
  634. self.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
  635. } completion:^(BOOL finished) {
  636. [self dismissComplete];
  637. }];
  638. }
  639. }
  640. - (void)dismissComplete
  641. {
  642. [self removeFromSuperview];
  643. if (self.delegate && [self.delegate respondsToSelector:@selector(popoverViewDidDismiss:)]) {
  644. [delegate popoverViewDidDismiss:self];
  645. }
  646. }
  647. - (void)animateRotationToNewPoint:(CGPoint)point inView:(UIView *)view withDuration:(NSTimeInterval)duration
  648. {
  649. [self layoutAtPoint:point inView:view];
  650. }
  651. #pragma mark - Drawing Routines
  652. // Only override drawRect: if you perform custom drawing.
  653. // An empty implementation adversely affects performance during animation.
  654. - (void)drawRect:(CGRect)rect
  655. {
  656. // Drawing code
  657. // Build the popover path
  658. CGRect frame = boxFrame;
  659. float xMin = CGRectGetMinX(frame);
  660. float yMin = CGRectGetMinY(frame);
  661. float xMax = CGRectGetMaxX(frame);
  662. float yMax = CGRectGetMaxY(frame);
  663. float radius = kBoxRadius; //Radius of the curvature.
  664. float cpOffset = kCPOffset; //Control Point Offset. Modifies how "curved" the corners are.
  665. /*
  666. LT2 RT1
  667. LT1⌜⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⌝RT2
  668. | |
  669. | popover |
  670. | |
  671. LB2⌞_______________⌟RB1
  672. LB1 RB2
  673. Traverse rectangle in clockwise order, starting at LT1
  674. L = Left
  675. R = Right
  676. T = Top
  677. B = Bottom
  678. 1,2 = order of traversal for any given corner
  679. */
  680. UIBezierPath *popoverPath = [UIBezierPath bezierPath];
  681. [popoverPath moveToPoint:CGPointMake(CGRectGetMinX(frame), CGRectGetMinY(frame) + radius)];//LT1
  682. [popoverPath addCurveToPoint:CGPointMake(xMin + radius, yMin) controlPoint1:CGPointMake(xMin, yMin + radius - cpOffset) controlPoint2:CGPointMake(xMin + radius - cpOffset, yMin)];//LT2
  683. //If the popover is positioned below (!above) the arrowPoint, then we know that the arrow must be on the top of the popover.
  684. //In this case, the arrow is located between LT2 and RT1
  685. if (!above) {
  686. [popoverPath addLineToPoint:CGPointMake(arrowPoint.x - kArrowHeight, yMin)];//left side
  687. [popoverPath addCurveToPoint:arrowPoint controlPoint1:CGPointMake(arrowPoint.x - kArrowHeight + kArrowCurvature, yMin) controlPoint2:arrowPoint];//actual arrow point
  688. [popoverPath addCurveToPoint:CGPointMake(arrowPoint.x + kArrowHeight, yMin) controlPoint1:arrowPoint controlPoint2:CGPointMake(arrowPoint.x + kArrowHeight - kArrowCurvature, yMin)];//right side
  689. }
  690. [popoverPath addLineToPoint:CGPointMake(xMax - radius, yMin)];//RT1
  691. [popoverPath addCurveToPoint:CGPointMake(xMax, yMin + radius) controlPoint1:CGPointMake(xMax - radius + cpOffset, yMin) controlPoint2:CGPointMake(xMax, yMin + radius - cpOffset)];//RT2
  692. [popoverPath addLineToPoint:CGPointMake(xMax, yMax - radius)];//RB1
  693. [popoverPath addCurveToPoint:CGPointMake(xMax - radius, yMax) controlPoint1:CGPointMake(xMax, yMax - radius + cpOffset) controlPoint2:CGPointMake(xMax - radius + cpOffset, yMax)];//RB2
  694. //If the popover is positioned above the arrowPoint, then we know that the arrow must be on the bottom of the popover.
  695. //In this case, the arrow is located somewhere between LB1 and RB2
  696. if (above) {
  697. [popoverPath addLineToPoint:CGPointMake(arrowPoint.x + kArrowHeight, yMax)];//right side
  698. [popoverPath addCurveToPoint:arrowPoint controlPoint1:CGPointMake(arrowPoint.x + kArrowHeight - kArrowCurvature, yMax) controlPoint2:arrowPoint];//arrow point
  699. [popoverPath addCurveToPoint:CGPointMake(arrowPoint.x - kArrowHeight, yMax) controlPoint1:arrowPoint controlPoint2:CGPointMake(arrowPoint.x - kArrowHeight + kArrowCurvature, yMax)];
  700. }
  701. [popoverPath addLineToPoint:CGPointMake(xMin + radius, yMax)];//LB1
  702. [popoverPath addCurveToPoint:CGPointMake(xMin, yMax - radius) controlPoint1:CGPointMake(xMin + radius - cpOffset, yMax) controlPoint2:CGPointMake(xMin, yMax - radius + cpOffset)];//LB2
  703. [popoverPath closePath];
  704. //// General Declarations
  705. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  706. CGContextRef context = UIGraphicsGetCurrentContext();
  707. //// Shadow Declarations
  708. UIColor* shadow = [UIColor colorWithWhite:0.0f alpha:kShadowAlpha];
  709. CGSize shadowOffset = CGSizeMake(0, 1);
  710. CGFloat shadowBlurRadius = kShadowBlur;
  711. //// Gradient Declarations
  712. NSArray* gradientColors = [NSArray arrayWithObjects:
  713. (id)kGradientTopColor.CGColor,
  714. (id)kGradientBottomColor.CGColor, nil];
  715. CGFloat gradientLocations[] = {0, 1};
  716. CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFTYPECAST(CFArrayRef)gradientColors), gradientLocations);
  717. //These floats are the top and bottom offsets for the gradient drawing so the drawing includes the arrows.
  718. float bottomOffset = (above ? kArrowHeight : 0.f);
  719. float topOffset = (!above ? kArrowHeight : 0.f);
  720. //Draw the actual gradient and shadow.
  721. CGContextSaveGState(context);
  722. CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, shadow.CGColor);
  723. CGContextBeginTransparencyLayer(context, NULL);
  724. [popoverPath addClip];
  725. CGContextDrawLinearGradient(context, gradient, CGPointMake(CGRectGetMidX(frame), CGRectGetMinY(frame) - topOffset), CGPointMake(CGRectGetMidX(frame), CGRectGetMaxY(frame) + bottomOffset), 0);
  726. CGContextEndTransparencyLayer(context);
  727. CGContextRestoreGState(context);
  728. //// Cleanup
  729. CGGradientRelease(gradient);
  730. CGColorSpaceRelease(colorSpace);
  731. //Draw the title background
  732. if (kDrawTitleGradient) {
  733. //Calculate the height of the title bg
  734. float titleBGHeight = -1;
  735. //NSLog(@"titleView:%@", titleView);
  736. if (titleView != nil) {
  737. titleBGHeight = kBoxPadding*2.f + titleView.frame.size.height;
  738. }
  739. //Draw the title bg height, but only if we need to.
  740. if (titleBGHeight > 0.f) {
  741. CGPoint startingPoint = CGPointMake(xMin, yMin + titleBGHeight);
  742. CGPoint endingPoint = CGPointMake(xMax, yMin + titleBGHeight);
  743. UIBezierPath *titleBGPath = [UIBezierPath bezierPath];
  744. [titleBGPath moveToPoint:startingPoint];
  745. [titleBGPath addLineToPoint:CGPointMake(CGRectGetMinX(frame), CGRectGetMinY(frame) + radius)];//LT1
  746. [titleBGPath addCurveToPoint:CGPointMake(xMin + radius, yMin) controlPoint1:CGPointMake(xMin, yMin + radius - cpOffset) controlPoint2:CGPointMake(xMin + radius - cpOffset, yMin)];//LT2
  747. //If the popover is positioned below (!above) the arrowPoint, then we know that the arrow must be on the top of the popover.
  748. //In this case, the arrow is located between LT2 and RT1
  749. if (!above) {
  750. [titleBGPath addLineToPoint:CGPointMake(arrowPoint.x - kArrowHeight, yMin)];//left side
  751. [titleBGPath addCurveToPoint:arrowPoint controlPoint1:CGPointMake(arrowPoint.x - kArrowHeight + kArrowCurvature, yMin) controlPoint2:arrowPoint];//actual arrow point
  752. [titleBGPath addCurveToPoint:CGPointMake(arrowPoint.x + kArrowHeight, yMin) controlPoint1:arrowPoint controlPoint2:CGPointMake(arrowPoint.x + kArrowHeight - kArrowCurvature, yMin)];//right side
  753. }
  754. [titleBGPath addLineToPoint:CGPointMake(xMax - radius, yMin)];//RT1
  755. [titleBGPath addCurveToPoint:CGPointMake(xMax, yMin + radius) controlPoint1:CGPointMake(xMax - radius + cpOffset, yMin) controlPoint2:CGPointMake(xMax, yMin + radius - cpOffset)];//RT2
  756. [titleBGPath addLineToPoint:endingPoint];
  757. [titleBGPath addLineToPoint:startingPoint];
  758. [titleBGPath closePath];
  759. //// General Declarations
  760. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  761. CGContextRef context = UIGraphicsGetCurrentContext();
  762. //// Gradient Declarations
  763. NSArray* gradientColors = [NSArray arrayWithObjects:
  764. (id)kGradientTitleTopColor.CGColor,
  765. (id)kGradientTitleBottomColor.CGColor, nil];
  766. CGFloat gradientLocations[] = {0, 1};
  767. CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFTYPECAST(CFArrayRef)gradientColors), gradientLocations);
  768. //These floats are the top and bottom offsets for the gradient drawing so the drawing includes the arrows.
  769. float topOffset = (!above ? kArrowHeight : 0.f);
  770. //Draw the actual gradient and shadow.
  771. CGContextSaveGState(context);
  772. CGContextBeginTransparencyLayer(context, NULL);
  773. [titleBGPath addClip];
  774. CGContextDrawLinearGradient(context, gradient, CGPointMake(CGRectGetMidX(frame), CGRectGetMinY(frame) - topOffset), CGPointMake(CGRectGetMidX(frame), CGRectGetMinY(frame) + titleBGHeight), 0);
  775. CGContextEndTransparencyLayer(context);
  776. CGContextRestoreGState(context);
  777. UIBezierPath *dividerLine = [UIBezierPath bezierPathWithRect:CGRectMake(startingPoint.x, startingPoint.y, (endingPoint.x - startingPoint.x), 0.5f)];
  778. [[UIColor colorWithRed:0.741 green:0.741 blue:0.741 alpha:0.5f] setFill];
  779. [dividerLine fill];
  780. //// Cleanup
  781. CGGradientRelease(gradient);
  782. CGColorSpaceRelease(colorSpace);
  783. }
  784. }
  785. //Draw the divider rects if we need to
  786. {
  787. if (kShowDividersBetweenViews && showDividerRects) {
  788. if (dividerRects && dividerRects.count > 0) {
  789. for (NSValue *value in dividerRects) {
  790. CGRect rect = value.CGRectValue;
  791. rect.origin.x += contentView.frame.origin.x;
  792. rect.origin.y += contentView.frame.origin.y;
  793. UIBezierPath *dividerPath = [UIBezierPath bezierPathWithRect:rect];
  794. [kDividerColor setFill];
  795. [dividerPath fill];
  796. }
  797. }
  798. }
  799. }
  800. //Draw border if we need to
  801. //The border is done last because it needs to be drawn on top of everything else
  802. if (kDrawBorder) {
  803. [kBorderColor setStroke];
  804. popoverPath.lineWidth = kBorderWidth;
  805. [popoverPath stroke];
  806. }
  807. }
  808. @end