PageRenderTime 44ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 0ms

/Dependencies/JGActionSheet/JGActionSheet.m

https://gitlab.com/Mr.Tomato/VideoEffects
Objective C | 1150 lines | 857 code | 293 blank | 0 comment | 128 complexity | 3abb07a3ee8e7db77739ab4c5fa59b51 MD5 | raw file
  1. #import <QuartzCore/QuartzCore.h>
  2. #import "JGActionSheet.h"
  3. #if !__has_feature(objc_arc)
  4. #error "JGActionSheet requires ARC!"
  5. #endif
  6. #pragma mark - Defines
  7. #ifndef kCFCoreFoundationVersionNumber_iOS_7_0
  8. #define kCFCoreFoundationVersionNumber_iOS_7_0 838.00
  9. #endif
  10. #ifndef __IPHONE_8_0
  11. #define __IPHONE_8_0 80000
  12. #endif
  13. #ifndef kBaseSDKiOS8
  14. #define kBaseSDKiOS8 (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0)
  15. #endif
  16. #ifndef iOS8
  17. #define iOS8 ([UIVisualEffectView class] != Nil)
  18. #endif
  19. #ifndef iOS7
  20. #define iOS7 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0)
  21. #endif
  22. #ifndef rgba
  23. #define rgba(r, g, b, a) [UIColor colorWithRed:r/255.0f green:g/255.0f blue:b/255.0f alpha:a]
  24. #endif
  25. #ifndef rgb
  26. #define rgb(r, g, b) rgba(r, g, b, 1.0f)
  27. #endif
  28. #ifndef iPad
  29. #define iPad (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
  30. #endif
  31. #define kHostsCornerRadius 3.0f
  32. #define kSpacing 5.0f
  33. #define kArrowBaseWidth 20.0f
  34. #define kArrowHeight 10.0f
  35. #define kShadowRadius 4.0f
  36. #define kShadowOpacity 0.2f
  37. #define kFixedWidth 320.0f
  38. #define kFixedWidthContinuous 300.0f
  39. #define kAnimationDurationForSectionCount(count) MAX(0.22f, MIN(count*0.12f, 0.45f))
  40. #pragma mark - Helpers
  41. @interface JGButton : UIButton
  42. @property (nonatomic, assign) NSUInteger row;
  43. @end
  44. @implementation JGButton
  45. @end
  46. NS_INLINE UIBezierPath *trianglePath(CGRect rect, JGActionSheetArrowDirection arrowDirection, BOOL closePath)
  47. {
  48. UIBezierPath *path = [UIBezierPath bezierPath];
  49. if (arrowDirection == JGActionSheetArrowDirectionBottom)
  50. {
  51. [path moveToPoint:CGPointZero];
  52. [path addLineToPoint:(CGPoint){CGRectGetWidth(rect)/2.0f, CGRectGetHeight(rect)}];
  53. [path addLineToPoint:(CGPoint){CGRectGetWidth(rect), 0.0f}];
  54. }
  55. else if (arrowDirection == JGActionSheetArrowDirectionLeft)
  56. {
  57. [path moveToPoint:(CGPoint){CGRectGetWidth(rect), 0.0f}];
  58. [path addLineToPoint:(CGPoint){0.0f, CGRectGetHeight(rect)/2.0f}];
  59. [path addLineToPoint:(CGPoint){CGRectGetWidth(rect), CGRectGetHeight(rect)}];
  60. }
  61. else if (arrowDirection == JGActionSheetArrowDirectionRight)
  62. {
  63. [path moveToPoint:CGPointZero];
  64. [path addLineToPoint:(CGPoint){CGRectGetWidth(rect), CGRectGetHeight(rect)/2.0f}];
  65. [path addLineToPoint:(CGPoint){0.0f, CGRectGetHeight(rect)}];
  66. }
  67. else if (arrowDirection == JGActionSheetArrowDirectionTop)
  68. {
  69. [path moveToPoint:(CGPoint){0.0f, CGRectGetHeight(rect)}];
  70. [path addLineToPoint:(CGPoint){CGRectGetWidth(rect)/2.0f, 0.0f}];
  71. [path addLineToPoint:(CGPoint){CGRectGetWidth(rect), CGRectGetHeight(rect)}];
  72. }
  73. if (closePath)
  74. {
  75. [path closePath];
  76. }
  77. return path;
  78. }
  79. static BOOL disableCustomEasing = NO;
  80. @interface JGActionSheetLayer : CAShapeLayer
  81. @end
  82. @implementation JGActionSheetLayer
  83. - (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key
  84. {
  85. if (!disableCustomEasing && [anim isKindOfClass:[CABasicAnimation class]])
  86. {
  87. CAMediaTimingFunction *func = [CAMediaTimingFunction functionWithControlPoints:0.215f: 0.61f: 0.355f: 1.0f];
  88. anim.timingFunction = func;
  89. }
  90. [super addAnimation:anim forKey:key];
  91. }
  92. @end
  93. @interface JGActionSheetTriangle : UIView
  94. - (void)setFrame:(CGRect)frame arrowDirection:(JGActionSheetArrowDirection)direction;
  95. @end
  96. @implementation JGActionSheetTriangle
  97. - (void)setFrame:(CGRect)frame arrowDirection:(JGActionSheetArrowDirection)direction
  98. {
  99. self.frame = frame;
  100. [((CAShapeLayer *)self.layer) setPath:trianglePath(frame, direction, YES).CGPath];
  101. self.layer.shadowPath = trianglePath(frame, direction, NO).CGPath;
  102. BOOL leftOrRight = (direction == JGActionSheetArrowDirectionLeft || direction == JGActionSheetArrowDirectionRight);
  103. CGRect pathRect = (CGRect){CGPointZero, {CGRectGetWidth(frame)+(leftOrRight ? kShadowRadius+1.0f : 2.0f*(kShadowRadius+1.0f)), CGRectGetHeight(frame)+(leftOrRight ? 2.0f*(kShadowRadius+1.0f) : kShadowRadius+1.0f)}};
  104. if (direction == JGActionSheetArrowDirectionTop)
  105. {
  106. pathRect.origin.y -= kShadowRadius+1.0f;
  107. }
  108. else if (direction == JGActionSheetArrowDirectionLeft)
  109. {
  110. pathRect.origin.x -= kShadowRadius+1.0f;
  111. }
  112. UIBezierPath *path = [UIBezierPath bezierPathWithRect:pathRect];
  113. CAShapeLayer *mask = [CAShapeLayer layer];
  114. mask.path = path.CGPath;
  115. mask.fillColor = [UIColor blackColor].CGColor;
  116. self.layer.mask = mask;
  117. self.layer.shadowColor = [UIColor blackColor].CGColor;
  118. self.layer.shadowOffset = CGSizeZero;
  119. self.layer.shadowRadius = kShadowRadius;
  120. self.layer.shadowOpacity = kShadowOpacity;
  121. self.layer.contentsScale = [UIScreen mainScreen].scale;
  122. ((CAShapeLayer *)self.layer).fillColor = [UIColor whiteColor].CGColor;
  123. }
  124. + (Class)layerClass
  125. {
  126. return [JGActionSheetLayer class];
  127. }
  128. @end
  129. @interface JGActionSheetView : UIView
  130. @end
  131. @implementation JGActionSheetView
  132. + (Class)layerClass
  133. {
  134. return [JGActionSheetLayer class];
  135. }
  136. @end
  137. #pragma mark - JGActionSheetSection
  138. @interface JGActionSheetSection ()
  139. @property (nonatomic, copy) void (^buttonPressedBlock)(NSIndexPath *indexPath);
  140. - (void)setUpForContinuous:(BOOL)continuous;
  141. @end
  142. @implementation JGActionSheetSection
  143. #pragma mark Initializers
  144. + (instancetype)sectionWithTitle:(NSString *)title message:(NSString *)message buttonTitles:(NSArray *)buttonTitles buttonStyle:(JGActionSheetButtonStyle)buttonStyle
  145. {
  146. return [[self alloc] initWithTitle:title message:message buttonTitles:buttonTitles buttonStyle:buttonStyle];
  147. }
  148. - (instancetype)initWithTitle:(NSString *)title message:(NSString *)message buttonTitles:(NSArray *)buttonTitles buttonStyle:(JGActionSheetButtonStyle)buttonStyle
  149. {
  150. self = [super init];
  151. if (self)
  152. {
  153. if (title)
  154. {
  155. UILabel *titleLabel = [[UILabel alloc] init];
  156. titleLabel.backgroundColor = [UIColor clearColor];
  157. titleLabel.textAlignment = NSTextAlignmentCenter;
  158. titleLabel.font = [UIFont boldSystemFontOfSize:14.0f];
  159. titleLabel.textColor = [UIColor blackColor];
  160. titleLabel.numberOfLines = 1;
  161. titleLabel.text = title;
  162. _titleLabel = titleLabel;
  163. [self addSubview:_titleLabel];
  164. }
  165. if (message)
  166. {
  167. UILabel *messageLabel = [[UILabel alloc] init];
  168. messageLabel.backgroundColor = [UIColor clearColor];
  169. messageLabel.textAlignment = NSTextAlignmentCenter;
  170. messageLabel.font = [UIFont systemFontOfSize:12.0f];
  171. messageLabel.textColor = [UIColor blackColor];
  172. messageLabel.numberOfLines = 0;
  173. messageLabel.text = message;
  174. _messageLabel = messageLabel;
  175. [self addSubview:_messageLabel];
  176. }
  177. if (buttonTitles.count)
  178. {
  179. NSMutableArray *buttons = [NSMutableArray arrayWithCapacity:buttonTitles.count];
  180. NSInteger index = 0;
  181. for (NSString *str in buttonTitles)
  182. {
  183. JGButton *b = [self makeButtonWithTitle:str style:buttonStyle];
  184. b.row = (NSUInteger)index;
  185. [self addSubview:b];
  186. [buttons addObject:b];
  187. index++;
  188. }
  189. _buttons = buttons.copy;
  190. }
  191. }
  192. return self;
  193. }
  194. + (instancetype)sectionWithTitle:(NSString *)title message:(NSString *)message contentView:(UIView *)contentView
  195. {
  196. return [[self alloc] initWithTitle:title message:message contentView:contentView];
  197. }
  198. - (instancetype)initWithTitle:(NSString *)title message:(NSString *)message contentView:(UIView *)contentView
  199. {
  200. self = [super init];
  201. if (self)
  202. {
  203. if (title)
  204. {
  205. UILabel *titleLabel = [[UILabel alloc] init];
  206. titleLabel.backgroundColor = [UIColor clearColor];
  207. titleLabel.textAlignment = NSTextAlignmentCenter;
  208. titleLabel.font = [UIFont boldSystemFontOfSize:14.0f];
  209. titleLabel.textColor = [UIColor blackColor];
  210. titleLabel.numberOfLines = 1;
  211. titleLabel.text = title;
  212. _titleLabel = titleLabel;
  213. [self addSubview:_titleLabel];
  214. }
  215. if (message)
  216. {
  217. UILabel *messageLabel = [[UILabel alloc] init];
  218. messageLabel.backgroundColor = [UIColor clearColor];
  219. messageLabel.textAlignment = NSTextAlignmentCenter;
  220. messageLabel.font = [UIFont systemFontOfSize:12.0f];
  221. messageLabel.textColor = [UIColor blackColor];
  222. messageLabel.numberOfLines = 0;
  223. messageLabel.text = message;
  224. _messageLabel = messageLabel;
  225. [self addSubview:_messageLabel];
  226. }
  227. _contentView = contentView;
  228. [self addSubview:self.contentView];
  229. }
  230. return self;
  231. }
  232. #pragma mark UI
  233. - (void)setUpForContinuous:(BOOL)continuous
  234. {
  235. if (continuous)
  236. {
  237. self.backgroundColor = [UIColor clearColor];
  238. self.layer.cornerRadius = 0.0f;
  239. self.layer.shadowOpacity = 0.0f;
  240. }
  241. else
  242. {
  243. self.backgroundColor = [UIColor whiteColor];
  244. self.layer.cornerRadius = kHostsCornerRadius;
  245. self.layer.shadowColor = [UIColor blackColor].CGColor;
  246. self.layer.shadowOffset = CGSizeZero;
  247. self.layer.shadowRadius = kShadowRadius;
  248. self.layer.shadowOpacity = kShadowOpacity;
  249. }
  250. }
  251. - (void)setButtonStyle:(JGActionSheetButtonStyle)buttonStyle forButtonAtIndex:(NSUInteger)index
  252. {
  253. if (index < self.buttons.count) {
  254. UIButton *button = self.buttons[index];
  255. [self setButtonStyle:buttonStyle forButton:button];
  256. }
  257. else {
  258. NSLog(@"ERROR: Index out of bounds");
  259. return;
  260. }
  261. }
  262. - (UIImage *)pixelImageWithColor:(UIColor *)color
  263. {
  264. UIGraphicsBeginImageContextWithOptions((CGSize){1.0f, 1.0f}, YES, 0.0f);
  265. [color setFill];
  266. [[UIBezierPath bezierPathWithRect:(CGRect){CGPointZero, {1.0f, 1.0f}}] fill];
  267. UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
  268. UIGraphicsEndImageContext();
  269. return [img resizableImageWithCapInsets:UIEdgeInsetsZero];
  270. }
  271. - (void)setButtonStyle:(JGActionSheetButtonStyle)buttonStyle forButton:(UIButton *)button
  272. {
  273. UIColor *backgroundColor, *borderColor, *titleColor = nil;
  274. UIFont *font = nil;
  275. if (buttonStyle == JGActionSheetButtonStyleDefault)
  276. {
  277. font = [UIFont systemFontOfSize:15.0f];
  278. titleColor = [UIColor blackColor];
  279. backgroundColor = [UIColor colorWithWhite:0.95f alpha:1.0f];
  280. borderColor = [UIColor colorWithWhite:0.9f alpha:1.0f];
  281. }
  282. else if (buttonStyle == JGActionSheetButtonStyleCancel)
  283. {
  284. font = [UIFont boldSystemFontOfSize:15.0f];
  285. titleColor = [UIColor blackColor];
  286. backgroundColor = [UIColor colorWithWhite:0.95f alpha:1.0f];
  287. borderColor = [UIColor colorWithWhite:0.9f alpha:1.0f];
  288. }
  289. else if (buttonStyle == JGActionSheetButtonStyleRed)
  290. {
  291. font = [UIFont systemFontOfSize:15.0f];
  292. titleColor = [UIColor whiteColor];
  293. backgroundColor = rgb(231.0f, 76.0f, 60.0f);
  294. borderColor = rgb(192.0f, 57.0f, 43.0f);
  295. }
  296. else if (buttonStyle == JGActionSheetButtonStyleGreen)
  297. {
  298. font = [UIFont systemFontOfSize:15.0f];
  299. titleColor = [UIColor whiteColor];
  300. backgroundColor = rgb(46.0f, 204.0f, 113.0f);
  301. borderColor = rgb(39.0f, 174.0f, 96.0f);
  302. }
  303. else if (buttonStyle == JGActionSheetButtonStyleBlue)
  304. {
  305. font = [UIFont systemFontOfSize:15.0f];
  306. titleColor = [UIColor whiteColor];
  307. backgroundColor = rgb(52.0f, 152.0f, 219.0f);
  308. borderColor = rgb(41.0f, 128.0f, 185.0f);
  309. }
  310. [button setTitleColor:titleColor forState:UIControlStateNormal];
  311. button.titleLabel.font = font;
  312. [button setBackgroundImage:[self pixelImageWithColor:backgroundColor] forState:UIControlStateNormal];
  313. [button setBackgroundImage:[self pixelImageWithColor:borderColor] forState:UIControlStateHighlighted];
  314. button.layer.borderColor = borderColor.CGColor;
  315. }
  316. - (JGButton *)makeButtonWithTitle:(NSString *)title style:(JGActionSheetButtonStyle)style
  317. {
  318. JGButton *b = [[JGButton alloc] init];
  319. b.layer.cornerRadius = 2.0f;
  320. b.layer.masksToBounds = YES;
  321. b.layer.borderWidth = 1.0f;
  322. [b setTitle:title forState:UIControlStateNormal];
  323. [b addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
  324. [self setButtonStyle:style forButton:b];
  325. return b;
  326. }
  327. - (void)buttonPressed:(JGButton *)button
  328. {
  329. if (self.buttonPressedBlock)
  330. {
  331. self.buttonPressedBlock([NSIndexPath indexPathForRow:(NSInteger)button.row inSection:self.tag]);
  332. }
  333. }
  334. - (CGRect)layoutForWidth:(CGFloat)width
  335. {
  336. CGFloat buttonHeight = 40.0f;
  337. CGFloat spacing = kSpacing;
  338. CGFloat height = 0.0f;
  339. if (self.titleLabel)
  340. {
  341. height += spacing;
  342. [self.titleLabel sizeToFit];
  343. height += CGRectGetHeight(self.titleLabel.frame);
  344. self.titleLabel.frame = (CGRect){{spacing, spacing}, {width-spacing*2.0f, CGRectGetHeight(self.titleLabel.frame)}};
  345. }
  346. if (self.messageLabel)
  347. {
  348. height += spacing;
  349. CGSize maxLabelSize = {width-spacing*2.0f, width};
  350. CGFloat messageLabelHeight = 0.0f;
  351. if (iOS7) {
  352. NSDictionary *attributes = @{NSFontAttributeName : self.messageLabel.font};
  353. messageLabelHeight = CGRectGetHeight([self.messageLabel.text boundingRectWithSize:maxLabelSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil]);
  354. }
  355. else {
  356. #pragma clang diagnostic push
  357. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  358. messageLabelHeight = [self.messageLabel.text sizeWithFont:self.messageLabel.font constrainedToSize:maxLabelSize lineBreakMode:self.messageLabel.lineBreakMode].height;
  359. #pragma clang diagnostic pop
  360. }
  361. self.messageLabel.frame = (CGRect){{spacing, height}, {width-spacing*2.0f, messageLabelHeight}};
  362. height += messageLabelHeight;
  363. }
  364. for (UIButton *button in self.buttons)
  365. {
  366. height += spacing;
  367. button.frame = (CGRect){{spacing, height}, {width-spacing*2.0f, buttonHeight}};
  368. height += buttonHeight;
  369. }
  370. if (self.contentView)
  371. {
  372. height += spacing;
  373. self.contentView.frame = (CGRect){{spacing, height}, {width-spacing*2.0f, self.contentView.frame.size.height}};
  374. height += CGRectGetHeight(self.contentView.frame);
  375. }
  376. height += spacing;
  377. self.frame = (CGRect){CGPointZero, {width, height}};
  378. return self.frame;
  379. }
  380. @end
  381. #pragma mark - JGActionSheet
  382. @interface JGActionSheet () <UIGestureRecognizerDelegate>
  383. {
  384. UIScrollView *_scrollView;
  385. JGActionSheetTriangle *_arrowView;
  386. JGActionSheetView *_scrollViewHost;
  387. CGRect _finalContentFrame;
  388. UIColor *_realBGColor;
  389. BOOL _anchoredAtPoint;
  390. CGPoint _anchorPoint;
  391. JGActionSheetArrowDirection _anchoredArrowDirection;
  392. }
  393. @end
  394. @implementation JGActionSheet
  395. @dynamic visible;
  396. #pragma mark Initializers
  397. + (instancetype)actionSheetWithSections:(NSArray *)sections
  398. {
  399. return [[self alloc] initWithSections:sections];
  400. }
  401. - (instancetype)initWithSections:(NSArray *)sections
  402. {
  403. NSAssert(sections.count > 0, @"Must at least provide 1 section");
  404. self = [super init];
  405. if (self)
  406. {
  407. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)];
  408. tap.delegate = self;
  409. [self addGestureRecognizer:tap];
  410. _scrollViewHost = [[JGActionSheetView alloc] init];
  411. _scrollViewHost.backgroundColor = [UIColor clearColor];
  412. _scrollView = [[UIScrollView alloc] init];
  413. _scrollView.backgroundColor = [UIColor clearColor];
  414. _scrollView.showsHorizontalScrollIndicator = NO;
  415. _scrollView.showsVerticalScrollIndicator = NO;
  416. [_scrollViewHost addSubview:_scrollView];
  417. [self addSubview:_scrollViewHost];
  418. self.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.3f];
  419. _sections = sections;
  420. NSInteger index = 0;
  421. __weak __typeof(self) weakSelf = self;
  422. void (^pressedBlock)(NSIndexPath *) = ^(NSIndexPath *indexPath)
  423. {
  424. [weakSelf buttonPressed:indexPath];
  425. };
  426. for (JGActionSheetSection *section in self.sections)
  427. {
  428. section.tag = index;
  429. [_scrollView addSubview:section];
  430. [section setButtonPressedBlock:pressedBlock];
  431. index++;
  432. }
  433. }
  434. return self;
  435. }
  436. #pragma mark Overrides
  437. + (Class)layerClass
  438. {
  439. return [JGActionSheetLayer class];
  440. }
  441. - (void)dealloc
  442. {
  443. [[NSNotificationCenter defaultCenter] removeObserver:self];
  444. }
  445. - (void)setBackgroundColor:(UIColor *)backgroundColor
  446. {
  447. [super setBackgroundColor:backgroundColor];
  448. _realBGColor = backgroundColor;
  449. }
  450. #pragma mark Callbacks
  451. - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
  452. {
  453. if ([self hitTest:[gestureRecognizer locationInView:self] withEvent:nil] == self && self.outsidePressBlock)
  454. {
  455. return YES;
  456. }
  457. return NO;
  458. }
  459. - (void)tapped:(UITapGestureRecognizer *)gesture
  460. {
  461. if ([self hitTest:[gesture locationInView:self] withEvent:nil] == self && self.outsidePressBlock)
  462. {
  463. self.outsidePressBlock(self);
  464. }
  465. }
  466. - (void)orientationChanged
  467. {
  468. if (_targetView && !CGRectEqualToRect(self.bounds, _targetView.bounds))
  469. {
  470. disableCustomEasing = YES;
  471. [UIView animateWithDuration:(iPad ? 0.4 : 0.3) delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut animations:^{
  472. if (_anchoredAtPoint)
  473. {
  474. [self moveToPoint:_anchorPoint arrowDirection:_anchoredArrowDirection animated:NO];
  475. }
  476. else
  477. {
  478. [self layoutSheetInitial:NO];
  479. }
  480. } completion:^(BOOL finished)
  481. {
  482. disableCustomEasing = NO;
  483. }];
  484. }
  485. }
  486. - (void)buttonPressed:(NSIndexPath *)indexPath
  487. {
  488. if (self.buttonPressedBlock)
  489. {
  490. self.buttonPressedBlock(self, indexPath);
  491. }
  492. if ([self.delegate respondsToSelector:@selector(actionSheet:pressedButtonAtIndexPath:)])
  493. {
  494. [self.delegate actionSheet:self pressedButtonAtIndexPath:indexPath];
  495. }
  496. }
  497. #pragma mark Layout
  498. - (void)layoutSheetForFrame:(CGRect)frame fitToRect:(BOOL)fitToRect initialSetUp:(BOOL)initial continuous:(BOOL)continuous
  499. {
  500. if (continuous)
  501. {
  502. frame.size.width = kFixedWidthContinuous;
  503. }
  504. CGFloat spacing = 2.0f*kSpacing;
  505. CGFloat width = CGRectGetWidth(frame);
  506. if (!continuous)
  507. {
  508. width -= 2.0f*spacing;
  509. }
  510. CGFloat height = (continuous ? 0.0f : spacing);
  511. for (JGActionSheetSection *section in self.sections)
  512. {
  513. if (initial)
  514. {
  515. [section setUpForContinuous:continuous];
  516. }
  517. CGRect f = [section layoutForWidth:width];
  518. f.origin.y = height;
  519. if (!continuous) {
  520. f.origin.x = spacing;
  521. }
  522. section.frame = f;
  523. height += CGRectGetHeight(f)+spacing;
  524. }
  525. if (continuous)
  526. {
  527. height -= spacing;
  528. }
  529. _scrollView.contentSize = (CGSize){CGRectGetWidth(frame), height};
  530. if (!fitToRect && !continuous)
  531. {
  532. frame.size.height = CGRectGetHeight(_targetView.bounds)-CGRectGetMinY(frame);
  533. }
  534. if (height > CGRectGetHeight(frame))
  535. {
  536. _scrollViewHost.frame = frame;
  537. }
  538. else
  539. {
  540. CGFloat finalY = 0.0f;
  541. if (fitToRect)
  542. {
  543. finalY = CGRectGetMaxY(frame)-height;
  544. }
  545. else if (continuous)
  546. {
  547. finalY = CGRectGetMinY(frame);
  548. }
  549. else
  550. {
  551. finalY = CGRectGetMinY(frame)+(CGRectGetHeight(frame)-height)/2.0f;
  552. }
  553. _scrollViewHost.frame = (CGRect){{CGRectGetMinX(frame), finalY}, _scrollView.contentSize};
  554. }
  555. _finalContentFrame = _scrollViewHost.frame;
  556. _scrollView.frame = _scrollViewHost.bounds;
  557. [_scrollView scrollRectToVisible:(CGRect){{0.0f, _scrollView.contentSize.height-1.0f}, {1.0f, 1.0f}} animated:NO];
  558. }
  559. - (void)layoutForVisible:(BOOL)visible
  560. {
  561. UIView *viewToModify = _scrollViewHost;
  562. if (visible)
  563. {
  564. self.backgroundColor = _realBGColor;
  565. if (iPad)
  566. {
  567. viewToModify.alpha = 1.0f;
  568. _arrowView.alpha = 1.0f;
  569. }
  570. else
  571. {
  572. viewToModify.frame = _finalContentFrame;
  573. }
  574. }
  575. else
  576. {
  577. super.backgroundColor = [UIColor clearColor];
  578. if (iPad)
  579. {
  580. viewToModify.alpha = 0.0f;
  581. _arrowView.alpha = 0.0f;
  582. }
  583. else
  584. {
  585. viewToModify.frame = (CGRect){{viewToModify.frame.origin.x, CGRectGetHeight(_targetView.bounds)}, _scrollView.contentSize};
  586. }
  587. }
  588. }
  589. #pragma mark Showing
  590. - (void)showInView:(UIView *)view animated:(BOOL)animated
  591. {
  592. NSAssert(!self.visible, @"Action Sheet is already visisble!");
  593. [[UIApplication sharedApplication] beginIgnoringInteractionEvents];
  594. _targetView = view;
  595. [self layoutSheetInitial:YES];
  596. if ([self.delegate respondsToSelector:@selector(actionSheetWillPresent:)])
  597. {
  598. [self.delegate actionSheetWillPresent:self];
  599. }
  600. void (^completion)(void) = ^{
  601. [[UIApplication sharedApplication] endIgnoringInteractionEvents];
  602. if ([self.delegate respondsToSelector:@selector(actionSheetDidPresent:)])
  603. {
  604. [self.delegate actionSheetDidPresent:self];
  605. }
  606. };
  607. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
  608. [self layoutForVisible:!animated];
  609. [_targetView addSubview:self];
  610. if (!animated)
  611. {
  612. completion();
  613. }
  614. else
  615. {
  616. CGFloat duration = (iPad ? 0.3f : kAnimationDurationForSectionCount(self.sections.count));
  617. [UIView animateWithDuration:duration animations:^{
  618. [self layoutForVisible:YES];
  619. } completion:^(BOOL finished)
  620. {
  621. completion();
  622. }];
  623. }
  624. }
  625. - (void)layoutSheetInitial:(BOOL)initial
  626. {
  627. self.frame = _targetView.bounds;
  628. _scrollViewHost.layer.cornerRadius = 0.0f;
  629. _scrollViewHost.layer.shadowOpacity = 0.0f;
  630. _scrollViewHost.backgroundColor = [UIColor clearColor];
  631. CGRect frame = self.frame;
  632. if (iPad)
  633. {
  634. CGFloat fixedWidth = kFixedWidth;
  635. frame.origin.x = (CGRectGetWidth(frame)-fixedWidth)/2.0f;
  636. frame.size.width = fixedWidth;
  637. }
  638. frame = UIEdgeInsetsInsetRect(frame, self.insets);
  639. [self layoutSheetForFrame:frame fitToRect:!iPad initialSetUp:initial continuous:NO];
  640. }
  641. #pragma mark Showing From Point
  642. - (void)showFromPoint:(CGPoint)point inView:(UIView *)view arrowDirection:(JGActionSheetArrowDirection)arrowDirection animated:(BOOL)animated
  643. {
  644. NSAssert(!self.visible, @"Action Sheet is already visisble!");
  645. if (!iPad)
  646. {
  647. return [self showInView:view animated:animated];
  648. }
  649. [[UIApplication sharedApplication] beginIgnoringInteractionEvents];
  650. _targetView = view;
  651. [self moveToPoint:point arrowDirection:arrowDirection animated:NO];
  652. if ([self.delegate respondsToSelector:@selector(actionSheetWillPresent:)])
  653. {
  654. [self.delegate actionSheetWillPresent:self];
  655. }
  656. void (^completion)(void) = ^{
  657. [[UIApplication sharedApplication] endIgnoringInteractionEvents];
  658. if ([self.delegate respondsToSelector:@selector(actionSheetDidPresent:)])
  659. {
  660. [self.delegate actionSheetDidPresent:self];
  661. }
  662. };
  663. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
  664. [self layoutForVisible:!animated];
  665. [_targetView addSubview:self];
  666. if (!animated)
  667. {
  668. completion();
  669. }
  670. else
  671. {
  672. CGFloat duration = 0.3f;
  673. [UIView animateWithDuration:duration animations:^{
  674. [self layoutForVisible:YES];
  675. } completion:^(BOOL finished)
  676. {
  677. completion();
  678. }];
  679. }
  680. }
  681. - (void)moveToPoint:(CGPoint)point arrowDirection:(JGActionSheetArrowDirection)arrowDirection animated:(BOOL)animated
  682. {
  683. if (!iPad)
  684. {
  685. return;
  686. }
  687. [[UIApplication sharedApplication] beginIgnoringInteractionEvents];
  688. disableCustomEasing = YES;
  689. NSAssert(self.visible, @"Action Sheet requires to be visible in order to move the anchor point!");
  690. void (^changes)(void) = ^{
  691. self.frame = _targetView.bounds;
  692. CGRect finalFrame = CGRectZero;
  693. CGFloat arrowHeight = kArrowHeight;
  694. CGFloat spacing = kSpacing;
  695. if (arrowDirection == JGActionSheetArrowDirectionRight)
  696. {
  697. finalFrame.size.width = point.x-arrowHeight;
  698. finalFrame.size.height = CGRectGetHeight(_targetView.bounds);
  699. }
  700. else if (arrowDirection == JGActionSheetArrowDirectionLeft)
  701. {
  702. finalFrame.size.width = CGRectGetWidth(_targetView.bounds)-point.x-arrowHeight;
  703. finalFrame.size.height = CGRectGetHeight(_targetView.bounds);
  704. finalFrame.origin.x = point.x+arrowHeight;
  705. }
  706. else if (arrowDirection == JGActionSheetArrowDirectionTop)
  707. {
  708. finalFrame.size.width = CGRectGetWidth(_targetView.bounds);
  709. finalFrame.size.height = CGRectGetHeight(_targetView.bounds)-point.y-arrowHeight;
  710. finalFrame.origin.y = point.y+arrowHeight;
  711. }
  712. else if (arrowDirection == JGActionSheetArrowDirectionBottom)
  713. {
  714. finalFrame.size.width = CGRectGetWidth(_targetView.bounds);
  715. finalFrame.size.height = point.y-arrowHeight;
  716. }
  717. else
  718. {
  719. @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Invalid arrow direction" userInfo:nil];
  720. }
  721. finalFrame.origin.x += spacing;
  722. finalFrame.origin.y += spacing;
  723. finalFrame.size.height -= spacing*2.0f;
  724. finalFrame.size.width -= spacing*2.0f;
  725. finalFrame = UIEdgeInsetsInsetRect(finalFrame, self.insets);
  726. _scrollViewHost.backgroundColor = [UIColor whiteColor];
  727. _scrollViewHost.layer.cornerRadius = kHostsCornerRadius;
  728. _scrollViewHost.layer.shadowColor = [UIColor blackColor].CGColor;
  729. _scrollViewHost.layer.shadowOffset = CGSizeZero;
  730. _scrollViewHost.layer.shadowRadius = kShadowRadius;
  731. _scrollViewHost.layer.shadowOpacity = kShadowOpacity;
  732. [self layoutSheetForFrame:finalFrame fitToRect:NO initialSetUp:YES continuous:YES];
  733. [self anchorSheetAtPoint:point withArrowDirection:arrowDirection availableFrame:finalFrame];
  734. };
  735. void (^completion)(void) = ^{
  736. [[UIApplication sharedApplication] endIgnoringInteractionEvents];
  737. };
  738. if (animated)
  739. {
  740. [UIView animateWithDuration:0.3 animations:changes completion:^(BOOL finished)
  741. {
  742. completion();
  743. }];
  744. }
  745. else
  746. {
  747. changes();
  748. completion();
  749. }
  750. disableCustomEasing = NO;
  751. }
  752. - (void)anchorSheetAtPoint:(CGPoint)point withArrowDirection:(JGActionSheetArrowDirection)arrowDirection availableFrame:(CGRect)frame
  753. {
  754. _anchoredAtPoint = YES;
  755. _anchorPoint = point;
  756. _anchoredArrowDirection = arrowDirection;
  757. CGRect finalFrame = _scrollViewHost.frame;
  758. CGFloat arrowHeight = kArrowHeight;
  759. CGFloat arrrowBaseWidth = kArrowBaseWidth;
  760. BOOL leftOrRight = (arrowDirection == JGActionSheetArrowDirectionLeft || arrowDirection == JGActionSheetArrowDirectionRight);
  761. CGRect arrowFrame = (CGRect){CGPointZero, {(leftOrRight ? arrowHeight : arrrowBaseWidth), (leftOrRight ? arrrowBaseWidth : arrowHeight)}};
  762. if (arrowDirection == JGActionSheetArrowDirectionRight)
  763. {
  764. arrowFrame.origin.x = point.x-arrowHeight;
  765. arrowFrame.origin.y = point.y-arrrowBaseWidth/2.0f;
  766. finalFrame.origin.x = point.x-CGRectGetWidth(finalFrame)-arrowHeight;
  767. }
  768. else if (arrowDirection == JGActionSheetArrowDirectionLeft)
  769. {
  770. arrowFrame.origin.x = point.x;
  771. arrowFrame.origin.y = point.y-arrrowBaseWidth/2.0f;
  772. finalFrame.origin.x = point.x+arrowHeight;
  773. }
  774. else if (arrowDirection == JGActionSheetArrowDirectionTop)
  775. {
  776. arrowFrame.origin.x = point.x-arrrowBaseWidth/2.0f;
  777. arrowFrame.origin.y = point.y;
  778. finalFrame.origin.y = point.y+arrowHeight;
  779. }
  780. else if (arrowDirection == JGActionSheetArrowDirectionBottom)
  781. {
  782. arrowFrame.origin.x = point.x-arrrowBaseWidth/2.0f;
  783. arrowFrame.origin.y = point.y-arrowHeight;
  784. finalFrame.origin.y = point.y-CGRectGetHeight(finalFrame)-arrowHeight;
  785. }
  786. if (leftOrRight)
  787. {
  788. finalFrame.origin.y = MIN(MAX(CGRectGetMaxY(frame)-CGRectGetHeight(finalFrame), CGRectGetMaxY(arrowFrame)-CGRectGetHeight(finalFrame)+kHostsCornerRadius), MIN(MAX(CGRectGetMinY(frame), point.y-CGRectGetHeight(finalFrame)/2.0f), CGRectGetMinY(arrowFrame)-kHostsCornerRadius));
  789. }
  790. else
  791. {
  792. finalFrame.origin.x = MIN(MAX(MIN(CGRectGetMinX(frame), CGRectGetMinX(arrowFrame)-kHostsCornerRadius), point.x-CGRectGetWidth(finalFrame)/2.0f), MAX(CGRectGetMaxX(frame)-CGRectGetWidth(finalFrame), CGRectGetMaxX(arrowFrame)+kHostsCornerRadius-CGRectGetWidth(finalFrame)));
  793. }
  794. if (!_arrowView)
  795. {
  796. _arrowView = [[JGActionSheetTriangle alloc] init];
  797. [self addSubview:_arrowView];
  798. }
  799. [_arrowView setFrame:arrowFrame arrowDirection:arrowDirection];
  800. if (!CGRectContainsRect(_targetView.bounds, finalFrame) || !CGRectContainsRect(_targetView.bounds, arrowFrame))
  801. {
  802. NSLog(@"WARNING: Action sheet does not fit within view bounds! Select a different arrow direction or provide a different anchor point!");
  803. }
  804. _scrollViewHost.frame = finalFrame;
  805. }
  806. #pragma mark Dismissal
  807. - (void)dismissAnimated:(BOOL)animated
  808. {
  809. NSAssert(self.visible, @"Action Sheet requires to be visible in order to dismiss!");
  810. [[UIApplication sharedApplication] beginIgnoringInteractionEvents];
  811. void (^completion)(void) = ^{
  812. [_arrowView removeFromSuperview];
  813. _arrowView = nil;
  814. _targetView = nil;
  815. [self removeFromSuperview];
  816. _anchoredAtPoint = NO;
  817. _anchoredArrowDirection = 0;
  818. _anchorPoint = CGPointZero;
  819. [[NSNotificationCenter defaultCenter] removeObserver:self];
  820. [[UIApplication sharedApplication] endIgnoringInteractionEvents];
  821. if ([self.delegate respondsToSelector:@selector(actionSheetDidDismiss:)])
  822. {
  823. [self.delegate actionSheetDidDismiss:self];
  824. }
  825. };
  826. if ([self.delegate respondsToSelector:@selector(actionSheetWillDismiss:)])
  827. {
  828. [self.delegate actionSheetWillDismiss:self];
  829. }
  830. if (animated)
  831. {
  832. CGFloat duration = 0.0f;
  833. if (iPad) {
  834. duration = 0.3f;
  835. }
  836. else
  837. {
  838. duration = kAnimationDurationForSectionCount(self.sections.count);
  839. }
  840. [UIView animateWithDuration:duration animations:^{
  841. [self layoutForVisible:NO];
  842. } completion:^(BOOL finished) {
  843. completion();
  844. }];
  845. }
  846. else
  847. {
  848. [self layoutForVisible:NO];
  849. completion();
  850. }
  851. }
  852. #pragma mark Visibility
  853. - (BOOL)isVisible
  854. {
  855. return (_targetView != nil);
  856. }
  857. @end