QBPopupMenu.m 33 KB

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