前言

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、门店商品的支持的功能: 向右横向展开视图(操作:下/上架、打印、编辑、同步网络)支持再次折叠隐藏视图 iOS 水平方向弹出菜单(支持展开折叠)_弹出菜单

2.2、网店商品目前只包含下架功能:展开折叠视图:(包含上/下架商品功能) iOS 水平方向弹出菜单(支持展开折叠)_前端_02


  1. 竖向弹出菜单视图


​​弹出菜单:会员模块的右上角的下拉菜单(竖向)





demo 设置两个测试开关 :

测试开关1:将水平方向弹出菜单视图集成到cell

测试开关2:将水平方向弹出菜单视图集成到VC的View




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

  1. 构建菜单内部的数据模型
+ (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;

}
  1. 实现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下载地址: 疑问解答,请关注公众号:iOS逆向

3.2 demo2:将水平方向弹出菜单视图集成到VC的View


1.支持展开折叠的弹出菜单的实现思路:


1.1将弹出视图添加到keyWindow,蒙版也添加到主窗口(主要原因是点击屏幕的空白处,需要隐藏弹出视图)


1.2展示的时候,动画从右上角往左下脚延伸;隐藏的时候,动画从左下脚往右上角收回 (展示的时候,从上往下,即x,y 慢慢变大) 1.3 内部视图采用collectionView进行布局 1.4 view的frame 是根据当前点击的菜单按钮所在的商品cell进行计算和坐标转换的。

2.水平方向弹出菜单视图的应用场景:


2.1、门店商品的支持的功能: 向右横向展开视图(操作:下/上架、打印、编辑、同步网络)支持再次折叠隐藏视图 iOS 水平方向弹出菜单(支持展开折叠)_.net_03

2.2、网店商品目前只包含下架功能:展开折叠视图:(包含上/下架商品功能) iOS 水平方向弹出菜单(支持展开折叠)_ico_04


3.3 水平方向弹出菜单视图

弹出菜单HorizontalpopupView的具体代码


iOS 水平方向弹出菜单(支持展开折叠)_弹出菜单_05


  • .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的选中事件【场景:比如筛选视图,监听蒙版的点击事件就隐藏筛选视图】 ​​blog.csdn.net/z929118967/…​​


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