背景
在做项目时需要用到筛选栏,本来想偷懒找个第三方,在gayhub下了几个,无外乎都是臃肿复杂,在我的想象中,这种库只需要简单的集成就好了,不需要关心太多东西。好吧,只能自己动手了 ╮(╯╰)╭
开始使用
获取控件
点击获取dome:ZTNavigationMenuDemo
目前还不支持pods集成,所以只能手动拉到项目里。
直接把ZTNavigationMenuDemo里的ZTNavigationMenu拉到项目里即可。
该控件使用到以下知识点
- +load方法
- runtime - 方法交换
- UIView的视图层次与相应的方法
- Masonry的使用
- UIView动画
依赖
该控件使用的是自动布局,依赖Masonry库,如果你的项目没有,请集成进去。
简单集成
如果不需要自定义的话,只需要简单的3行代码即可集成该控件:
NSArray *data = [[NSArray alloc] initWithObjects:@"Objective-C", @"Swift", @"CPP", @"Java", @"PHP", @"SQL Server", nil]; // 数据源,并不需要一开始就给控件,你可以等到你需要的时候再给控件赋值
ZTNavigationMenuBlock block = ^(NSArray<NSString *> *data, int index) {
NSLog(@"data = %@", [data objectAtIndex:index]);
}; // 点击cell的回调,同样的也不需要一开始就给控件
// 3行代码创建控件
ZTNavigationMenu *titleView = [[ZTNavigationMenu alloc] initWithBlock:block];
[titleView setData:data];
[[self navigationItem] setTitleView:titleView];
只需要这样子就OK了,显示效果如下:
属性与方法
如果不满足默认效果,我还提供了一些属性与方法可以自定义控件。
属性说明:
@property(nonatomic, strong) NSArray<NSString *> *data; // 数据源,目前只能是字符串数组;只需要赋值就会自动刷新,不像tableView调用reloadData方法才会刷新
@property(nonatomic, strong) UIFont *titleFont; // 标题字体;如果该值非法则会调为默认
@property(nonatomic, strong) UIColor *titleColor; // 标题颜色;如果该值非法则会调为默认值
@property(nonatomic, assign, readonly) int index; // 选中cell的下标;当获取该值时,如果是-1,则表示没有选中的cell(可能data没数据)
@property(nonatomic, assign) int menuWidth; // 该控件的宽度,默认是140;取值范围是140~200;小于最小会被调整为最小值,大于最大会被调整为最大值
@property(nonatomic, assign) int cellHeight; // cell的高度,默认50;取值范围是30~100;超出范围同样会被调整
@property(nonatomic, assign) int contentHeight; // 显示内容的高度,默认200;取值范围是100~350;超出范围会被调整
@property(nonatomic, assign) int layoutGuideBottom; // 遮罩距离底部的高度,默认0;取值范围1~150;当设置值为范围之外的时候,将会采取默认行为
@property(nonatomic, assign) UIBlurEffectStyle effectStyle; // 模糊风格,只有style为ZTBackgroundStyleEffect时才有效;默认UIBlurEffectStyleLight;非法值都被调整为默认值
@property(nonatomic, assign) CGFloat effectAlpha; // 模糊透明度,默认0.8;取值范围0.1~1;超出范围会被调整
@property(nonatomic, assign) ZTBackgroundStyle style; // 背景类型
@property(nonatomic, assign, getter=isTapBackgroundHidden) BOOL tapBackgroundHidden; // 点击遮罩可以隐藏Menu,YES是启用(默认YES)
@property(nonatomic, assign, getter=isRotate) BOOL rotate; // Menu的图片是否可以旋转,YES是可以旋转(默认YES)
实例化方法:
// block可以不传,后续可以通过addBlock方法添加上去
+ (instancetype)navigationMenu;
+ (instancetype)navigationMenuWithBlock:(ZTNavigationMenuBlock)block;
- (instancetype)initWithBlock:(ZTNavigationMenuBlock)block;
方法说明:
- (void)addBlock:(ZTNavigationMenuBlock)block; // 更新回调的block
- (void)hiddenNavigationMenu; // 隐藏菜单
- (BOOL)updateImage:(NSString *)imageName andSize:(CGSize)size; // 更新Menu的图片和大小,如果图片获取失败则返回NO,并且使用默认图片和大小;size的范围是0~44,如果size的某个值非法的话,则会被调整到合法范围
- (BOOL)updateIndex:(int)index; // 更新index(切换选中的cell),如果index非法,则返回NO,如果Menu还没添加到导航栏,会返回YES但不代表一定能选中该index的cell,可能会因为data元素不够而无法选中
- (void)invalidate; // 销毁ZTNavigationMenu;不要调用removeFromSuperview和[[self navigationItem] setTitleView:nil]来销毁;一旦调用了该方法,则该控件就不可用了,需要再次使用的话,请重新创建一个
自定义显示效果
自定义显示效果我就只写一个,因为dome里都有,感兴趣的同学请下载dome自行查看。(其实是因为我懒。。。)
很多APP都是用模糊来代替半透明了,因为模糊实在是好看,所以我也实现了模糊遮罩,使用起来也很简单,只需要几句代码:
[titleView setStyle:ZTBackgroundStyleEffect]; // 开启模糊显示
[titleView setEffectStyle:UIBlurEffectStyleLight]; // 模糊类型
[titleView setEffectAlpha:0.8f]; // 透明度
因为这些我用的都是默认值
[titleView setEffectStyle:UIBlurEffectStyleLight];
[titleView setEffectAlpha:0.8f];
所以事实上这些实现这句就可以了
[titleView setStyle:ZTBackgroundStyleEffect];
控件部分实现讲解
实例化
重写init方法,并且把默认值都在这里赋值;因为是继承于UIControl,所以可以直接在这里添加点击事件。
- (instancetype)init
{
if (self = [super init])
{
_titleFont = kZTDefaultTitleFont;
_titleColor = kZTDefaultTitleColor;
_effectStyle = UIBlurEffectStyleLight;
_style = ZTBackgroundStyleTranslucent;
_tapBackgroundHidden = YES;
_rotate = YES;
_tempIndex = -1;
_index = -1;
_menuWidth = 140;
_cellHeight = 50;
_contentHeight = 200;
_layoutGuideBottom = 0;
_effectAlpha = 0.8f;
[self setBackgroundColor:[UIColor clearColor]];
[self addTarget:self action:@selector(tapCall) forControlEvents:UIControlEventTouchUpInside];
[self createView];
}
return self;
}
初始化的时候主要是做了这些工作:设置默认值、绑定点击事件、创建添加到导航栏的view
创建contenView
当ZTNavigationMenu添加到导航栏的时候,会触发UIView的didMoveToSuperview方法,我们在该方法里创建contenView;因为didMoveToSuperview会多次触发,所以需要用个变量来记录第一次的触发,防止多次创建view。
- (void)didMoveToSuperview
{
if ([self isAddToView])
{
return;
}
[self setAddToView:YES];
[self setVC:[self getCurrentVC]];
[self updateMenuWidth];
[self createMenuView]; // 创建contenView
[self setData:[self tempData]];
[self updateIndex:[self tempIndex]];
[self setTempData:nil];
return;
}
方法重写的思路
因为contenView是在添加到导航栏之后才会创建的,所以在添加到导航栏之前对contenView进行配置的话会无效,因为contenView还不存在。当然,这不是我们想要的,所以我们要做些处理;
另外,当添加到导航栏之后进行配置,虽然contenView存在了,但它是按默认值去创建的,所以我们也要对其进行更新。
以下是我重写data方法的思路:
- (void)setData:(NSArray *)data
{
if ([self isAddToView])
{
[self showData:data];
}
else
{
[self setTempData:data];
}
return;
}
- (void)showData:(NSArray *)data
{
NSString *title = nil;
NSUInteger count = [[self data] count];
int i = 0; // 判断标志,0是刷新(默认),-1是删除;1不做操作
// 判断data数量是不是0
if ([data count] == 0)
{
if (count > 0) // 原来是有数据的,则删除数据
{
i = -1;
}
else
{
// 都没有数据,不做操作
i = 1;
}
UIViewController *VC = [self VC];
title = [VC title];
_index = -1;
}
else
{
title = [data objectAtIndex:0];
_index = 0;
}
[[self titleLabel] setText:title];
_data = data;
if (i == 1)
{
return;
}
// 刷新分组
NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndex:0];
kWeakSelf(weakSelf);
[[self collectionView] performBatchUpdates:^{
if (i == -1)
{
[[weakSelf collectionView] deleteSections:indexSet];
}
else
{
[[weakSelf collectionView] reloadSections:indexSet];
}
} completion:^(BOOL finished) {
}];
return;
}
如果contenView不存在,那么设置data会出问题,所以我们通过[self isAddToView]去判断是否已经添加到导航栏了,如果未添加,那么我们就把data保存到内部的tempData属性中,当触发didMoveToSuperview方法时,我们再通过调用showData方法显示数据。
如果已经添加到导航栏,那就直接调用showData刷新数据。
移除控件
当不需要的时候,我们就会想把它给干掉,但事实上该控件由多个部分组成的,并不能简单的通过removeFromSuperview去删除,这样子会删除不干净。
为了简单方便的干净删除控件,我提供了invalidate方法:
- (void)invalidate
{
[self ex_removeFromSuperview];
[[self backgroundView] removeFromSuperview];
UIViewController *VC = [self VC];
[[VC navigationItem] setTitleView:nil];
[self setBlock:nil];
[self setVC:nil];
[self setBackgroundView:nil];
[self setTitleLabel:nil];
[self setImageView:nil];
return;
}
通过+ load方法实现方法交换的载入。
通过方法交换,把removeFromSuperview给交换掉,防止外界调用removeFromSuperview。
留下的坑
- 目前不支持pods集成
- 检测是否显示tabar时,目前只支持tabbarController作为rootController的情况,对于UINavigationController里的tabbarController、侧滑 + tabbarController、侧滑 + UINavigationController这3种情况暂时无法判断,如果遮罩出现到tabbar下面时,你可以设置layoutGuideBottom来解决这个问题
- 暂时不支持你自己写的cell,如果你需要自定义cell,请继承ZTMenuCell,在createView中创建view,实现selecteButton和resetButton方法,分别用来设置cell的选中状态和清除选中状态;如果不满足于此的话,修改源码吧
iOS OC Swift Flutter开发群 139322447