QBPopupMenu.m 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926
  1. //
  2. // QBPopupMenu.m
  3. // QBPopupMenu
  4. //
  5. // Created by Tanaka Katsuma on 2013/11/22.
  6. // Copyright (c) 2013年 Katsuma Tanaka. All rights reserved.
  7. //
  8. #import "QBPopupMenu.h"
  9. #import "QBPopupMenuOverlayView.h"
  10. #import "QBPopupMenuItemView.h"
  11. #import "QBPopupMenuPagenatorView.h"
  12. static const NSTimeInterval kQBPopupMenuAnimationDuration = 0.2;
  13. @interface QBPopupMenu ()
  14. @property (nonatomic, assign, getter = isVisible, readwrite) BOOL visible;
  15. @property (nonatomic, strong) QBPopupMenuOverlayView *overlayView;
  16. @property (nonatomic, weak) UIView *view;
  17. @property (nonatomic, assign) CGRect targetRect;
  18. @property (nonatomic, assign) NSUInteger page;
  19. @property (nonatomic, assign) QBPopupMenuArrowDirection actualArrorDirection;
  20. @property (nonatomic, assign) CGPoint arrowPoint;
  21. @property (nonatomic, strong) NSMutableArray *itemViews;
  22. @property (nonatomic, strong) NSMutableArray *groupedItemViews;
  23. @property (nonatomic, strong) NSMutableArray *visibleItemViews;
  24. @end
  25. @implementation QBPopupMenu
  26. + (Class)itemViewClass
  27. {
  28. return [QBPopupMenuItemView class];
  29. }
  30. + (Class)pagenatorViewClass
  31. {
  32. return [QBPopupMenuPagenatorView class];
  33. }
  34. + (instancetype)popupMenuWithItems:(NSArray *)items
  35. {
  36. return [[self alloc] initWithItems:items];
  37. }
  38. - (instancetype)initWithItems:(NSArray *)items
  39. {
  40. self = [super initWithFrame:CGRectZero];
  41. if (self) {
  42. // View settings
  43. self.opaque = NO;
  44. self.backgroundColor = [UIColor clearColor];
  45. self.clipsToBounds = YES;
  46. // Property settings
  47. self.items = items;
  48. self.height = 36;
  49. self.cornerRadius = 8;
  50. self.arrowSize = 9;
  51. self.arrowDirection = QBPopupMenuArrowDirectionDefault;
  52. self.popupMenuInsets = UIEdgeInsetsMake(10, 10, 10, 10);
  53. self.margin = 2;
  54. self.color = [[UIColor blackColor] colorWithAlphaComponent:0.8];
  55. self.highlightedColor = [[UIColor darkGrayColor] colorWithAlphaComponent:0.8];
  56. }
  57. return self;
  58. }
  59. #pragma mark - Accessors
  60. - (void)setItems:(NSArray *)items
  61. {
  62. _items = items;
  63. // Create item views
  64. [self createItemViews];
  65. }
  66. - (void)setHeight:(CGFloat)height
  67. {
  68. _height = height;
  69. // Update view
  70. CGRect frame = self.frame;
  71. frame.size.height = height;
  72. self.frame = frame;
  73. }
  74. #pragma mark - Managing Popup Menu
  75. - (void)showInView:(UIView *)view targetRect:(CGRect)targetRect animated:(BOOL)animated
  76. {
  77. if ([self isVisible]) {
  78. return;
  79. }
  80. self.view = view;
  81. self.targetRect = targetRect;
  82. // Decide arrow direction
  83. QBPopupMenuArrowDirection arrowDirection = self.arrowDirection;
  84. if (arrowDirection == QBPopupMenuArrowDirectionDefault) {
  85. if ((targetRect.origin.y - (self.height + self.arrowSize)) >= self.popupMenuInsets.top) {
  86. arrowDirection = QBPopupMenuArrowDirectionDown;
  87. }
  88. else if ((targetRect.origin.y + targetRect.size.height + (self.height + self.arrowSize)) < (view.bounds.size.height - self.popupMenuInsets.bottom)) {
  89. arrowDirection = QBPopupMenuArrowDirectionUp;
  90. }
  91. else {
  92. CGFloat left = targetRect.origin.x - self.popupMenuInsets.left;
  93. CGFloat right = view.bounds.size.width - (targetRect.origin.x + targetRect.size.width + self.popupMenuInsets.right);
  94. arrowDirection = (left > right) ? QBPopupMenuArrowDirectionLeft : QBPopupMenuArrowDirectionRight;
  95. }
  96. }
  97. self.actualArrorDirection = arrowDirection;
  98. // Calculate width
  99. CGFloat maximumWidth = 0;
  100. CGFloat minimumWidth = 40;
  101. switch (arrowDirection) {
  102. case QBPopupMenuArrowDirectionDown:
  103. case QBPopupMenuArrowDirectionUp:
  104. maximumWidth = view.bounds.size.width - (self.popupMenuInsets.left + self.popupMenuInsets.right);
  105. if (maximumWidth < minimumWidth) maximumWidth = minimumWidth;
  106. break;
  107. case QBPopupMenuArrowDirectionLeft:
  108. maximumWidth = targetRect.origin.x - self.popupMenuInsets.left;
  109. break;
  110. case QBPopupMenuArrowDirectionRight:
  111. maximumWidth = view.bounds.size.width - (targetRect.origin.x + targetRect.size.width + self.popupMenuInsets.right);
  112. break;
  113. default:
  114. break;
  115. }
  116. // Layout item views
  117. [self groupItemViewsWithMaximumWidth:maximumWidth];
  118. // Show page
  119. [self showPage:0];
  120. // Create overlay view
  121. self.overlayView = ({
  122. QBPopupMenuOverlayView *overlayView = [[QBPopupMenuOverlayView alloc] initWithFrame:view.bounds];
  123. overlayView.popupMenu = self;
  124. overlayView;
  125. });
  126. // Delegate
  127. if (self.delegate && [self.delegate respondsToSelector:@selector(popupMenuWillAppear:)]) {
  128. [self.delegate popupMenuWillAppear:self];
  129. }
  130. // Show
  131. [view addSubview:self.overlayView];
  132. if (animated) {
  133. self.alpha = 0;
  134. [self.overlayView addSubview:self];
  135. [UIView animateWithDuration:kQBPopupMenuAnimationDuration animations:^(void) {
  136. self.alpha = 1.0;
  137. } completion:^(BOOL finished) {
  138. self.visible = YES;
  139. // Delegate
  140. if (self.delegate && [self.delegate respondsToSelector:@selector(popupMenuDidAppear:)]) {
  141. [self.delegate popupMenuDidAppear:self];
  142. }
  143. }];
  144. } else {
  145. [self.overlayView addSubview:self];
  146. self.visible = YES;
  147. // Delegate
  148. if (self.delegate && [self.delegate respondsToSelector:@selector(popupMenuDidAppear:)]) {
  149. [self.delegate popupMenuDidAppear:self];
  150. }
  151. }
  152. }
  153. - (void)dismissAnimated:(BOOL)animated
  154. {
  155. if (![self isVisible]) {
  156. return;
  157. }
  158. // Delegate
  159. if (self.delegate && [self.delegate respondsToSelector:@selector(popupMenuWillDisappear:)]) {
  160. [self.delegate popupMenuWillDisappear:self];
  161. }
  162. if (animated) {
  163. [UIView animateWithDuration:kQBPopupMenuAnimationDuration animations:^{
  164. self.alpha = 0;
  165. } completion:^(BOOL finished) {
  166. [self removeFromSuperview];
  167. [self.overlayView removeFromSuperview];
  168. self.visible = NO;
  169. // Delegate
  170. if (self.delegate && [self.delegate respondsToSelector:@selector(popupMenuDidDisappear:)]) {
  171. [self.delegate popupMenuDidDisappear:self];
  172. }
  173. }];
  174. } else {
  175. [self removeFromSuperview];
  176. [self.overlayView removeFromSuperview];
  177. self.visible = NO;
  178. // Delegate
  179. if (self.delegate && [self.delegate respondsToSelector:@selector(popupMenuDidDisappear:)]) {
  180. [self.delegate popupMenuDidDisappear:self];
  181. }
  182. }
  183. }
  184. - (void)updateWithTargetRect:(CGRect)targetRect
  185. {
  186. self.targetRect = targetRect;
  187. [self updatePopupMenuFrameAndArrowPosition];
  188. [self updatePopupMenuImage];
  189. }
  190. - (void)showPreviousPage
  191. {
  192. [self showPage:(self.page - 1)];
  193. }
  194. - (void)showNextPage
  195. {
  196. [self showPage:(self.page + 1)];
  197. }
  198. - (void)showPage:(NSUInteger)page
  199. {
  200. self.page = page;
  201. [self updateVisibleItemViewsWithPage:page];
  202. [self layoutVisibleItemViews];
  203. [self updatePopupMenuFrameAndArrowPosition];
  204. [self updatePopupMenuImage];
  205. }
  206. #pragma mark - Updating Content
  207. - (void)createItemViews
  208. {
  209. NSMutableArray *itemViews = [NSMutableArray array];
  210. for (QBPopupMenuItem *item in self.items) {
  211. QBPopupMenuItemView *itemView = [[[self class] itemViewClass] itemViewWithItem:item];
  212. itemView.popupMenu = self;
  213. [itemViews addObject:itemView];
  214. }
  215. self.itemViews = itemViews;
  216. }
  217. - (void)resetItemViewState:(QBPopupMenuItemView *)itemView
  218. {
  219. // NOTE: Reset properties related to the size of the button before colling sizeThatFits: of item view,
  220. // or the size of the view will change from the second time.
  221. itemView.button.contentEdgeInsets = UIEdgeInsetsZero;
  222. itemView.image = nil;
  223. itemView.highlightedImage = nil;
  224. }
  225. - (void)groupItemViewsWithMaximumWidth:(CGFloat)maximumWidth
  226. {
  227. NSMutableArray *groupedItemViews = [NSMutableArray array];
  228. CGFloat pagenatorWidth = [QBPopupMenuPagenatorView pagenatorWidth];
  229. // Create new array
  230. NSMutableArray *itemViews = [NSMutableArray array];
  231. CGFloat width = 0;
  232. if (self.actualArrorDirection == QBPopupMenuArrowDirectionLeft || self.actualArrorDirection == QBPopupMenuArrowDirectionRight) {
  233. width += self.arrowSize;
  234. }
  235. for (QBPopupMenuItemView *itemView in self.itemViews) {
  236. // Clear state
  237. [self resetItemViewState:itemView];
  238. CGSize itemViewSize = [itemView sizeThatFits:CGSizeZero];
  239. if (itemViews.count > 0 && width + itemViewSize.width + pagenatorWidth > maximumWidth) {
  240. [groupedItemViews addObject:[itemViews copy]];
  241. // Create new array
  242. itemViews = [NSMutableArray array];
  243. width = pagenatorWidth;
  244. if (self.actualArrorDirection == QBPopupMenuArrowDirectionLeft || self.actualArrorDirection == QBPopupMenuArrowDirectionRight) {
  245. width += self.arrowSize;
  246. }
  247. }
  248. [itemViews addObject:itemView];
  249. width += itemViewSize.width;
  250. }
  251. if (itemViews.count > 0) {
  252. [groupedItemViews addObject:[itemViews copy]];
  253. }
  254. self.groupedItemViews = groupedItemViews;
  255. }
  256. - (void)updateVisibleItemViewsWithPage:(NSUInteger)page
  257. {
  258. // Remove all visible item views
  259. for (UIView *view in self.visibleItemViews) {
  260. [view removeFromSuperview];
  261. }
  262. // Add item views
  263. NSMutableArray *visibleItemViews = [NSMutableArray array];
  264. NSUInteger numberOfPages = self.groupedItemViews.count;
  265. if (numberOfPages > 1 && page != 0) {
  266. /* Custom */
  267. QBPopupMenuPagenatorView *leftPagenatorView = [[[self class] pagenatorViewClass] leftPagenatorViewWithTarget:self action:@selector(showPreviousPage) accessibilityLabel:_previousPageAccessibilityLabel];
  268. [self addSubview:leftPagenatorView];
  269. [visibleItemViews addObject:leftPagenatorView];
  270. }
  271. NSArray *itemViews = [self.groupedItemViews objectAtIndex:page];
  272. for (QBPopupMenuItemView *itemView in itemViews) {
  273. [self addSubview:itemView];
  274. [visibleItemViews addObject:itemView];
  275. }
  276. if (numberOfPages > 1 && page != numberOfPages - 1) {
  277. /* Custom */
  278. QBPopupMenuPagenatorView *rightPagenatorView = [[[self class] pagenatorViewClass] rightPagenatorViewWithTarget:self action:@selector(showNextPage) accessibilityLabel:_nextPageAccessibilityLabel];
  279. [self addSubview:rightPagenatorView];
  280. [visibleItemViews addObject:rightPagenatorView];
  281. }
  282. self.visibleItemViews = visibleItemViews;
  283. }
  284. - (void)layoutVisibleItemViews
  285. {
  286. CGFloat height = self.height;
  287. if (self.actualArrorDirection == QBPopupMenuArrowDirectionDown || self.actualArrorDirection == QBPopupMenuArrowDirectionUp) {
  288. height += self.arrowSize;
  289. }
  290. CGFloat offset = 0;
  291. for (NSInteger i = 0; i < self.visibleItemViews.count; i++) {
  292. QBPopupMenuItemView *itemView = [self.visibleItemViews objectAtIndex:i];
  293. // Clear state
  294. [self resetItemViewState:itemView];
  295. // Set item view insets
  296. if (i == 0 && self.actualArrorDirection == QBPopupMenuArrowDirectionLeft) {
  297. itemView.button.contentEdgeInsets = UIEdgeInsetsMake(0, self.arrowSize, 0, 0);
  298. }
  299. else if (i == self.visibleItemViews.count - 1 && self.actualArrorDirection == QBPopupMenuArrowDirectionRight) {
  300. itemView.button.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, self.arrowSize);
  301. }
  302. else if (self.actualArrorDirection == QBPopupMenuArrowDirectionDown) {
  303. itemView.button.contentEdgeInsets = UIEdgeInsetsMake(0, 0, self.arrowSize, 0);
  304. }
  305. else if (self.actualArrorDirection == QBPopupMenuArrowDirectionUp) {
  306. itemView.button.contentEdgeInsets = UIEdgeInsetsMake(self.arrowSize, 0, 0, 0);
  307. }
  308. // Set item view frame
  309. CGSize size = [itemView sizeThatFits:CGSizeZero];
  310. CGFloat width = size.width;
  311. if ((i == 0 && self.actualArrorDirection == QBPopupMenuArrowDirectionLeft) ||
  312. (i == self.visibleItemViews.count - 1 && self.actualArrorDirection == QBPopupMenuArrowDirectionRight)) {
  313. width += self.arrowSize;
  314. }
  315. itemView.frame = CGRectMake(offset, 0, width, height);
  316. offset += width;
  317. }
  318. }
  319. - (void)updatePopupMenuFrameAndArrowPosition
  320. {
  321. // Calculate popup frame
  322. CGRect popupMenuFrame = CGRectZero;
  323. CGPoint arrowPoint = CGPointZero;
  324. UIView *itemView = [self.visibleItemViews lastObject];
  325. CGFloat width = itemView.frame.origin.x + itemView.frame.size.width;
  326. CGFloat height = itemView.frame.origin.y + itemView.frame.size.height;
  327. switch (self.actualArrorDirection) {
  328. case QBPopupMenuArrowDirectionDown:
  329. {
  330. popupMenuFrame = CGRectMake(self.targetRect.origin.x + (self.targetRect.size.width - width) / 2.0,
  331. self.targetRect.origin.y - (height + self.margin),
  332. width,
  333. height);
  334. if (popupMenuFrame.origin.x + popupMenuFrame.size.width > self.view.frame.size.width - self.popupMenuInsets.right) {
  335. popupMenuFrame.origin.x = self.view.frame.size.width - self.popupMenuInsets.right - popupMenuFrame.size.width;
  336. }
  337. if (popupMenuFrame.origin.x < self.popupMenuInsets.left) {
  338. popupMenuFrame.origin.x = self.popupMenuInsets.left;
  339. }
  340. CGFloat centerOfTargetRect = self.targetRect.origin.x + self.targetRect.size.width / 2.0;
  341. arrowPoint = CGPointMake(MAX(self.cornerRadius, MIN(popupMenuFrame.size.width - self.cornerRadius, centerOfTargetRect - popupMenuFrame.origin.x)),
  342. popupMenuFrame.size.height);
  343. }
  344. break;
  345. case QBPopupMenuArrowDirectionUp:
  346. {
  347. popupMenuFrame = CGRectMake(self.targetRect.origin.x + (self.targetRect.size.width - width) / 2.0,
  348. self.targetRect.origin.y + (self.targetRect.size.height + self.margin),
  349. width,
  350. height);
  351. if (popupMenuFrame.origin.x + popupMenuFrame.size.width > self.view.frame.size.width - self.popupMenuInsets.right) {
  352. popupMenuFrame.origin.x = self.view.frame.size.width - self.popupMenuInsets.right - popupMenuFrame.size.width;
  353. }
  354. if (popupMenuFrame.origin.x < self.popupMenuInsets.left) {
  355. popupMenuFrame.origin.x = self.popupMenuInsets.left;
  356. }
  357. CGFloat centerOfTargetRect = self.targetRect.origin.x + self.targetRect.size.width / 2.0;
  358. arrowPoint = CGPointMake(MAX(self.cornerRadius, MIN(popupMenuFrame.size.width - self.cornerRadius, centerOfTargetRect - popupMenuFrame.origin.x)),
  359. 0);
  360. }
  361. break;
  362. case QBPopupMenuArrowDirectionLeft:
  363. {
  364. popupMenuFrame = CGRectMake(self.targetRect.origin.x + (self.targetRect.size.width + self.margin),
  365. self.targetRect.origin.y + (self.targetRect.size.height - height) / 2.0,
  366. width,
  367. height);
  368. if (popupMenuFrame.origin.y + popupMenuFrame.size.height > self.view.frame.size.height - self.popupMenuInsets.bottom) {
  369. popupMenuFrame.origin.y = self.view.frame.size.height - self.popupMenuInsets.bottom - popupMenuFrame.size.height;
  370. }
  371. if (popupMenuFrame.origin.y < self.popupMenuInsets.top) {
  372. popupMenuFrame.origin.y = self.popupMenuInsets.top;
  373. }
  374. CGFloat centerOfTargetRect = self.targetRect.origin.y + self.targetRect.size.height / 2.0;
  375. arrowPoint = CGPointMake(0,
  376. MAX(self.cornerRadius, MIN(popupMenuFrame.size.height - self.cornerRadius, centerOfTargetRect - popupMenuFrame.origin.y)));
  377. }
  378. break;
  379. case QBPopupMenuArrowDirectionRight:
  380. {
  381. popupMenuFrame = CGRectMake(self.targetRect.origin.x - (width + self.margin),
  382. self.targetRect.origin.y + (self.targetRect.size.height - height) / 2.0,
  383. width,
  384. height);
  385. if (popupMenuFrame.origin.y + popupMenuFrame.size.height > self.view.frame.size.height - self.popupMenuInsets.bottom) {
  386. popupMenuFrame.origin.y = self.view.frame.size.height - self.popupMenuInsets.bottom - popupMenuFrame.size.height;
  387. }
  388. if (popupMenuFrame.origin.y < self.popupMenuInsets.top) {
  389. popupMenuFrame.origin.y = self.popupMenuInsets.top;
  390. }
  391. CGFloat centerOfTargetRect = self.targetRect.origin.y + self.targetRect.size.height / 2.0;
  392. arrowPoint = CGPointMake(popupMenuFrame.size.width,
  393. MAX(self.cornerRadius, MIN(popupMenuFrame.size.height - self.cornerRadius, centerOfTargetRect - popupMenuFrame.origin.y)));
  394. }
  395. break;
  396. default:
  397. break;
  398. }
  399. // Round coordinates
  400. popupMenuFrame = CGRectMake(round(popupMenuFrame.origin.x),
  401. round(popupMenuFrame.origin.y),
  402. round(popupMenuFrame.size.width),
  403. round(popupMenuFrame.size.height));
  404. arrowPoint = CGPointMake(round(arrowPoint.x),
  405. round(arrowPoint.y));
  406. self.frame = popupMenuFrame;
  407. self.arrowPoint = arrowPoint;
  408. }
  409. - (void)updatePopupMenuImage
  410. {
  411. UIImage *popupMenuImage = [self popupMenuImageWithHighlighted:NO];
  412. UIImage *popupMenuHighlightedImage = [self popupMenuImageWithHighlighted:YES];
  413. for (NSInteger i = 0; i < self.visibleItemViews.count; i++) {
  414. QBPopupMenuItemView *itemView = [self.visibleItemViews objectAtIndex:i];
  415. UIImage *image = [self cropImageFromImage:popupMenuImage inRect:itemView.frame];
  416. UIImage *highlightedImage = [self cropImageFromImage:popupMenuHighlightedImage inRect:itemView.frame];
  417. itemView.image = image;
  418. itemView.highlightedImage = highlightedImage;
  419. }
  420. }
  421. #pragma mark - Creating Popup Menu Image
  422. - (UIImage *)cropImageFromImage:(UIImage *)image inRect:(CGRect)rect
  423. {
  424. CGFloat scale = [[UIScreen mainScreen] scale];
  425. CGRect scaledRect = CGRectMake(rect.origin.x * scale, rect.origin.y * scale, rect.size.width * scale, rect.size.height * scale);
  426. CGImageRef imageRef = CGImageCreateWithImageInRect(image.CGImage, scaledRect);
  427. UIImage *croppedImage = [UIImage imageWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
  428. CGImageRelease(imageRef);
  429. return croppedImage;
  430. }
  431. - (UIImage *)popupMenuImageWithHighlighted:(BOOL)highlighted
  432. {
  433. UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
  434. // Draw body
  435. CGFloat y = (self.actualArrorDirection == QBPopupMenuArrowDirectionUp) ? self.arrowSize : 0;
  436. CGFloat height = self.height;
  437. for (NSInteger i = 0; i < self.visibleItemViews.count; i++) {
  438. QBPopupMenuItemView *itemView = [self.visibleItemViews objectAtIndex:i];
  439. CGRect frame = itemView.frame;
  440. if (i == 0) {
  441. if (self.visibleItemViews.count == 1) {
  442. CGRect headRect;
  443. CGRect bodyRect;
  444. CGRect tailRect;
  445. if (self.actualArrorDirection == QBPopupMenuArrowDirectionLeft) {
  446. headRect = CGRectMake(self.arrowSize, y, self.cornerRadius, height);
  447. bodyRect = CGRectMake(self.arrowSize + self.cornerRadius, y, frame.size.width - (self.arrowSize + self.cornerRadius * 2.0), height);
  448. tailRect = CGRectMake(frame.size.width - self.cornerRadius, y, self.cornerRadius, height);
  449. }
  450. else if (self.actualArrorDirection == QBPopupMenuArrowDirectionRight) {
  451. headRect = CGRectMake(0, y, self.cornerRadius, height);
  452. bodyRect = CGRectMake(self.cornerRadius, y, frame.size.width - (self.arrowSize + self.cornerRadius * 2.0), height);
  453. tailRect = CGRectMake(frame.size.width - (self.arrowSize + self.cornerRadius), y, self.cornerRadius, height);
  454. }
  455. else {
  456. headRect = CGRectMake(0, y, self.cornerRadius, height);
  457. bodyRect = CGRectMake(self.cornerRadius, y, frame.size.width - self.cornerRadius * 2.0, height);
  458. tailRect = CGRectMake(frame.size.width - self.cornerRadius, y, self.cornerRadius, height);
  459. }
  460. // Draw head
  461. [self drawHeadInRect:headRect cornerRadius:self.cornerRadius highlighted:highlighted];
  462. // Draw body
  463. [self drawBodyInRect:bodyRect firstItem:YES lastItem:YES highlighted:highlighted];
  464. // Draw tail
  465. [self drawTailInRect:tailRect cornerRadius:self.cornerRadius highlighted:highlighted];
  466. } else {
  467. CGRect headRect;
  468. CGRect bodyRect;
  469. if (self.actualArrorDirection == QBPopupMenuArrowDirectionLeft) {
  470. headRect = CGRectMake(self.arrowSize, y, self.cornerRadius, height);
  471. bodyRect = CGRectMake(self.arrowSize + self.cornerRadius, y, frame.size.width - (self.arrowSize + self.cornerRadius), height);
  472. } else {
  473. headRect = CGRectMake(0, y, self.cornerRadius, height);
  474. bodyRect = CGRectMake(self.cornerRadius, y, frame.size.width - self.cornerRadius, height);
  475. }
  476. // Draw head
  477. [self drawHeadInRect:headRect cornerRadius:self.cornerRadius highlighted:highlighted];
  478. // Draw body
  479. [self drawBodyInRect:bodyRect firstItem:YES lastItem:NO highlighted:highlighted];
  480. }
  481. }
  482. else if (i == self.visibleItemViews.count - 1) {
  483. CGRect bodyRect;
  484. CGRect tailRect;
  485. if (self.actualArrorDirection == QBPopupMenuArrowDirectionRight) {
  486. bodyRect = CGRectMake(frame.origin.x, y, frame.size.width - (self.cornerRadius + self.arrowSize), height);
  487. tailRect = CGRectMake(frame.origin.x + frame.size.width - (self.cornerRadius + self.arrowSize), y, self.cornerRadius, height);
  488. } else {
  489. bodyRect = CGRectMake(frame.origin.x, y, frame.size.width - self.cornerRadius, height);
  490. tailRect = CGRectMake(frame.origin.x + frame.size.width - self.cornerRadius, y, self.cornerRadius, height);
  491. }
  492. // Draw body
  493. [self drawBodyInRect:bodyRect firstItem:NO lastItem:YES highlighted:highlighted];
  494. // Draw tail
  495. [self drawTailInRect:tailRect cornerRadius:self.cornerRadius highlighted:highlighted];
  496. }
  497. else {
  498. // Draw body
  499. CGRect bodyRect = CGRectMake(frame.origin.x, y, frame.size.width, height);
  500. [self drawBodyInRect:bodyRect firstItem:NO lastItem:NO highlighted:highlighted];
  501. }
  502. }
  503. // Draw arrow
  504. [self drawArrowAtPoint:self.arrowPoint arrowSize:self.arrowSize arrowDirection:self.actualArrorDirection highlighted:highlighted];
  505. // Create image from buffer
  506. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  507. UIGraphicsEndImageContext();
  508. return image;
  509. }
  510. #pragma mark - Creating Paths
  511. - (CGMutablePathRef)arrowPathInRect:(CGRect)rect direction:(QBPopupMenuArrowDirection)direction
  512. {
  513. // Create arrow path
  514. CGMutablePathRef path = CGPathCreateMutable();
  515. switch (direction) {
  516. case QBPopupMenuArrowDirectionDown:
  517. {
  518. CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y);
  519. CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y);
  520. CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width / 2.0, rect.origin.y + rect.size.height);
  521. CGPathCloseSubpath(path);
  522. }
  523. break;
  524. case QBPopupMenuArrowDirectionUp:
  525. {
  526. CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height);
  527. CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);
  528. CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width / 2.0, rect.origin.y);
  529. CGPathCloseSubpath(path);
  530. }
  531. break;
  532. case QBPopupMenuArrowDirectionLeft:
  533. {
  534. CGPathMoveToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y);
  535. CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);
  536. CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height / 2.0);
  537. CGPathCloseSubpath(path);
  538. }
  539. break;
  540. case QBPopupMenuArrowDirectionRight:
  541. {
  542. CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y);
  543. CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height);
  544. CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height / 2.0);
  545. CGPathCloseSubpath(path);
  546. }
  547. break;
  548. default:
  549. break;
  550. }
  551. return path;
  552. }
  553. - (CGMutablePathRef)headPathInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius
  554. {
  555. CGMutablePathRef path = CGPathCreateMutable();
  556. CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y + cornerRadius);
  557. CGPathAddArcToPoint(path, NULL, rect.origin.x, rect.origin.y, rect.origin.x + cornerRadius, rect.origin.y, cornerRadius);
  558. CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y);
  559. CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);
  560. CGPathAddLineToPoint(path, NULL, rect.origin.x + cornerRadius, rect.origin.y + rect.size.height);
  561. CGPathAddArcToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height, rect.origin.x, rect.origin.y + rect.size.height - cornerRadius, cornerRadius);
  562. CGPathCloseSubpath(path);
  563. return path;
  564. }
  565. - (CGMutablePathRef)tailPathInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius
  566. {
  567. CGMutablePathRef path = CGPathCreateMutable();
  568. CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y);
  569. CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width - cornerRadius, rect.origin.y);
  570. CGPathAddArcToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y, rect.origin.x + rect.size.width, rect.origin.y + cornerRadius, cornerRadius);
  571. CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height - cornerRadius);
  572. CGPathAddArcToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height, rect.origin.x + rect.size.width - cornerRadius, rect.origin.y + rect.size.height, cornerRadius);
  573. CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height);
  574. CGPathCloseSubpath(path);
  575. return path;
  576. }
  577. - (CGMutablePathRef)bodyPathInRect:(CGRect)rect
  578. {
  579. CGMutablePathRef path = CGPathCreateMutable();
  580. CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y);
  581. CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y);
  582. CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);
  583. CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height);
  584. CGPathCloseSubpath(path);
  585. return path;
  586. }
  587. - (CGMutablePathRef)leftSeparatorPathInRect:(CGRect)rect
  588. {
  589. CGMutablePathRef path = CGPathCreateMutable();
  590. CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y);
  591. CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.size.height);
  592. CGPathAddLineToPoint(path, NULL, rect.origin.x - rect.size.width, rect.size.height);
  593. CGPathAddLineToPoint(path, NULL, rect.origin.x - rect.size.width, rect.origin.y);
  594. CGPathCloseSubpath(path);
  595. return path;
  596. }
  597. - (CGMutablePathRef)rightSeparatorPathInRect:(CGRect)rect
  598. {
  599. CGMutablePathRef path = CGPathCreateMutable();
  600. CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y);
  601. CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.size.height);
  602. CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.size.height);
  603. CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y);
  604. CGPathCloseSubpath(path);
  605. return path;
  606. }
  607. #pragma mark - Drawing
  608. - (void)drawArrowAtPoint:(CGPoint)point arrowSize:(CGFloat)arrowSize arrowDirection:(QBPopupMenuArrowDirection)arrowDirection highlighted:(BOOL)highlighted
  609. {
  610. CGRect arrowRect = CGRectZero;
  611. switch (arrowDirection) {
  612. case QBPopupMenuArrowDirectionDown:
  613. {
  614. arrowRect = CGRectMake(point.x - arrowSize + 1.0,
  615. point.y - arrowSize,
  616. arrowSize * 2.0 - 1.0,
  617. arrowSize);
  618. arrowRect.origin.x = MIN(MAX(arrowRect.origin.x, self.cornerRadius),
  619. self.frame.size.width - self.cornerRadius - arrowRect.size.width);
  620. }
  621. break;
  622. case QBPopupMenuArrowDirectionUp:
  623. {
  624. arrowRect = CGRectMake(point.x - arrowSize + 1.0,
  625. 0,
  626. arrowSize * 2.0 - 1.0,
  627. arrowSize);
  628. arrowRect.origin.x = MIN(MAX(arrowRect.origin.x, self.cornerRadius),
  629. self.frame.size.width - self.cornerRadius - arrowRect.size.width);
  630. }
  631. break;
  632. case QBPopupMenuArrowDirectionLeft:
  633. {
  634. arrowRect = CGRectMake(0,
  635. point.y - arrowSize + 1.0,
  636. arrowSize,
  637. arrowSize * 2.0 - 1.0);
  638. }
  639. break;
  640. case QBPopupMenuArrowDirectionRight:
  641. {
  642. arrowRect = CGRectMake(point.x - arrowSize,
  643. point.y - arrowSize + 1.0,
  644. arrowSize,
  645. arrowSize * 2.0 - 1.0);
  646. }
  647. break;
  648. default:
  649. break;
  650. }
  651. [self drawArrowInRect:arrowRect direction:arrowDirection highlighted:highlighted];
  652. }
  653. - (void)drawArrowInRect:(CGRect)rect direction:(QBPopupMenuArrowDirection)direction highlighted:(BOOL)highlighted
  654. {
  655. CGContextRef context = UIGraphicsGetCurrentContext();
  656. // Arrow
  657. CGContextSaveGState(context); {
  658. CGMutablePathRef path = [self arrowPathInRect:rect direction:direction];
  659. CGContextAddPath(context, path);
  660. UIColor *color = highlighted ? self.highlightedColor : self.color;
  661. CGContextSetFillColorWithColor(context, [color CGColor]);
  662. CGContextFillPath(context);
  663. CGPathRelease(path);
  664. } CGContextRestoreGState(context);
  665. }
  666. - (void)drawHeadInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius highlighted:(BOOL)highlighted
  667. {
  668. CGContextRef context = UIGraphicsGetCurrentContext();
  669. // Head
  670. CGContextSaveGState(context); {
  671. CGMutablePathRef path = [self headPathInRect:rect cornerRadius:cornerRadius];
  672. CGContextAddPath(context, path);
  673. UIColor *color = highlighted ? self.highlightedColor : self.color;
  674. CGContextSetFillColorWithColor(context, [color CGColor]);
  675. CGContextFillPath(context);
  676. CGPathRelease(path);
  677. } CGContextRestoreGState(context);
  678. }
  679. - (void)drawTailInRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius highlighted:(BOOL)highlighted
  680. {
  681. CGContextRef context = UIGraphicsGetCurrentContext();
  682. // Tail
  683. CGContextSaveGState(context); {
  684. CGMutablePathRef path = [self tailPathInRect:rect cornerRadius:cornerRadius];
  685. CGContextAddPath(context, path);
  686. UIColor *color = highlighted ? self.highlightedColor : self.color;
  687. CGContextSetFillColorWithColor(context, [color CGColor]);
  688. CGContextFillPath(context);
  689. CGPathRelease(path);
  690. } CGContextRestoreGState(context);
  691. }
  692. - (void)drawBodyInRect:(CGRect)rect firstItem:(BOOL)firstItem lastItem:(BOOL)lastItem highlighted:(BOOL)highlighted
  693. {
  694. CGContextRef context = UIGraphicsGetCurrentContext();
  695. // Body
  696. CGContextSaveGState(context); {
  697. CGMutablePathRef path = [self bodyPathInRect:rect];
  698. CGContextAddPath(context, path);
  699. UIColor *color = highlighted ? self.highlightedColor : self.color;
  700. CGContextSetFillColorWithColor(context, [color CGColor]);
  701. CGContextFillPath(context);
  702. CGPathRelease(path);
  703. } CGContextRestoreGState(context);
  704. // Draw separator
  705. if (!firstItem) {
  706. [self drawLeftSeparatorInRect:CGRectMake(rect.origin.x, rect.origin.y + 1, 0.5, rect.size.height - 2)];
  707. }
  708. if (!lastItem) {
  709. [self drawRightSeparatorInRect:CGRectMake(rect.origin.x + rect.size.width - 1, rect.origin.y + 1, 0.5, rect.size.height - 2)];
  710. }
  711. }
  712. - (void)drawRightSeparatorInRect:(CGRect)rect
  713. {
  714. CGContextRef context = UIGraphicsGetCurrentContext();
  715. // Separator
  716. CGContextSaveGState(context); {
  717. CGMutablePathRef path = [self rightSeparatorPathInRect:rect];
  718. CGContextAddPath(context, path);
  719. CGContextSetFillColorWithColor(context, [[Colors popupMenuSeparator] CGColor]);
  720. CGContextFillPath(context);
  721. CGPathRelease(path);
  722. } CGContextRestoreGState(context);
  723. }
  724. - (void)drawLeftSeparatorInRect:(CGRect)rect
  725. {
  726. CGContextRef context = UIGraphicsGetCurrentContext();
  727. // Separator
  728. CGContextSaveGState(context); {
  729. CGMutablePathRef path = [self leftSeparatorPathInRect:rect];
  730. CGContextAddPath(context, path);
  731. CGContextSetFillColorWithColor(context, [[Colors popupMenuSeparator] CGColor]);
  732. CGContextFillPath(context);
  733. CGPathRelease(path);
  734. } CGContextRestoreGState(context);
  735. }
  736. @end