【iOS】3行代码轻松集成导航栏菜单--ZTNavigationMenu

背景

在做项目时需要用到筛选栏,本来想偷懒找个第三方,在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了,显示效果如下:

未展开.png

展开.png

属性与方法


如果不满足默认效果,我还提供了一些属性与方法可以自定义控件。
属性说明:

@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];                   // 透明度
模糊遮罩.png

因为这些我用的都是默认值

[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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容