IGLDropDownMenu.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. //
  2. // IGLDropDownMenu.m
  3. // IGLDropDownMenuDemo
  4. //
  5. // Created by Galvin Li on 8/30/14.
  6. // Copyright (c) 2014 Galvin Li. All rights reserved.
  7. //
  8. #import "IGLDropDownMenu.h"
  9. #ifdef NSFoundationVersionNumber_iOS_6_1
  10. #define IOS7_OR_GREATER (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1)
  11. #else
  12. #define IOS7_OR_GREATER NO
  13. #endif
  14. @interface IGLDropDownMenu ()
  15. @property (nonatomic, strong) IGLDropDownItem *menuButton;
  16. @property (nonatomic, assign) CGFloat offsetX;
  17. @property (nonatomic, assign) NSInteger selectedIndex;
  18. @property (nonatomic, assign) CGRect oldFrame;
  19. @property (nonatomic, assign) CGRect originalFrame;
  20. @property (nonatomic, copy) void (^selectedItemChangeBlock)(NSInteger selectedIndex);
  21. @end
  22. @implementation IGLDropDownMenu
  23. - (instancetype)initWithFrame:(CGRect)frame
  24. {
  25. self = [super initWithFrame:frame];
  26. if (self) {
  27. [self commonInit];
  28. }
  29. return self;
  30. }
  31. - (instancetype)initWithCoder:(NSCoder *)aDecoder
  32. {
  33. self = [super initWithCoder:aDecoder];
  34. if (self) {
  35. [self commonInit];
  36. }
  37. return self;
  38. }
  39. - (void)commonInit
  40. {
  41. self.originalFrame = self.frame;
  42. [self resetParams];
  43. self.menuButton = [[IGLDropDownItem alloc] init];
  44. }
  45. - (void)setFrame:(CGRect)frame
  46. {
  47. super.frame = frame;
  48. self.originalFrame = frame;
  49. }
  50. - (void)setExpanding:(BOOL)expanding
  51. {
  52. _expanding = expanding;
  53. [self updateView];
  54. }
  55. - (void)setDropDownItems:(NSArray *)dropDownItems
  56. {
  57. _dropDownItems = [[NSArray alloc] initWithArray:dropDownItems copyItems:YES];
  58. }
  59. - (CGFloat)alphaOnFold
  60. {
  61. if (_alphaOnFold != -1) {
  62. return _alphaOnFold;
  63. }
  64. if ([self isSlidingInType]) {
  65. return 0.0;
  66. }
  67. return 1.0;
  68. }
  69. - (void)selectItemAtIndex:(NSUInteger)index
  70. {
  71. if (index < self.dropDownItems.count) {
  72. [self selectChangeToItem:self.dropDownItems[index]];
  73. }
  74. }
  75. - (void)resetParams
  76. {
  77. super.frame = self.oldFrame;
  78. self.offsetX = 0;
  79. self.animationDuration = 0.3;
  80. self.animationOption = UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState;
  81. self.itemAnimationDelay = 0.0;
  82. self.direction = IGLDropDownMenuDirectionDown;
  83. self.rotate = IGLDropDownMenuRotateNone;
  84. self.type = IGLDropDownMenuTypeNormal;
  85. self.slidingInOffset = -1;
  86. self.gutterY = 0;
  87. self.alphaOnFold = -1;
  88. self.flipWhenToggleView = NO;
  89. _expanding = NO;
  90. self.useSpringAnimation = YES;
  91. self.selectedIndex = -1;
  92. }
  93. - (void)reloadView
  94. {
  95. if (self.isExpanding) {
  96. super.frame = self.oldFrame;
  97. } else {
  98. self.oldFrame = self.frame;
  99. }
  100. self.itemSize = self.frame.size;
  101. // clear all subviews
  102. for (UIView *view in [self subviews]) {
  103. [view removeFromSuperview];
  104. }
  105. if (self.rotate == IGLDropDownMenuRotateLeft) {
  106. self.offsetX = self.dropDownItems.count * self.dropDownItems.count * self.itemSize.height / 28;
  107. }
  108. [super setFrame:CGRectMake(self.originalFrame.origin.x - self.offsetX, self.originalFrame.origin.y, self.frame.size.width + self.gutterY, self.frame.size.height)];
  109. self.menuButton.iconImage = self.menuIconImage;
  110. self.menuButton.text = self.menuText;
  111. self.menuButton.paddingLeft = self.paddingLeft;
  112. [self.menuButton setFrame:CGRectMake(self.offsetX + 0, 0, self.itemSize.width, self.itemSize.height)];
  113. switch (self.direction) {
  114. case IGLDropDownMenuDirectionDown:
  115. self.menuButton.layer.anchorPoint = CGPointMake(0.5, 0);
  116. break;
  117. case IGLDropDownMenuDirectionUp:
  118. self.menuButton.layer.anchorPoint = CGPointMake(0.5, 1);
  119. break;
  120. default:
  121. break;
  122. }
  123. [self.menuButton addTarget:self action:@selector(toggleView) forControlEvents:UIControlEventTouchUpInside];
  124. [self addSubview:self.menuButton];
  125. for (int i = (int)self.dropDownItems.count - 1; i >= 0; i--) {
  126. IGLDropDownItem *item = self.dropDownItems[i];
  127. item.index = i;
  128. item.paddingLeft = self.paddingLeft;
  129. [item addTarget:self action:@selector(itemClicked:) forControlEvents:UIControlEventTouchUpInside];
  130. [self setUpFoldItem:item];
  131. [self insertSubview:item belowSubview:self.menuButton];
  132. }
  133. [self updateSelfFrame];
  134. }
  135. - (BOOL)isSlidingInType
  136. {
  137. switch (self.type) {
  138. case IGLDropDownMenuTypeNormal:
  139. case IGLDropDownMenuTypeStack:
  140. return NO;
  141. case IGLDropDownMenuTypeSlidingInBoth:
  142. case IGLDropDownMenuTypeSlidingInFromLeft:
  143. case IGLDropDownMenuTypeSlidingInFromRight:
  144. return YES;
  145. default:
  146. return NO;
  147. }
  148. }
  149. - (void)updateSelfFrame
  150. {
  151. CGFloat buttonHeight = CGRectGetHeight(self.menuButton.frame);
  152. CGFloat x = self.frame.origin.x;
  153. CGFloat y = self.originalFrame.origin.y;
  154. CGFloat height = buttonHeight;
  155. CGFloat width = CGRectGetWidth(self.menuButton.frame);
  156. if (self.isExpanding) {
  157. for (IGLDropDownItem *item in self.dropDownItems) {
  158. if (item.alpha > 0) {
  159. width = MAX(width, CGRectGetMaxX(item.frame));
  160. switch (self.direction) {
  161. case IGLDropDownMenuDirectionDown:
  162. height = MAX(height, CGRectGetMaxY(item.frame));
  163. break;
  164. case IGLDropDownMenuDirectionUp:
  165. height = MAX(height, -CGRectGetMinY(item.frame));
  166. break;
  167. default:
  168. break;
  169. }
  170. }
  171. }
  172. }
  173. [self.menuButton setFrame:CGRectMake(self.offsetX + 0, 0, self.itemSize.width, self.itemSize.height)];
  174. if (self.direction == IGLDropDownMenuDirectionUp) {
  175. if (self.isExpanding) {
  176. height += buttonHeight;
  177. }
  178. y -= height - buttonHeight;
  179. [self.menuButton setFrame:CGRectMake(self.offsetX + 0, height - buttonHeight, self.itemSize.width, self.itemSize.height)];
  180. for (IGLDropDownItem *item in self.dropDownItems) {
  181. if (self.isExpanding) {
  182. item.center = CGPointMake(item.center.x, height - buttonHeight + item.center.y);
  183. } else {
  184. item.center = CGPointMake(item.center.x, item.center.y - self.frame.size.height + buttonHeight);
  185. }
  186. }
  187. }
  188. [super setFrame:CGRectMake(x, y, width, height)];
  189. }
  190. - (void)toggleView
  191. {
  192. self.expanding = !self.isExpanding;
  193. }
  194. - (void)updateView
  195. {
  196. if (self.shouldFlipWhenToggleView) {
  197. [self flipMainButton];
  198. }
  199. if (self.isExpanding) {
  200. [self expandView];
  201. } else {
  202. [self foldView];
  203. }
  204. }
  205. - (void)expandView
  206. {
  207. // expand the view
  208. for (int i = (int)self.dropDownItems.count - 1; i >= 0; i--) {
  209. IGLDropDownItem *item = self.dropDownItems[i];
  210. CGFloat delay = 0;
  211. // item expand after the main button flip up
  212. if (self.shouldFlipWhenToggleView) {
  213. delay += 0.1;
  214. }
  215. if ([self isSlidingInType]) {
  216. // first item move first
  217. delay += self.itemAnimationDelay * i;
  218. } else {
  219. // last item move first
  220. delay += self.itemAnimationDelay * (self.dropDownItems.count - i - 1);
  221. }
  222. if (self.shouldUseSpringAnimation && IOS7_OR_GREATER) {
  223. #if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000)
  224. [UIView animateWithDuration:self.animationDuration * 2 delay:delay usingSpringWithDamping:0.5 initialSpringVelocity:2.0 options:self.animationOption animations:^{
  225. [self setUpExpandItem:item];
  226. } completion:^(BOOL finished) {
  227. if (i == 0) {
  228. [self updateSelfFrame];
  229. }
  230. }];
  231. #endif
  232. } else {
  233. [UIView animateWithDuration:self.animationDuration delay:delay options:self.animationOption animations:^{
  234. [self setUpExpandItem:item];
  235. } completion:^(BOOL finished) {
  236. if (i == 0) {
  237. [self updateSelfFrame];
  238. }
  239. }];
  240. }
  241. }
  242. }
  243. - (void)foldView
  244. {
  245. // fold the view
  246. for (int i = (int)self.dropDownItems.count - 1; i >= 0; i--) {
  247. IGLDropDownItem *item = self.dropDownItems[i];
  248. CGFloat delay = 0;
  249. // item fold after the main button flip up
  250. if (self.shouldFlipWhenToggleView) {
  251. delay += 0.1;
  252. }
  253. if ([self isSlidingInType]) {
  254. // last item move first
  255. delay += self.itemAnimationDelay * (self.dropDownItems.count - i - 1);
  256. } else {
  257. // first item move first
  258. delay += self.itemAnimationDelay * i;
  259. }
  260. if (self.shouldUseSpringAnimation && IOS7_OR_GREATER) {
  261. #if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000)
  262. [UIView animateWithDuration:self.animationDuration delay:delay usingSpringWithDamping:1.0 initialSpringVelocity:2.0 options:self.animationOption animations:^{
  263. [self setUpFoldItem:item];
  264. } completion:^(BOOL finished) {
  265. if (i == 0) {
  266. [self updateSelfFrame];
  267. }
  268. }];
  269. #endif
  270. } else {
  271. [UIView animateWithDuration:self.animationDuration delay:delay options:self.animationOption animations:^{
  272. [self setUpFoldItem:item];
  273. } completion:^(BOOL finished) {
  274. if (i == 0) {
  275. [self updateSelfFrame];
  276. }
  277. }];
  278. }
  279. }
  280. }
  281. - (void)setUpExpandItem:(IGLDropDownItem*)item
  282. {
  283. // set alpha for slidingIn
  284. item.alpha = 1.0;
  285. // set frame (MUST before rotation reset)
  286. [item setFrame:[self frameOnExpandForItemAtIndex:item.index]];
  287. // set rotate
  288. item.transform = [self transformOnExpandForItemAtIndex:item.index];
  289. }
  290. - (void)setUpFoldItem:(IGLDropDownItem*)item
  291. {
  292. // reset rotate
  293. item.transform = CGAffineTransformMakeRotation(0);
  294. // set frame (MUST after rotation reset)
  295. [item setFrame:[self frameOnFoldForItemAtIndex:item.index]];
  296. // set alpha for slidingIn
  297. item.alpha = self.alphaOnFold;
  298. }
  299. - (void)flipMainButton
  300. {
  301. CGFloat flipAxis = 1;
  302. if (self.direction == IGLDropDownMenuDirectionUp) {
  303. flipAxis = -1;
  304. }
  305. CABasicAnimation *topAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
  306. topAnimation.autoreverses = YES;
  307. topAnimation.duration = 0.2;
  308. topAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DPerspect(CATransform3DMakeRotation(M_PI_2/3*2, flipAxis, 0, 0), CGPointMake(0, 0), 400)];
  309. [self.menuButton.layer addAnimation:topAnimation forKey:nil];
  310. }
  311. - (CGRect)frameOnFoldForItemAtIndex:(NSInteger)index
  312. {
  313. CGFloat x = self.offsetX;
  314. CGFloat y = 0;
  315. CGFloat width = self.itemSize.width;
  316. CGFloat height = self.itemSize.height;
  317. NSInteger count = index >= 2 ? 2 : index;
  318. CGFloat slidingInOffect = self.slidingInOffset != -1 ? self.slidingInOffset : self.itemSize.width / 3;
  319. switch (self.type) {
  320. case IGLDropDownMenuTypeNormal:
  321. // just take the default value
  322. break;
  323. case IGLDropDownMenuTypeStack:
  324. x += count * 2;
  325. y = (count + 1) * 3;
  326. width -= count * 4;
  327. break;
  328. case IGLDropDownMenuTypeSlidingInBoth:
  329. if (index % 2 != 0) {
  330. slidingInOffect = -slidingInOffect;
  331. }
  332. x = slidingInOffect;
  333. y = (index + 1) * (height + self.gutterY);
  334. break;
  335. case IGLDropDownMenuTypeSlidingInFromLeft:
  336. x = -slidingInOffect;
  337. y = (index + 1) * (height + self.gutterY);
  338. break;
  339. case IGLDropDownMenuTypeSlidingInFromRight:
  340. x = slidingInOffect;
  341. y = (index + 1) * (height + self.gutterY);
  342. break;
  343. default:
  344. break;
  345. }
  346. if (self.direction == IGLDropDownMenuDirectionUp) {
  347. if (self.isExpanding) {
  348. y = -y;
  349. } else {
  350. CGFloat buttonHeight = CGRectGetHeight(self.menuButton.frame);
  351. y = self.frame.size.height - buttonHeight - y;
  352. NSLog(@"bHeight: %f, height: %f", buttonHeight, self.frame.size.height);
  353. }
  354. }
  355. return CGRectMake(x, y, width, height);
  356. }
  357. - (CGRect)frameOnExpandForItemAtIndex:(NSInteger)index
  358. {
  359. CGFloat x = 0;
  360. CGFloat y = (index + 1) * (self.itemSize.height + self.gutterY);
  361. CGFloat width = self.itemSize.width;
  362. CGFloat height = self.itemSize.height;
  363. switch (self.rotate) {
  364. case IGLDropDownMenuRotateNone:
  365. // just take the default value
  366. break;
  367. case IGLDropDownMenuRotateLeft:
  368. x = self.offsetX + -index * index * self.itemSize.height / 20.0;
  369. break;
  370. case IGLDropDownMenuRotateRight:
  371. x = index * index * self.itemSize.height / 20.0;
  372. break;
  373. case IGLDropDownMenuRotateRandom:
  374. x = floor([self random0to1] * 11 - 5);
  375. break;
  376. default:
  377. break;
  378. }
  379. if (self.direction == IGLDropDownMenuDirectionUp) {
  380. y = -y;
  381. }
  382. return CGRectMake(x, y, width, height);
  383. }
  384. - (CGAffineTransform)transformOnExpandForItemAtIndex:(NSInteger)index
  385. {
  386. CGFloat angle = 0;
  387. switch (self.rotate) {
  388. case IGLDropDownMenuRotateNone:
  389. // just take the default value
  390. break;
  391. case IGLDropDownMenuRotateLeft:
  392. angle = 5.0 * index / 180.0 * M_PI;
  393. break;
  394. case IGLDropDownMenuRotateRight:
  395. angle = -5.0 * index / 180.0 * M_PI;
  396. break;
  397. case IGLDropDownMenuRotateRandom:
  398. angle = floor([self random0to1] * 11 - 5) / 180.0 * M_PI;
  399. break;
  400. default:
  401. break;
  402. }
  403. if (self.direction == IGLDropDownMenuDirectionUp) {
  404. angle = -angle;
  405. }
  406. return CGAffineTransformMakeRotation(angle);
  407. }
  408. - (void)addSelectedItemChangeBlock:(void (^)(NSInteger))block
  409. {
  410. self.selectedItemChangeBlock = block;
  411. }
  412. - (void)selectChangeToItem:(IGLDropDownItem*)item
  413. {
  414. self.menuButton.iconImage = item.iconImage;
  415. self.object = item.object;
  416. self.menuButton.text = item.text;
  417. self.expanding = NO;
  418. self.selectedIndex = item.index;
  419. if (self.selectedItemChangeBlock) {
  420. self.selectedItemChangeBlock(self.selectedIndex);
  421. }
  422. if ([self.delegate respondsToSelector:@selector(dropDownMenu:selectedItemAtIndex:)]) {
  423. [self.delegate dropDownMenu:self selectedItemAtIndex:self.selectedIndex];
  424. }
  425. }
  426. #pragma mark - button action
  427. - (void)itemClicked:(IGLDropDownItem*)sender
  428. {
  429. if (![sender.text isEqualToString:@"没有更多了"]) {
  430. if (self.isExpanding) {
  431. [self selectChangeToItem:sender];
  432. }
  433. }
  434. }
  435. #pragma mark -
  436. - (float)random0to1
  437. {
  438. return [self randomFloatBetween:0.0 andLargerFloat:1.0];
  439. }
  440. - (float)randomFloatBetween:(float)num1 andLargerFloat:(float)num2
  441. {
  442. int startVal = num1*10000;
  443. int endVal = num2*10000;
  444. int randomValue = startVal +(arc4random()%(endVal - startVal));
  445. float a = randomValue;
  446. return(a /10000.0);
  447. }
  448. CATransform3D CATransform3DMakePerspective(CGPoint center, float disZ)
  449. {
  450. CATransform3D transToCenter = CATransform3DMakeTranslation(-center.x, -center.y, 0);
  451. CATransform3D transBack = CATransform3DMakeTranslation(center.x, center.y, 0);
  452. CATransform3D scale = CATransform3DIdentity;
  453. scale.m34 = -1.0f/disZ;
  454. return CATransform3DConcat(CATransform3DConcat(transToCenter, scale), transBack);
  455. }
  456. CATransform3D CATransform3DPerspect(CATransform3D t, CGPoint center, float disZ)
  457. {
  458. return CATransform3DConcat(t, CATransform3DMakePerspective(center, disZ));
  459. }
  460. @end