前言
1.支持展开折叠的弹出菜单的实现思路:
1.1将弹出视图添加到keyWindow,蒙版也添加到主窗口(主要原因是点击屏幕的空白处,需要隐藏弹出视图)
1.2展示的时候,动画从右上角往左下脚延伸;隐藏的时候,动画从左下脚往右上角收回 (展示的时候,从上往下,即x,y 慢慢变大)
1.3 内部视图采用collectionView进行布局
1.4 view的frame 是根据当前点击的菜单按钮所在的商品cell进行计算和坐标转换的。
/** 触发折叠菜单隐藏和显示的按钮 */ @property (nonatomic,weak) UIButton *btn; /** 用于计算折叠菜单frame, */ @property (nonatomic,weak) UIButton *tmpbtn;
2.水平方向弹出菜单视图的应用场景:
2.1、门店商品的支持的功能:向右横向展开视图(操作:下/上架、打印、编辑、同步网络)支持再次折叠隐藏视图
2.2、网店商品目前只包含下架功能:展开折叠视图:(包含上/下架商品功能)
- 竖向弹出菜单视图
弹出菜单:会员模块的右上角的下拉菜单(竖向)https://kunnan.blog.csdn.net/article/details/84618986
视频:https://live.csdn.net/v/173757
demo1下载地址:https://download.csdn.net/download/u011018979/20598998
demo 设置两个测试开关 :
测试开关1:将水平方向弹出菜单视图集成到cell
测试开关2:将水平方向弹出菜单视图集成到VC的View
demo2下载地址:https://download.csdn.net/download/u011018979/20537947
demo2的内容是:将水平方向弹出菜单视图集成到VC的View
疑问解答,请关注公众号:iOS逆向
I、 支持展开折叠的弹出菜单的实现思路
1.1将弹出视图添加到keyWindow,蒙版也添加到主窗口(主要原因是点击屏幕的空白处,需要隐藏弹出视图)
#define kWindow [UIApplication sharedApplication].keyWindow [kWindow addSubview:self]; [kWindow addSubview:self.cover];//蒙版添加到主窗口, 蒙版用于监听点击事件,来隐藏弹出视图
1.2 展开
展示的时候,动画从右上角往左下脚延伸;隐藏的时候,动画从左下脚往右上角收回 (展示的时候,从上往下,即x,y 慢慢变大)
展开效果的实现原理:
1 点击展示商品信息的cell 上面的弹出按钮时,阴影alpha由0到1,弹窗的scale由0到1(这里使用CABasicAnimation)
2 点击空白处(self.cover),再让阴影alpha由1到0,弹窗的scale由1到0(同样使用CABasicAnimation),动画完成后移除阴影和弹窗
1.3 内部视图采用collectionView进行布局
@property (strong, nonatomic) UICollectionView *collectionView;
1.4 view的frame 是根据当前点击的菜单按钮所在的商品cell进行计算和坐标转换的。
CGRect endRect = [weakSelf.btn.superview convertRect:weakSelf.btn.frame toView:kWindow]; CGRect Rect = [weakSelf.tmpbtn.superview convertRect:weakSelf.tmpbtn.frame toView:kWindow]; // 设置菜单的frame weakSelf.models.rect = Rect; weakSelf.models.endRect = endRect;// 结束的位置 [[[weakSelf models].viewModel expandMenuSubject] sendNext:weakSelf.models]; }];
II、用法
2.1 创建弹出菜单popmenuView
- 构建菜单内部的数据模型
+ (NSMutableArray*)getMenudatas4MiniAppWithBlock:(void (^)(id sender))block{ NSMutableArray* tmp = [NSMutableArray array]; QCTCollectionModel *network = [QCTCollectionModel new]; network.titleName = QCTLocal(@"Shelves_key"); network.imgName= @"icon_sp_shangjia"; network.block = block; network.type =QCTCollectionModelType4Shelves; [tmp addObject:network]; return tmp; }
- 实现popmenuView的懒加载
#pragma mark - ******** 支持展开折叠的弹出菜单视图 - (QCTHorizontalpopupView *)popmenuView{ if (!_popmenuView) { _popmenuView = [[QCTHorizontalpopupView alloc] initWithFrame:self.view.frame viewModel:self.viewModel]; _popmenuView.hidden = YES; #pragma mark - ******** 构建折叠视图的模型 __weak __typeof__(self) weakSelf = self; self.viewModel.Menudatas = [QCTCollectionModel getMenudatas4MiniAppWithBlock:^( QCTCollectionModel * sender) { [[[weakSelf viewModel] hiddenSubject]sendNext:nil]; NSLog(@"点击了%@",[sender titleName]); switch (sender.type) { case QCTCollectionModelType4edit: { [weakSelf setupQCTEditMerchandiseViewController:sender]; } break; case QCTCollectionModelType4Shelves: // /**上架*/ { // 根据不同的商品类型进行界面跳转 [weakSelf setupQCTCollectionModelType4Shelves:sender]; } break; case QCTCollectionModelType4Offtheshelf: { // 根据不同的商品类型进行界面跳转 [weakSelf setupQCTCollectionModelType4Shelves:sender]; } break; // /**打印*/ case QCTCollectionModelType4Printer: { //打印 [weakSelf printerInfo:self.popmenuView.model]; } break; default: break; } } ]; _popmenuView.models = self.viewModel.Menudatas; } return _popmenuView; }
2.2 监听弹出和折叠事件
/** 监听弹出事件,此事件由展示商品信息的cell发出。 */ [self.viewModel.showMenuSubject subscribeNext:^(QCTgoodsManListModel * x) { [weakSelf.popmenuView updateRect: x.rect ];// 更新popmenuView的位置。 [weakSelf.popmenuView updateendRect: x.endRect ];// 设置折叠动画的终点 [weakSelf.popmenuView expandView:x.expandViewCGPoint ]; }]; /** 折叠弹出菜单 */ [self.viewModel.hiddenSubject subscribeNext:^(id _Nullable x) { [weakSelf.popmenuView foldView]; }]; // 监听弹出菜单按钮的点击事情,进行判断是展开还是隐藏 [self.viewModel.expandMenuSubject subscribeNext:^(id _Nullable x) { [weakSelf expandMenu:x]; }];
- 判断是展开弹出菜单,还是折叠
#pragma mark - ******** 判断是展开弹出菜单,还是折叠 - (void)expandMenu:(id)x{//点击按钮 self.popmenuView.model = x; [ self.viewModel.reloadSubject sendNext:nil]; if ([self.popmenuView isHidden]) { [self.viewModel.showMenuSubject sendNext:x]; }else{ [self.viewModel.hiddenSubject sendNext:nil]; } }
III、完整demo
demo 设置两个测试开关
[self addpopV2cell];// 测试开关1:将水平方向弹出菜单视图集成到cell // [self addpopV2VCView];// 测试开关2:将水平方向弹出菜单视图集成到VC的View
3.1 demo1: 将水平方向弹出菜单视图集成到cell
demo1下载地址:https://download.csdn.net/download/u011018979/20598998
3.2 demo2:将水平方向弹出菜单视图集成到VC的View
文章:https://kunnan.blog.csdn.net/article/details/106406160
视频:https://live.csdn.net/v/173757
demo2下载地址:https://download.csdn.net/download/u011018979/20537947
1.支持展开折叠的弹出菜单的实现思路:
1.1将弹出视图添加到keyWindow,蒙版也添加到主窗口(主要原因是点击屏幕的空白处,需要隐藏弹出视图)
1.2展示的时候,动画从右上角往左下脚延伸;隐藏的时候,动画从左下脚往右上角收回 (展示的时候,从上往下,即x,y 慢慢变大)
1.3 内部视图采用collectionView进行布局
1.4 view的frame 是根据当前点击的菜单按钮所在的商品cell进行计算和坐标转换的。
2.水平方向弹出菜单视图的应用场景:
2.1、门店商品的支持的功能:向右横向展开视图(操作:下/上架、打印、编辑、同步网络)支持再次折叠隐藏视图
2.2、网店商品目前只包含下架功能:展开折叠视图:(包含上/下架商品功能)
3.3 水平方向弹出菜单视图
弹出菜单HorizontalpopupView的具体代码
- .h
#import <UIKit/UIKit.h> #import "QCTgoodsManListModel.h" #import "QCThorizontalMenuCollectionViewCell.h" #import "QCTgoodsListViewModel.h" NS_ASSUME_NONNULL_BEGIN /** Horizontal Popup View 横向(水平方向)弹出菜单视图 本app类似的弹出菜单:会员模块的右上角的下拉菜单(竖向)。 I 、 展开折叠菜单(横向): 0、场景: 0.1、网店商品目前只包含下架功能:展开折叠视图:(包含上/下架商品功能)。 0.2、门店商品的支持的功能: 向右横向展开视图(操作:下/上架、打印、编辑、同步网络)支持再次折叠隐藏视图 II、实现思路: 1、 将视图添加到keyWindow,蒙版也添加到主窗口(避免每个商品cell都创建一个菜单视图) #define kWindow [UIApplication sharedApplication].keyWindow [kWindow addSubview:self]; [kWindow addSubview:self.cover];//蒙版添加到主窗口 2、展开效果:展示的时候,动画从右上角往左下脚延伸;隐藏的时候,动画从左下脚往右上角收回 // (展示的时候,从上往下,即x,y 慢慢变大) 2.1、展开效果的实现原理: 点击弹出按钮时,阴影alpha由0到1,弹窗的scale由0到1(这里使用CABasicAnimation) 点击空白处,再让阴影alpha由1到0,弹窗的scale由1到0(同样使用CABasicAnimation),动画完成后移除阴影和弹窗 3、内部视图采用collectionView进行布局 4、view的frame 是根据当前点击的菜单按钮所在的商品cell进行计算和坐标转换的。 [[_btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) { // 获取_btn 在Windows的 frame CGRect endRect = [weakSelf.btn.superview convertRect:weakSelf.btn.frame toView:kWindow]; CGRect Rect = [weakSelf.tmpbtn.superview convertRect:weakSelf.tmpbtn.frame toView:kWindow]; // 设置菜单的frame weakSelf.models.rect = Rect; weakSelf.models.endRect = endRect;// 结束的位置 [[[weakSelf models].viewModel expandMenuSubject] sendNext:weakSelf.models]; }]; */ @interface QCTHorizontalpopupView : UIView /** 商品数据,存储着商品信息。用于打印、上下架以及编辑等 */ @property (nonatomic, strong) QCTgoodsManListModel* model; /** 菜单视图的模型数据 */ @property (nonatomic, strong) NSMutableArray* models; @property (nonatomic, copy) void (^block)(id sender); @property (nonatomic,strong) QCTgoodsListViewModel *viewModel; - (id)initWithFrame:(CGRect)frame viewModel:(id)viewModel ; /** 更新菜单的frame。 需要根据models 设置设置下宽度 */ - (void)updateRect:(CGRect)rect; /** 更新折叠动画的结束位置 */ - (void)updateendRect:(CGRect)rect; /** 展开方法 */ - (void)expandView:(CGPoint)AnchorPoint; /** 折叠方法 */ - (void)foldView; @end NS_ASSUME_NONNULL_END
- m
#import "QCTHorizontalpopupView.h" @interface QCTHorizontalpopupView () /** */ @property (strong, nonatomic) UICollectionView *collectionView; @property (strong, nonatomic) NSIndexPath *indexPath; /** 蒙版,用于处理点击空白处(即非弹出菜单部分)的事件 */ @property (nonatomic, weak) UIView *cover; /** 弹出菜单的横向箭头 */ @property (nonatomic, strong) UIImageView *img; /** 存储折叠动画的结束位置的View */ @property (nonatomic, strong) UIView *clickView; @end @implementation QCTHorizontalpopupView - (UIView *)clickView{ if (nil == _clickView) { UIView *tmpView = [[UIView alloc]init]; _clickView = tmpView; [self addSubview:_clickView]; tmpView.backgroundColor = [UIColor clearColor]; tmpView.alpha = 0; __weak __typeof__(self) weakSelf = self; [tmpView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.equalTo(weakSelf).offset(0); make.size.mas_equalTo(CGSizeMake(kAdjustRatio(10), kAdjustRatio(10))); make.left.equalTo(weakSelf.img.mas_right).offset(kAdjustRatio(5)); }]; } return _clickView; } // 背景图片的菜单箭头 -(UIImageView *)img{//icon_huiyuanka_sanjiao if (!_img) { _img = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_sp_sanjiao"]];// [self addSubview:_img]; __weak __typeof__(self) weakSelf = self; [_img mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.equalTo(weakSelf).offset(0); make.left.equalTo(weakSelf.mas_right).offset(-kAdjustRatio(0)); }]; } return _img; } /** 蒙版用于监听点击事件,来隐藏弹出视图 */ -(UIView *)cover{ if (!_cover) { UIView *tmp = [[UIView alloc] initWithFrame:kWindow.bounds]; _cover = tmp; _cover.backgroundColor = [UIColor clearColor]; [kWindow addSubview:self.cover];//蒙版添加到主窗口 _cover.hidden = YES; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(removeMenuList)]; [_cover addGestureRecognizer:tap]; } return _cover; } - (void)setViewModel:(QCTgoodsListViewModel *)viewModel{ _viewModel = viewModel; } - (id)init { return [self initWithFrame:CGRectZero viewModel:nil]; } - (id)initWithFrame:(CGRect)frame { return [self initWithFrame:frame viewModel:nil]; } - (id)initWithCoder:(NSCoder *)aDecoder { return [self initWithFrame:CGRectZero viewModel:nil]; } - (id)initWithViewModel:(id)viewModel { return [self initWithFrame:CGRectZero viewModel:viewModel]; } - (id)initWithFrame:(CGRect)frame viewModel:(id)viewModel { if (self = [super initWithFrame:frame]) { self.viewModel = viewModel; [self bindViewModel]; [self collectionView]; [self cover]; [self img]; [self clickView]; } return self; } - (void)bindViewModel { @weakify(self); __weak __typeof__(self) weakSelf = self; [self.viewModel.reloadSubject subscribeNext:^(id _Nullable x) { @strongify(self); [self.collectionView reloadData]; }]; } - (void)updateendRect:(CGRect)rect{ self.clickView.frame = rect; } /** 更新frame. rect 是根据写,x, y 是根据当前选中的商品对应的cell位置进行获取的。 */ - (void)updateRect:(CGRect)rect{ self.frame = rect; [self updateFocusIfNeeded]; [self.collectionView updateConstraints]; [self.collectionView layoutIfNeeded]; [self layoutSubviews]; } /** NSMutableArray */ - (void)setModels:(NSMutableArray*)models{ _models = models; [self.collectionView reloadData]; } - (UICollectionView *)collectionView { if (_collectionView == nil) { UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; // 2.设置整个collectionView的内边距 //分别为上、左、下、右 // flowLayout.sectionInset = UIEdgeInsetsMake(0,kAdjustRatio(10),0,kAdjustRatio(10)); //.设置每一行之间的间距 flowLayout.minimumLineSpacing = 0; flowLayout.minimumInteritemSpacing = 0; // flowLayout.itemSize = CGSizeMake((SCREEN_WIDTH-3*kAdjustRatio(10))/3.0, self.optionsView.height); _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:flowLayout]; // _collectionView.backgroundColor = [UIColor clearColor]; UIView *style = _collectionView; style.layer.cornerRadius = 3; style.layer.backgroundColor = [[UIColor colorWithRed:0.0f/255.0f green:0.0f/255.0f blue:0.0f/255.0f alpha:1.0f] CGColor]; style.alpha = 0.8; _collectionView.showsVerticalScrollIndicator = NO; _collectionView.bounces = NO; _collectionView.dataSource = self; _collectionView.delegate = self; [_collectionView registerClass:[QCThorizontalMenuCollectionViewCell class] forCellWithReuseIdentifier:@"QCThorizontalMenuCollectionViewCell"]; if (@available(iOS 11.0, *)) { _collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; } else { // Fallback on earlier versions } _collectionView.scrollEnabled = NO; __weak __typeof__(self) weakSelf = self; [self addSubview:_collectionView]; [_collectionView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.top.bottom.right.equalTo(weakSelf); }]; } return _collectionView; } #pragma mark - UICollectionViewDelegate - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{ return 1; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return self.models.count; } -(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { CGSize size; // size = CGSizeMake(self.collectionView.width/self.models.count, kAdjustRatio(kHorizontalpopupViewH));//kHorizontalpopupViewH // size = CGSizeMake(self.collectionView.width/3, kAdjustRatio(50)); return size; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { QCThorizontalMenuCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"QCThorizontalMenuCollectionViewCell" forIndexPath:indexPath]; QCTCollectionModel *model = self.models[indexPath.row]; // // /**上架*/ // QCTCollectionModelType4Shelves, // /**下架*/ // QCTCollectionModelType4Offtheshelf, // // if(model.type == QCTCollectionModelType4Shelves || model.type == QCTCollectionModelType4Offtheshelf ){ if(!self.model.state.boolValue){ model.titleName = QCTLocal(@"Shelves_key"); // @"上架"; model.imgName = @"icon_sp_shangjia"; model.type = QCTCollectionModelType4Shelves; }else{ model.type = QCTCollectionModelType4Offtheshelf; model.titleName = QCTLocal(@"Off_the_shelf"); // @"下架"; model.imgName = @"icon_sp_xiajia"; } } cell.model =model; cell.goodmodel = self.model; return cell; } #pragma 动画: 设置layer.anchorPoint + (void) setAnchorPoint:(CGPoint)anchorpoint forView:(UIView *)view{ CGRect oldFrame = view.frame; view.layer.anchorPoint = anchorpoint; view.frame = oldFrame; } /** 点击弹出按钮时,阴影alpha由0到1,弹窗的scale由0到1(这里使用CABasicAnimation) 点击空白处,再让阴影alpha由1到0,弹窗的scale由1到0(同样使用CABasicAnimation),动画完成后移除阴影和弹窗 //展示的时候,动画从右上角往左下脚延伸;隐藏的时候,动画从左下脚往右上角收回 // (展示的时候,从上往下,即x,y 慢慢变大) */ - (void)expandView:(CGPoint)AnchorPoint{ [self removeFromSuperview]; [kWindow addSubview:self]; [self.cover removeFromSuperview]; [kWindow addSubview:self.cover];//蒙版添加到主窗口 [kWindow bringSubviewToFront:self]; //展示的时候,动画从右上角往左下脚延伸;隐藏的时候,动画从左下脚往右上角收回 // (展示的时候,从上往下,即x,y 慢慢变大) [[self class] setAnchorPoint:CGPointMake(1.f, 0.5f) forView:self]; self.transform = CGAffineTransformMakeScale(0.001f, 0.001f); self.hidden = NO;// 修改为动画, MemberCardMenuView 提供一个动画的实例方法 self.cover.hidden = NO; self.cover.alpha = 0; [UIView animateWithDuration:0.3 animations:^{ self.transform = CGAffineTransformMakeScale(1.f, 1.f); self.cover.alpha = 1; } completion:^(BOOL finished) { self.transform = CGAffineTransformIdentity; }]; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self.viewModel.hiddenSubject sendNext:nil]; } /** 折叠 */ - (void)foldView{ /* 知识点: (0,0) 为左上角,(0,1) 为左下角, (1, 0)右上, (1,1) 右下 */ [UIView animateWithDuration:0.3 animations:^{ self.transform = CGAffineTransformMakeScale(0.001f, 0.001f); self.cover.alpha = 0; } completion:^(BOOL finished) { self.hidden = YES; self.cover.hidden = YES; self.transform = CGAffineTransformIdentity; [self removeFromSuperview]; [self.cover removeFromSuperview]; }]; } #pragma mark - 移除菜单 -(void)removeMenuList{ [self.viewModel.hiddenSubject sendNext:nil]; } @end
3.4 集成到cell
粉丝疑问:是否可以用在cell的点击事件
答:可以,请参考本文的集成例子QCTQCTgoodsListTableViewCellView
你也可以利用cancelsTouchesInView属性,控制点击事件优先级。案例:iOS设置tableView的点击事件优先级低于cell的选中事件【场景:比如筛选视图,监听蒙版的点击事件就隐藏筛选视图】https://blog.csdn.net/z929118967/article/details/89405040
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init]; [[tap rac_gestureSignal] subscribeNext:^(id x) { @strongify(self); if (self.alpha) { [self.viewModel.hiddenSubject sendNext:nil]; } }]; [self addGestureRecognizer:tap]; UITapGestureRecognizer *cutTap = [[UITapGestureRecognizer alloc] init]; cutTap.cancelsTouchesInView = NO;// 设置tableView的点击事件优先级,低于cell的选中事件 [[cutTap rac_gestureSignal] subscribeNext:^(id x) { // @strongify(self); [self.viewModel.hiddenSubject sendNext:nil]; }]; [self.tableView addGestureRecognizer:cutTap];
QCTQCTgoodsListTableViewCellView
集成到cell
#import "QCTQCTgoodsListTableViewCellView.h" /** 展示商品信息 */ @interface QCTQCTgoodsListTableViewCellView () @property (strong, nonatomic) UIImageView *iconImgV; @property (nonatomic,weak) UILabel *titleLab; @property (nonatomic,weak) UILabel *barCodeTitleLab;// @property (nonatomic,weak) UILabel *priceLab; @property (nonatomic,weak) UILabel *skuLab;// 库存 /** 折叠菜单按钮 */ @property (nonatomic,weak) UIButton *btn;// 更多 /** 用于 计算折叠菜单frame, */ @property (nonatomic,weak) UIButton *tmpbtn;// @property (nonatomic,weak) UIView *linView;// @end @implementation QCTQCTgoodsListTableViewCellView // 完整代码从demo查看 @end