iOS - 仿美团下拉列表

很早之前就打算写个仿美团的下拉列表,但因为最近一直在忙其他的事情所以一直没有着手做,上周还算有时间,于是抽了一天的时间简单封装了下,当然到目前为止实现的功能还只是只能显示一组下拉列表,如果以后有时间,这个demo我会继续更新和维护,到时候会放在这个博客里边,笔者写这个demo主要是自己学习之用,当然能够帮到对这方面有需求的用户那更好。先看下效果图:

仿美团下拉列表gif效果图
仿美团下拉列表gif效果图

思路分析

对于项目中特别是我们在开发中之前没有遇到的需求或者问题,首先我们应该通过对需求的分析而总结出实现的思路,其实不管对于iOS开发,只要有了实现思路,代码就不是什么问题了。其实对于美团的下拉列表,通过观察可以发现当对商品列表的TableView做向上滑动时它的下拉列表的菜单是有悬停效果的,而当对TableView不断向下拉时它的下拉列表的菜单又会随着TableView向下滑动,而我们知道TableView的组头视图是有这个效果的,所以如果没有什么特别需求,我们完全可以将要封装的下拉列表菜单当做TableView的第一组的组头视图。再看TableView的下拉菜单按钮:

下拉列表菜单

每点击一下就要改变标题颜色,而且文字右边那个朝下的三角形也要换成对应颜色的朝上的箭头,同时还要弹出对应的下拉列表。但这里我们就应该想,要实现这样的按钮效果,可能需要自己的封装,毕竟系统的UIButton是没有这样的效果的,同时我们可以解压下美团官方的APP报,如果能找找到对应的图片那么我们的思路就八九不离十了(经过笔者查看,官方包里边确实有对应的两个三角形图片),我们看到美团的下拉出来的TableView在切换不同的列表的时候,如果拉出来的TableView高度不同,列表会有个缓慢的改变高度的过程,那么我们就需要每次切换数据源时让TableView执行一次reloadData的操作。

代码实现

封装菜单按钮

我们创建MenuButton类,让其继承自UIView。(笔者本来想让其继承自UIButton的,但是想到UIButton里边已经有UILabelUIImageView控件了,为了本着简洁高效的原则,所以就没有继承自UIButton

.h里边:

/** 当按钮标题改变时,要触发`setTitle:`这个方法 */
@property (nonatomic, copy) NSString *title;

/**
 *  选中某个按钮时回调方法
 *
 *  @param button    用来标记选中的按钮
 *  @param index     用来标记`选中了第几个按钮`
 *  @param selected  用来标记`这个按钮选中状态`
 */
@property (nonatomic, copy) void (^clickMenuButton) (MenuButton *button, NSString *title, BOOL selected);

/**
 *  按钮初始化方法
 *
 *  @param frame    按钮frame
 *  @param title    按钮标题
 *  @param defImage 按钮上的默认图片
 *  @param selImage 按钮选中时的图片
 */
- (instancetype)initWithFrame:(CGRect)frame
                        title:(NSString *)title
                     defImage:(UIImage *)defImage
                     selImage:(UIImage *)selImage

经过我们的分析我只,我们需要两个控件一个UILabel用来显示按钮的标题,一个UIImageView用来设置标题右边那个三角形图片,在.m里边:

@interface MenuButton ()
{
    NSString *_title;  // 按钮标题
    UIImage *_defImg;  // 按钮没有选中时右边图片
    UIImage *_selImg;  // 按钮选中时右边图片
}
/** 用来设置按钮标题 */
@property (nonatomic, strong) UILabel *titLabel;  
/** 用来设置图片 */
@property (nonatomic, strong) UIImageView *imgView;
@end

定义按钮初始化方法:

- (instancetype)initWithFrame:(CGRect)frame
                        title:(NSString *)title
                     defImage:(UIImage *)defImage
                     selImage:(UIImage *)selImage
{
    self = [super initWithFrame:frame];
    if (self) {
        _title = title;
        _defImg = defImage;
        _selImg = selImage;
        [self setupSubViews];
    }
    return self;
}

- (void)setupSubViews
{
    self.selected = NO;

    [self addSubview:self.titLabel];
    [self addSubview:self.imgView];
    // self.backgroundColor = [UIColor colorWithWhite:0.960 alpha:1.000];

    // 给视图添加手势,当我们点击视图是触发`menuButtonClicked`方法
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(menuButtonClicked)];
    [self addGestureRecognizer:tap];
}

#pragma mark - 手势点击事件
- (void)menuButtonClicked
{
    _selected = ! _selected;

    if (_selected) {
        self.titLabel.textColor = kSelTitleCor;
        self.imgView.image = _selImg;
    }
    else {
        self.titLabel.textColor = kDefTitleCor;
        self.imgView.image = _defImg;
    }

    if (self.clickMenuButton) {
        self.clickMenuButton(self, self.titLabel.text,_selected);
    }
}

当按钮的标题改变时,我们要实现视图的setTitle:方法:

- (void)setTitle:(NSString *)title
{
    self.titLabel.text = title;
    UIFont *font = [UIFont systemFontOfSize:kFontSize];
    _titLabel.font = font;
    
    // 为了不至于让标题和图片由于位置变化而太难看,我们需要在按钮标题每次改变时重新设置它的frame
    CGSize size = [title sizeWithFont:font maxSize:CGSizeMake(self.w, self.h)];
    _titLabel.center = CGPointMake(self.w/2 - kTriangleWH/2, self.h/2);
    _titLabel.bounds = CGRectMake(0, 0, size.width, size.height);
    _imgView.frame = CGRectMake(CGRectGetMaxX(self.titLabel.frame), (self.h-kTriangleWH)/2, kTriangleWH, kTriangleWH);
    [self setNeedsLayout];
    [self setIsSeled:NO];
}

封装下拉菜单

我们创建MenuButton类,让其继承自UIView,在.h里边:

/**
 *  数据源
 */
@property (nonatomic, strong) NSArray *dataSource;

/**
 *  选中某个按钮时回调方法(这个回调其实可以不写)
 *
 *  @param button    用来标记选中的按钮
 *  @param index     用来标记`选中了第几个按钮`
 *  @param selected  用来标记`这个按钮选中状态`
 */
@property (nonatomic, copy) void (^clickMenuButton) (MenuButton *button, NSInteger index, BOOL selected);


 /**
 *  选中下拉列表某行的回调方法
 *
 *  @param index  用来标记`选中了第几行`
 *  @param title  用来标记`这个这一行的标题`
 */
@property (nonatomic, copy) void (^clickListView) ( NSInteger index, NSString *title);


/**
 *  初始化菜单视图
 *
 *  @param frame    菜单视图frame(推荐x:0.f y:自定义 width:屏幕宽度 height:>=25)
 *  @param titles   要显示的按钮标题数组
 *  @param defImage 按钮没有点击之前右边那个小图片
 *  @param selImage 按钮点击之后右边那个小图片
 *
 *  @return 菜单视图
 */
- (instancetype)initWithFrame:(CGRect)frame
                   Titles:(NSArray <NSString *>*)titles
                 defImage:(UIImage *)defImage
                 selImage:(UIImage *)selImage;

.m里边我们初始化界面:

- (instancetype)initWithFrame:(CGRect)frame Titles:(NSArray<NSString *> *)titles defImage:(UIImage *)defImage selImage:(UIImage *)selImage
{
    self = [super initWithFrame:frame];
    if (self) {
        [self subViewsWithTitles:titles defImage:defImage selImage:selImage];
    }
    return self;
}

- (void)subViewsWithTitles:(NSArray *)titles defImage:(UIImage *)defImage selImage:(UIImage *)selImage
{
    self.backgroundColor = [UIColor whiteColor];

    // 上边的分割线
    CGRect topLineFrame = CGRectMake(0, 1.f, kS_W, 0.5f);
    UIView *topLine = [[UIView alloc] initWithFrame:topLineFrame];
    topLine.backgroundColor = kLineCor;
    [self addSubview:topLine];

    // 下边的分割线
    CGRect bottomLineFrame = CGRectMake(0, self.h-1.f, kS_W, 0.5f);
    UIView *bottomLine = [[UIView alloc] initWithFrame:bottomLineFrame];
    bottomLine.backgroundColor = kLineCor;
    [self addSubview:bottomLine];

    NSInteger count = [titles count];
    for (int i=0; i<count; i++) {
    // 创建按钮
        CGFloat buttonW = (self.w - (count-1)*kLineW)/count;
        CGFloat buttonH = 40.f;
        CGFloat buttonX = (buttonW+kLineW) * i;
        CGRect btnFrame = CGRectMake(buttonX, 0.f, buttonW, buttonH);
        MenuButton *button = [[MenuButton alloc] initWithFrame:btnFrame title:titles[i] defImage:defImage selImage:selImage];
        [self addSubview:button];
    
    
        __weak typeof(self)weakSelf = self;
        button.clickMenuButton = ^(MenuButton *button, NSString *title, BOOL selected){
            if (weakSelf.clickMenuButton) {
                weakSelf.clickMenuButton( button,i, selected);
            }
        
            if (!_button) {
                _button = button;
            }
            if (button != _button) {
                [_button resetStatus:_button];
            }
            else {
            }
        
            _currenTitle = title;
            // ------------------------
    
            _button = button;
            if (selected) {
                [self showListViewAnimation];
            }
            else {
                [self hideListViewAnimation];
            }
        
            // ----------------------
        
        };
    
        // 按钮之间的竖直分割线
        if (i < count-1) {
            CGFloat lineX = buttonX + buttonW;
            CGFloat lineY = (self.h-kLineH)/2;
            CGRect lineFrame = CGRectMake(lineX, lineY, kLineW, kLineH);
            UIView *line = [[UIView alloc] initWithFrame:lineFrame];
            line.backgroundColor = kLineCor;
            [self addSubview:line];
        }
    }
}

// 一定要重写这个方法,在这里`reloadData`,这样当我们点击不同的按钮时TableView高度将会发生变化
- (void)setDataSource:(NSArray *)dataSource
{
    _dataSource = dataSource;
    [self.lTableView reloadData];
}

// 更具数据源里边数据情况,返回TableView高度
- (CGFloat)maxListHeightWithModel:(NSArray *)dataSource
{
    NSInteger count = dataSource.count;
    CGFloat height = 0.f;
    CGFloat oriHeight = kRowH*count;
    oriHeight > kS_H/3*2 ? (height = kS_H/3*2) : (height = kRowH*count);

    return height;
}

#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.dataSource.count;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return kRowH;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    ListCell *cell = [ListCell cellWithTableView:tableView];
    cell.title = self.dataSource[indexPath.row];
    cell.selected = [self.dataSource[indexPath.row] isEqualToString:_currenTitle];
    return cell;
}

#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 当点击下拉菜单某一行时移除下菜单和遮罩层
    [UIView animateWithDuration:0.2f animations:^{
        self.shadow.alpha = 0.f;
        self.lTableView.h = 0.f;
    } completion:^(BOOL finished) {
        [self.shadow removeFromSuperview];
        [self.lTableView removeFromSuperview];
    }];

    _button.isSeled = NO;
    _button.title = self.dataSource[indexPath.row];

    // 当点击下拉菜单某一行时重置按钮状态
    [_button resetStatus:_button];

    // 实现回调方法
    if (self.clickListView) {
        self.clickListView(indexPath.row, self.dataSource[indexPath.row]);
    }
}

到这里几个主要的方法差不多了,由于现在只是实现了单列下拉列表的情况,所以代码还是比较简单的。

我们在ViewController里边测试下代码效果:

// 在TableView的这个方法里边我们返回下拉菜单视图
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    // 推荐将`MenuListView`设置为tableView的第一组的组头视图

    UIImage *defImg = [UIImage imageNamed:@"gc_navi_arrow_down"];
    UIImage *selImg = [UIImage imageNamed:@"gc_navi_arrow_up"];
    CGRect frame = CGRectMake(0.f, 0.f, kS_W, 40.f);
    NSArray *titles = @[@"自助餐", @"附近", @"智能排序", @"筛选"];
    MenuListView *menu = [[MenuListView alloc] initWithFrame:frame Titles:titles defImage:defImg selImage:selImg];

    __weak typeof (menu)weakMenu = menu;
    menu.clickMenuButton = ^(MenuButton *button, NSInteger index, BOOL selected){
    // NSLog(@"点击了第 %ld 个按钮,选中还是取消?:%d", index, selected);
    if (index == 0) {
        weakMenu.dataSource = @[@"自助餐",@"火锅",@"海鲜",@"烧烤啤酒",@"甜点饮食",@"生日蛋糕",@"小吃快餐",@"日韩料理",@"西餐",@"聚餐宴请",@"川菜",@"江浙菜",@"香锅烤鱼",@"粤菜",@"中式烧烤/烤串",@"西北菜",@"咖啡酒吧",@"京菜鲁菜",@"湘菜",@"生鲜蔬果",@"东北菜",@"云贵菜",@"东南亚菜",@"素食",@"创意菜",@"躺/粥/炖菜",@"新疆菜",@"其他美食"];
    }
    else if (index == 1) {
        weakMenu.dataSource = @[@"附近",@"新津县",@"都江堰",@"温江区",@"郫县",@"龙泉驿区",@"锦江区",@"金牛区",@"成华区",@"青羊区",@"武侯区"];
    }
    else if (index == 2) {
        weakMenu.dataSource = @[@"智能排序", @"离我最近", @"评价最高", @"最新发布", @"人气最高", @"价格最低", @"价格最高"];
    }
    else if (index == 3) {
        weakMenu.dataSource = @[@"只看免预约",@"节假日可用",@"用餐时间段",@"用餐人数",@"餐厅地点"];
    }
};

    // 选中下拉列表某行时的回调(这个回调方法请务必实现!)
    menu.clickListView = ^(NSInteger index, NSString *title){
        NSLog(@"选中了-> %d   标题-> %@", index, title);
    };

    return menu;
}

运行结果:

写在后面

目前这个demo实现的功能还比较简单,如果以后有时间我会继续更新。

Demo地址

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,618评论 4 59
  • Swift版本点击这里欢迎加入QQ群交流: 594119878最新更新日期:18-09-17 About A cu...
    ylgwhyh阅读 24,865评论 7 249
  • 我决定了,每天就写这么一点点话,不管是开心的还是高兴的,不更新,只是思念。
    晒谷场阅读 192评论 0 0
  • 因为同事孩子的原因,同事告诉我想让我替他上个白班,虽然心里不太情愿,但还是答应下来了,帮个忙而已嘛。 考虑到明天要...
    似笑非笑阅读 270评论 0 0
  • 早上好 最近突然想明白一件事,我的心里驻着一个单蠢天真,乐善好施,时而没心没肺,时而多愁善感,时而机敏聪辩,时而木...
    彼岸花_81d9阅读 278评论 0 0