YCMenuView.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. //
  2. // YCMenuView.m
  3. // Demo
  4. //
  5. // Created by 蔡亚超 on 2018/8/28.
  6. // Copyright © 2018年 WellsCai. All rights reserved.
  7. // github地址:https://github.com/WellsYC/YCMenuView
  8. // 简书地址:https://www.jianshu.com/u/f853cbc68abb
  9. #define kScreenWidth [UIScreen mainScreen].bounds.size.width
  10. #define kScreenHeight [UIScreen mainScreen].bounds.size.height
  11. #define kMainWindow [UIApplication sharedApplication].keyWindow
  12. #define kArrowWidth 15
  13. #define kArrowHeight 10
  14. #define kDefaultMargin 10
  15. #define kAnimationTime 0.25
  16. #import "YCMenuView.h"
  17. @interface UIView (YCFrame)
  18. @property (nonatomic, assign) CGFloat x;
  19. @property (nonatomic, assign) CGFloat y;
  20. @property (nonatomic, assign) CGPoint origin;
  21. @property (nonatomic, assign) CGFloat centerX;
  22. @property (nonatomic, assign) CGFloat centerY;
  23. @property (nonatomic, assign) CGFloat width;
  24. @property (nonatomic, assign) CGFloat height;
  25. @property (nonatomic, assign) CGSize size;
  26. @end
  27. @implementation UIView (YCFrame)
  28. - (CGFloat)x
  29. {
  30. return self.frame.origin.x;
  31. }
  32. - (void)setX:(CGFloat)value
  33. {
  34. CGRect frame = self.frame;
  35. frame.origin.x = value;
  36. self.frame = frame;
  37. }
  38. - (CGFloat)y
  39. {
  40. return self.frame.origin.y;
  41. }
  42. - (void)setY:(CGFloat)value
  43. {
  44. CGRect frame = self.frame;
  45. frame.origin.y = value;
  46. self.frame = frame;
  47. }
  48. - (CGPoint)origin
  49. {
  50. return self.frame.origin;
  51. }
  52. - (void)setOrigin:(CGPoint)origin
  53. {
  54. CGRect frame = self.frame;
  55. frame.origin = origin;
  56. self.frame = frame;
  57. }
  58. - (CGFloat)centerX
  59. {
  60. return self.center.x;
  61. }
  62. - (void)setCenterX:(CGFloat)centerX
  63. {
  64. CGPoint center = self.center;
  65. center.x = centerX;
  66. self.center = center;
  67. }
  68. - (CGFloat)centerY
  69. {
  70. return self.center.y;
  71. }
  72. - (void)setCenterY:(CGFloat)centerY
  73. {
  74. CGPoint center = self.center;
  75. center.y = centerY;
  76. self.center = center;
  77. }
  78. - (CGFloat)width
  79. {
  80. return self.frame.size.width;
  81. }
  82. - (void)setWidth:(CGFloat)width
  83. {
  84. CGRect frame = self.frame;
  85. frame.size.width = width;
  86. self.frame = frame;
  87. }
  88. - (CGFloat)height
  89. {
  90. return self.frame.size.height;
  91. }
  92. - (void)setHeight:(CGFloat)height
  93. {
  94. CGRect frame = self.frame;
  95. frame.size.height = height;
  96. self.frame = frame;
  97. }
  98. - (CGSize)size
  99. {
  100. return self.frame.size;
  101. }
  102. - (void)setSize:(CGSize)size
  103. {
  104. CGRect frame = self.frame;
  105. frame.size = size;
  106. self.frame = frame;
  107. }
  108. @end
  109. @interface YCMenuCell : UITableViewCell
  110. @property (nonatomic,assign) BOOL isShowSeparator;
  111. @property (nonatomic,strong) UIColor * separatorColor;
  112. @end
  113. @implementation YCMenuCell
  114. - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
  115. if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
  116. _isShowSeparator = YES;
  117. _separatorColor = [UIColor lightGrayColor];
  118. }
  119. return self;
  120. }
  121. - (void)setSeparatorColor:(UIColor *)separatorColor{
  122. _separatorColor = separatorColor;
  123. [self setNeedsDisplay];
  124. }
  125. - (void)setIsShowSeparator:(BOOL)isShowSeparator{
  126. _isShowSeparator = isShowSeparator;
  127. [self setNeedsDisplay];
  128. }
  129. - (void)drawRect:(CGRect)rect{
  130. if (!_isShowSeparator)return;
  131. UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(0, rect.size.height - 0.5, rect.size.width, 0.5)];
  132. [_separatorColor setFill];
  133. [path fillWithBlendMode:kCGBlendModeNormal alpha:1.0f];
  134. [path closePath];
  135. }
  136. @end
  137. @interface YCMenuAction()
  138. @property (nonatomic) NSString *title;
  139. @property (nonatomic) UIImage *image;
  140. @property (copy, nonatomic)void (^handler)(YCMenuAction *);
  141. @end
  142. @implementation YCMenuAction
  143. + (instancetype)actionWithTitle:(NSString *)title image:(UIImage *)image handler:(void (^)(YCMenuAction *))handler{
  144. YCMenuAction *action = [[YCMenuAction alloc] initWithTitle:title image:image handler:handler];
  145. return action;
  146. }
  147. - (instancetype)initWithTitle:(NSString *)title image:(UIImage *)image handler:(void (^)(YCMenuAction *))handler{
  148. if (self = [super init]) {
  149. _title = title;
  150. _image = image;
  151. _handler = [handler copy];
  152. }
  153. return self;
  154. }
  155. @end
  156. @interface YCMenuView()<UITableViewDelegate,UITableViewDataSource>
  157. {
  158. CGPoint _refPoint;
  159. UIView *_refView;
  160. CGFloat _menuWidth;
  161. CGFloat _arrowPosition; // 三角底部的起始点x
  162. CGFloat _topMargin;
  163. BOOL _isReverse; // 是否反向
  164. BOOL _needReload; //是否需要刷新
  165. }
  166. @property(nonatomic,copy) NSArray<YCMenuAction *> *actions;
  167. @property(nonatomic,strong)UITableView *tableView;
  168. @property(nonatomic,strong)UIView *contentView;
  169. @property(nonatomic,strong)UIView *bgView;
  170. @end
  171. static NSString *const menuCellID = @"YCMenuCell";
  172. @implementation YCMenuView
  173. + (instancetype)menuWithActions:(NSArray<YCMenuAction *> *)actions width:(CGFloat)width atPoint:(CGPoint)point{
  174. NSAssert(width>0.0f, @"width要大于0");
  175. YCMenuView *menu = [[YCMenuView alloc] initWithActions:actions width:width atPoint:point];
  176. return menu;
  177. }
  178. + (instancetype)menuWithActions:(NSArray<YCMenuAction *> *)actions width:(CGFloat)width relyonView:(id)view{
  179. NSAssert(width>0.0f, @"width要大于0");
  180. NSAssert([view isKindOfClass:[UIView class]]||[view isKindOfClass:[UIBarButtonItem class]], @"relyonView必须是UIView或UIBarButtonItem");
  181. YCMenuView *menu = [[YCMenuView alloc] initWithActions:actions width:width relyonView:view];
  182. return menu;
  183. }
  184. - (instancetype)initWithActions:(NSArray<YCMenuAction *> *)actions width:(CGFloat)width atPoint:(CGPoint)point{
  185. if (self = [super init]) {
  186. _actions = [actions copy];
  187. _refPoint = point;
  188. _menuWidth = width;
  189. [self defaultConfiguration];
  190. [self setupSubView];
  191. }
  192. return self;
  193. }
  194. - (instancetype)initWithActions:(NSArray<YCMenuAction *> *)actions width:(CGFloat)width relyonView:(id)view{
  195. if (self = [super init]) {
  196. // 针对UIBarButtonItem做的处理
  197. if ([view isKindOfClass:[UIBarButtonItem class]]) {
  198. UIView *bgView = [view valueForKey:@"_view"];
  199. _refView = bgView;
  200. }else{
  201. _refView = view;
  202. }
  203. _actions = [actions copy];
  204. _menuWidth = width;
  205. [self defaultConfiguration];
  206. [self setupSubView];
  207. }
  208. return self;
  209. }
  210. - (void)defaultConfiguration{
  211. self.alpha = 0.0f;
  212. [self setDefaultShadow];
  213. _cornerRaius = 5.0f;
  214. _separatorColor = [UIColor blackColor];
  215. _menuColor = [UIColor whiteColor];
  216. _menuCellHeight = 44.0f;
  217. _maxDisplayCount = 5;
  218. _isShowShadow = YES;
  219. _dismissOnselected = YES;
  220. _dismissOnTouchOutside = YES;
  221. _textColor = [UIColor blackColor];
  222. _textFont = [UIFont systemFontOfSize:15.0f];
  223. _offset = 0.0f;
  224. }
  225. - (void)setupSubView{
  226. [self calculateArrowAndFrame];
  227. [self setupMaskLayer];
  228. [self addSubview:self.contentView];
  229. }
  230. - (void)reloadData{
  231. [self.contentView removeFromSuperview];
  232. [self.tableView removeFromSuperview];
  233. self.contentView = nil;
  234. self.tableView = nil;
  235. [self setupSubView];
  236. }
  237. - (CGPoint)getRefPoint{
  238. CGRect absoluteRect = [_refView convertRect:_refView.bounds toView:kMainWindow];
  239. CGPoint refPoint;
  240. CGFloat menuHeight = (_actions.count > _maxDisplayCount) ? _maxDisplayCount * _menuCellHeight + kArrowHeight: _actions.count * _menuCellHeight + kArrowHeight;
  241. if (absoluteRect.origin.y + absoluteRect.size.height + menuHeight > kScreenHeight - 10) {
  242. refPoint = CGPointMake(absoluteRect.origin.x + absoluteRect.size.width / 2, absoluteRect.origin.y);
  243. _isReverse = YES;
  244. }else{
  245. refPoint = CGPointMake(absoluteRect.origin.x + absoluteRect.size.width / 2, absoluteRect.origin.y + absoluteRect.size.height);
  246. _isReverse = NO;
  247. }
  248. return refPoint;
  249. }
  250. - (void)show{
  251. // 自定义设置统一在这边刷新一次
  252. if (_needReload) [self reloadData];
  253. [kMainWindow addSubview: self.bgView];
  254. [kMainWindow addSubview: self];
  255. self.layer.affineTransform = CGAffineTransformMakeScale(0.1, 0.1);
  256. [UIView animateWithDuration: kAnimationTime animations:^{
  257. self.layer.affineTransform = CGAffineTransformMakeScale(1.0, 1.0);
  258. self.alpha = 1.0f;
  259. self.bgView.alpha = 1.0f;
  260. }];
  261. }
  262. - (void)dismiss{
  263. if (!_dismissOnTouchOutside) return;
  264. //执行dismiss回调
  265. if (self.dissmisBlock) {
  266. self.dissmisBlock();
  267. }
  268. [UIView animateWithDuration: kAnimationTime animations:^{
  269. self.layer.affineTransform = CGAffineTransformMakeScale(0.1, 0.1);
  270. self.alpha = 0.0f;
  271. self.bgView.alpha = 0.0f;
  272. } completion:^(BOOL finished) {
  273. [self removeFromSuperview];
  274. [self.bgView removeFromSuperview];
  275. }];
  276. }
  277. #pragma mark - Private
  278. - (void)setupMaskLayer{
  279. CAShapeLayer *layer = [self drawMaskLayer];
  280. self.contentView.layer.mask = layer;
  281. }
  282. - (void)calculateArrowAndFrame{
  283. if (_refView) {
  284. _refPoint = [self getRefPoint];
  285. }
  286. CGFloat originX;
  287. CGFloat originY;
  288. CGFloat width;
  289. CGFloat height;
  290. width = _menuWidth;
  291. height = (_actions.count > _maxDisplayCount) ? _maxDisplayCount * _menuCellHeight + kArrowHeight: _actions.count * _menuCellHeight + kArrowHeight;
  292. // 默认在中间
  293. _arrowPosition = 0.5 * width - 0.5 * kArrowWidth;
  294. // 设置出menu的x和y(默认情况)
  295. originX = _refPoint.x - _arrowPosition - 0.5 * kArrowWidth;
  296. originY = _refPoint.y;
  297. // 考虑向左右展示不全的情况,需要反向展示
  298. if (originX + width > kScreenWidth - 10) {
  299. originX = kScreenWidth - kDefaultMargin - width;
  300. }else if (originX < 10) {
  301. //向上的情况间距也至少是kDefaultMargin
  302. originX = kDefaultMargin;
  303. }
  304. //设置三角形的起始点
  305. if ((_refPoint.x <= originX + width - _cornerRaius) && (_refPoint.x >= originX + _cornerRaius)) {
  306. _arrowPosition = _refPoint.x - originX - 0.5 * kArrowWidth;
  307. }else if (_refPoint.x < originX + _cornerRaius) {
  308. _arrowPosition = _cornerRaius;
  309. }else {
  310. _arrowPosition = width - _cornerRaius - kArrowWidth;
  311. }
  312. //如果不是根据关联视图,得算一次是否反向
  313. if (!_refView) {
  314. _isReverse = (originY + height > kScreenHeight - kDefaultMargin)?YES:NO;
  315. }
  316. CGPoint anchorPoint;
  317. if (_isReverse) {
  318. originY = _refPoint.y - height;
  319. anchorPoint = CGPointMake(fabs(_arrowPosition) / width, 1);
  320. _topMargin = 0;
  321. }else{
  322. anchorPoint = CGPointMake(fabs(_arrowPosition) / width, 0);
  323. _topMargin = kArrowHeight;
  324. }
  325. originY += originY >= _refPoint.y ? _offset : -_offset;
  326. //保存原来的frame,防止设置锚点后偏移
  327. self.layer.anchorPoint = anchorPoint;
  328. self.frame = CGRectMake(originX, originY, width, height);
  329. }
  330. - (CAShapeLayer *)drawMaskLayer{
  331. CAShapeLayer *maskLayer = [CAShapeLayer layer];
  332. CGFloat bottomMargin = !_isReverse?0 :kArrowHeight;
  333. // 定出四个转角点
  334. CGPoint topRightArcCenter = CGPointMake(self.width - _cornerRaius, _topMargin + _cornerRaius);
  335. CGPoint topLeftArcCenter = CGPointMake(_cornerRaius, _topMargin + _cornerRaius);
  336. CGPoint bottomRightArcCenter = CGPointMake(self.width - _cornerRaius, self.height - bottomMargin - _cornerRaius);
  337. CGPoint bottomLeftArcCenter = CGPointMake(_cornerRaius, self.height - bottomMargin - _cornerRaius);
  338. UIBezierPath *path = [UIBezierPath bezierPath];
  339. // 从左上倒角的下边开始画
  340. [path moveToPoint: CGPointMake(0, _topMargin + _cornerRaius)];
  341. [path addLineToPoint: CGPointMake(0, bottomLeftArcCenter.y)];
  342. [path addArcWithCenter: bottomLeftArcCenter radius: _cornerRaius startAngle: -M_PI endAngle: -M_PI-M_PI_2 clockwise: NO];
  343. if (_isReverse) {
  344. [path addLineToPoint: CGPointMake(_arrowPosition, self.height - kArrowHeight)];
  345. [path addLineToPoint: CGPointMake(_arrowPosition + 0.5*kArrowWidth, self.height)];
  346. [path addLineToPoint: CGPointMake(_arrowPosition + kArrowWidth, self.height - kArrowHeight)];
  347. }
  348. [path addLineToPoint: CGPointMake(self.width - _cornerRaius, self.height - bottomMargin)];
  349. [path addArcWithCenter: bottomRightArcCenter radius: _cornerRaius startAngle: -M_PI-M_PI_2 endAngle: -M_PI*2 clockwise: NO];
  350. [path addLineToPoint: CGPointMake(self.width, self.height - bottomMargin + _cornerRaius)];
  351. [path addArcWithCenter: topRightArcCenter radius: _cornerRaius startAngle: 0 endAngle: -M_PI_2 clockwise: NO];
  352. if (!_isReverse) {
  353. [path addLineToPoint: CGPointMake(_arrowPosition + kArrowWidth, _topMargin)];
  354. [path addLineToPoint: CGPointMake(_arrowPosition + 0.5 * kArrowWidth, 0)];
  355. [path addLineToPoint: CGPointMake(_arrowPosition, _topMargin)];
  356. }
  357. [path addLineToPoint: CGPointMake(_cornerRaius, _topMargin)];
  358. [path addArcWithCenter: topLeftArcCenter radius: _cornerRaius startAngle: -M_PI_2 endAngle: -M_PI clockwise: NO];
  359. [path closePath];
  360. maskLayer.path = path.CGPath;
  361. return maskLayer;
  362. }
  363. - (void)setDefaultShadow{
  364. self.layer.shadowOpacity = 0.2;
  365. self.layer.shadowOffset = CGSizeMake(0, 0);
  366. self.layer.shadowRadius = 5.0;
  367. }
  368. #pragma mark - <UITableViewDelegate,UITableViewDataSource>
  369. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
  370. return _actions.count;
  371. }
  372. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
  373. YCMenuCell *cell = [tableView dequeueReusableCellWithIdentifier:menuCellID forIndexPath:indexPath];
  374. YCMenuAction *action = _actions[indexPath.row];
  375. cell.backgroundColor = [UIColor clearColor];
  376. cell.textLabel.font = _textFont;
  377. cell.textLabel.textAlignment = NSTextAlignmentCenter;
  378. cell.textLabel.textColor = _textColor;
  379. cell.textLabel.text = action.title;
  380. cell.separatorColor = _separatorColor;
  381. cell.imageView.image = action.image?action.image:nil;
  382. if (indexPath.row == _actions.count - 1) {
  383. cell.isShowSeparator = NO;
  384. }
  385. return cell;
  386. }
  387. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
  388. [tableView deselectRowAtIndexPath:indexPath animated:YES];
  389. if (_dismissOnselected) [self dismiss];
  390. YCMenuAction *action = _actions[indexPath.row];
  391. if (action.handler) {
  392. action.handler(action);
  393. }
  394. }
  395. #pragma mark - Setting&&Getting
  396. - (UITableView *)tableView{
  397. if (!_tableView) {
  398. _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, _topMargin, self.width, self.height - kArrowHeight) style:UITableViewStylePlain];
  399. _tableView.backgroundColor = [UIColor clearColor];
  400. _tableView.delegate = self;
  401. _tableView.dataSource = self;
  402. _tableView.bounces = _actions.count > _maxDisplayCount? YES : NO;
  403. _tableView.rowHeight = _menuCellHeight;
  404. _tableView.tableFooterView = [UIView new];
  405. _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
  406. [_tableView registerClass:[YCMenuCell class] forCellReuseIdentifier:menuCellID];
  407. }
  408. return _tableView;
  409. }
  410. - (UIView *)bgView{
  411. if (!_bgView) {
  412. _bgView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
  413. _bgView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0];
  414. _bgView.alpha = 0.0f;
  415. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismiss)];
  416. [_bgView addGestureRecognizer:tap];
  417. }
  418. return _bgView;
  419. }
  420. - (UIView *)contentView{
  421. if (!_contentView) {
  422. _contentView = [[UIView alloc] initWithFrame:self.bounds];
  423. _contentView.backgroundColor = _menuColor;
  424. _contentView.layer.masksToBounds = YES;
  425. [_contentView addSubview:self.tableView];
  426. }
  427. return _contentView;
  428. }
  429. #pragma mark - 设置属性
  430. - (void)setCornerRaius:(CGFloat)cornerRaius{
  431. if (_cornerRaius == cornerRaius)return;
  432. _cornerRaius = cornerRaius;
  433. self.contentView.layer.mask = [self drawMaskLayer];
  434. }
  435. - (void)setMenuColor:(UIColor *)menuColor{
  436. if ([_menuColor isEqual:menuColor]) return;
  437. _menuColor = menuColor;
  438. self.contentView.backgroundColor = menuColor;
  439. }
  440. - (void)setBackgroundColor:(UIColor *)backgroundColor{
  441. if ([_menuColor isEqual:backgroundColor]) return;
  442. _menuColor = backgroundColor;
  443. self.contentView.backgroundColor = _menuColor;
  444. }
  445. - (void)setSeparatorColor:(UIColor *)separatorColor{
  446. if ([_separatorColor isEqual:separatorColor]) return;
  447. _separatorColor = separatorColor;
  448. [self.tableView reloadData];
  449. }
  450. - (void)setMenuCellHeight:(CGFloat)menuCellHeight{
  451. if (_menuCellHeight == menuCellHeight)return;
  452. _menuCellHeight = menuCellHeight;
  453. _needReload = YES;
  454. }
  455. - (void)setMaxDisplayCount:(NSInteger)maxDisplayCount{
  456. if (_maxDisplayCount == maxDisplayCount)return;
  457. _maxDisplayCount = maxDisplayCount;
  458. _needReload = YES;
  459. }
  460. - (void)setIsShowShadow:(BOOL)isShowShadow{
  461. if (_isShowShadow == isShowShadow)return;
  462. _isShowShadow = isShowShadow;
  463. if (!_isShowShadow) {
  464. self.layer.shadowOpacity = 0.0;
  465. self.layer.shadowOffset = CGSizeMake(0, 0);
  466. self.layer.shadowRadius = 0.0;
  467. }else{
  468. [self setDefaultShadow];
  469. }
  470. }
  471. - (void)setTextFont:(UIFont *)textFont{
  472. if ([_textFont isEqual:textFont]) return;
  473. _textFont = textFont;
  474. [self.tableView reloadData];
  475. }
  476. - (void)setTextColor:(UIColor *)textColor{
  477. if ([_textColor isEqual:textColor]) return;
  478. _textColor = textColor;
  479. [self.tableView reloadData];
  480. }
  481. //- (void)setOffset:(CGFloat)offset{
  482. // if (offset == offset) return;
  483. // _offset = offset;
  484. // if (offset < 0.0f) {
  485. // offset = 0.0f;
  486. // }
  487. // self.y += self.y >= _refPoint.y ? offset : -offset;
  488. //}
  489. @end