暂无描述

JGActionSheet.m 32KB

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