iOS - 仿美團下拉列表

很早之前就打算寫個仿美團的下拉列表,但因為最近一直在忙其他的事情所以一直沒有著手做,上周還算有時間,于是抽了一天的時間簡單封裝了下,當(dāng)然到目前為止實現(xiàn)的功能還只是只能顯示一組下拉列表,如果以后有時間,這個demo我會繼續(xù)更新和維護,到時候會放在這個博客里邊,筆者寫這個demo主要是自己學(xué)習(xí)之用,當(dāng)然能夠幫到對這方面有需求的用戶那更好。先看下效果圖:

仿美團下拉列表gif效果圖
仿美團下拉列表gif效果圖

思路分析

對于項目中特別是我們在開發(fā)中之前沒有遇到的需求或者問題,首先我們應(yīng)該通過對需求的分析而總結(jié)出實現(xiàn)的思路,其實不管對于iOS開發(fā),只要有了實現(xiàn)思路,代碼就不是什么問題了。其實對于美團的下拉列表,通過觀察可以發(fā)現(xiàn)當(dāng)對商品列表的TableView做向上滑動時它的下拉列表的菜單是有懸停效果的,而當(dāng)對TableView不斷向下拉時它的下拉列表的菜單又會隨著TableView向下滑動,而我們知道TableView的組頭視圖是有這個效果的,所以如果沒有什么特別需求,我們完全可以將要封裝的下拉列表菜單當(dāng)做TableView的第一組的組頭視圖。再看TableView的下拉菜單按鈕:

下拉列表菜單

每點擊一下就要改變標(biāo)題顏色,而且文字右邊那個朝下的三角形也要換成對應(yīng)顏色的朝上的箭頭,同時還要彈出對應(yīng)的下拉列表。但這里我們就應(yīng)該想,要實現(xiàn)這樣的按鈕效果,可能需要自己的封裝,畢竟系統(tǒng)的UIButton是沒有這樣的效果的,同時我們可以解壓下美團官方的APP報,如果能找找到對應(yīng)的圖片那么我們的思路就八九不離十了(經(jīng)過筆者查看,官方包里邊確實有對應(yīng)的兩個三角形圖片),我們看到美團的下拉出來的TableView在切換不同的列表的時候,如果拉出來的TableView高度不同,列表會有個緩慢的改變高度的過程,那么我們就需要每次切換數(shù)據(jù)源時讓TableView執(zhí)行一次reloadData的操作。

代碼實現(xiàn)

封裝菜單按鈕

我們創(chuàng)建MenuButton類,讓其繼承自UIView。(筆者本來想讓其繼承自UIButton的,但是想到UIButton里邊已經(jīng)有UILabelUIImageView控件了,為了本著簡潔高效的原則,所以就沒有繼承自UIButton

.h里邊:

/** 當(dāng)按鈕標(biāo)題改變時,要觸發(fā)`setTitle:`這個方法 */
@property (nonatomic, copy) NSString *title;

/**
 *  選中某個按鈕時回調(diào)方法
 *
 *  @param button    用來標(biāo)記選中的按鈕
 *  @param index     用來標(biāo)記`選中了第幾個按鈕`
 *  @param selected  用來標(biāo)記`這個按鈕選中狀態(tài)`
 */
@property (nonatomic, copy) void (^clickMenuButton) (MenuButton *button, NSString *title, BOOL selected);

/**
 *  按鈕初始化方法
 *
 *  @param frame    按鈕frame
 *  @param title    按鈕標(biāo)題
 *  @param defImage 按鈕上的默認(rèn)圖片
 *  @param selImage 按鈕選中時的圖片
 */
- (instancetype)initWithFrame:(CGRect)frame
                        title:(NSString *)title
                     defImage:(UIImage *)defImage
                     selImage:(UIImage *)selImage

經(jīng)過我們的分析我只,我們需要兩個控件一個UILabel用來顯示按鈕的標(biāo)題,一個UIImageView用來設(shè)置標(biāo)題右邊那個三角形圖片,在.m里邊:

@interface MenuButton ()
{
    NSString *_title;  // 按鈕標(biāo)題
    UIImage *_defImg;  // 按鈕沒有選中時右邊圖片
    UIImage *_selImg;  // 按鈕選中時右邊圖片
}
/** 用來設(shè)置按鈕標(biāo)題 */
@property (nonatomic, strong) UILabel *titLabel;  
/** 用來設(shè)置圖片 */
@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];

    // 給視圖添加手勢,當(dāng)我們點擊視圖是觸發(fā)`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);
    }
}

當(dāng)按鈕的標(biāo)題改變時,我們要實現(xiàn)視圖的setTitle:方法:

- (void)setTitle:(NSString *)title
{
    self.titLabel.text = title;
    UIFont *font = [UIFont systemFontOfSize:kFontSize];
    _titLabel.font = font;
    
    // 為了不至于讓標(biāo)題和圖片由于位置變化而太難看,我們需要在按鈕標(biāo)題每次改變時重新設(shè)置它的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];
}

封裝下拉菜單

我們創(chuàng)建MenuButton類,讓其繼承自UIView,在.h里邊:

/**
 *  數(shù)據(jù)源
 */
@property (nonatomic, strong) NSArray *dataSource;

/**
 *  選中某個按鈕時回調(diào)方法(這個回調(diào)其實可以不寫)
 *
 *  @param button    用來標(biāo)記選中的按鈕
 *  @param index     用來標(biāo)記`選中了第幾個按鈕`
 *  @param selected  用來標(biāo)記`這個按鈕選中狀態(tài)`
 */
@property (nonatomic, copy) void (^clickMenuButton) (MenuButton *button, NSInteger index, BOOL selected);


 /**
 *  選中下拉列表某行的回調(diào)方法
 *
 *  @param index  用來標(biāo)記`選中了第幾行`
 *  @param title  用來標(biāo)記`這個這一行的標(biāo)題`
 */
@property (nonatomic, copy) void (^clickListView) ( NSInteger index, NSString *title);


/**
 *  初始化菜單視圖
 *
 *  @param frame    菜單視圖frame(推薦x:0.f y:自定義 width:屏幕寬度 height:>=25)
 *  @param titles   要顯示的按鈕標(biāo)題數(shù)組
 *  @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++) {
    // 創(chuàng)建按鈕
        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`,這樣當(dāng)我們點擊不同的按鈕時TableView高度將會發(fā)生變化
- (void)setDataSource:(NSArray *)dataSource
{
    _dataSource = dataSource;
    [self.lTableView reloadData];
}

// 更具數(shù)據(jù)源里邊數(shù)據(jù)情況,返回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
{
    // 當(dāng)點擊下拉菜單某一行時移除下菜單和遮罩層
    [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];

    // 當(dāng)點擊下拉菜單某一行時重置按鈕狀態(tài)
    [_button resetStatus:_button];

    // 實現(xiàn)回調(diào)方法
    if (self.clickListView) {
        self.clickListView(indexPath.row, self.dataSource[indexPath.row]);
    }
}

到這里幾個主要的方法差不多了,由于現(xiàn)在只是實現(xiàn)了單列下拉列表的情況,所以代碼還是比較簡單的。

我們在ViewController里邊測試下代碼效果:

// 在TableView的這個方法里邊我們返回下拉菜單視圖
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    // 推薦將`MenuListView`設(shè)置為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 = @[@"自助餐",@"火鍋",@"海鮮",@"燒烤啤酒",@"甜點飲食",@"生日蛋糕",@"小吃快餐",@"日韓料理",@"西餐",@"聚餐宴請",@"川菜",@"江浙菜",@"香鍋烤魚",@"粵菜",@"中式燒烤/烤串",@"西北菜",@"咖啡酒吧",@"京菜魯菜",@"湘菜",@"生鮮蔬果",@"東北菜",@"云貴菜",@"東南亞菜",@"素食",@"創(chuàng)意菜",@"躺/粥/燉菜",@"新疆菜",@"其他美食"];
    }
    else if (index == 1) {
        weakMenu.dataSource = @[@"附近",@"新津縣",@"都江堰",@"溫江區(qū)",@"郫縣",@"龍泉驛區(qū)",@"錦江區(qū)",@"金牛區(qū)",@"成華區(qū)",@"青羊區(qū)",@"武侯區(qū)"];
    }
    else if (index == 2) {
        weakMenu.dataSource = @[@"智能排序", @"離我最近", @"評價最高", @"最新發(fā)布", @"人氣最高", @"價格最低", @"價格最高"];
    }
    else if (index == 3) {
        weakMenu.dataSource = @[@"只看免預(yù)約",@"節(jié)假日可用",@"用餐時間段",@"用餐人數(shù)",@"餐廳地點"];
    }
};

    // 選中下拉列表某行時的回調(diào)(這個回調(diào)方法請務(wù)必實現(xiàn)!)
    menu.clickListView = ^(NSInteger index, NSString *title){
        NSLog(@"選中了-> %d   標(biāo)題-> %@", index, title);
    };

    return menu;
}

運行結(jié)果:

寫在后面

目前這個demo實現(xiàn)的功能還比較簡單,如果以后有時間我會繼續(xù)更新。

Demo地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,257評論 4 61
  • Swift版本點擊這里歡迎加入QQ群交流: 594119878最新更新日期:18-09-17 About A cu...
    ylgwhyh閱讀 25,573評論 7 249
  • 我決定了,每天就寫這么一點點話,不管是開心的還是高興的,不更新,只是思念。
    曬谷場閱讀 229評論 0 0
  • 因為同事孩子的原因,同事告訴我想讓我替他上個白班,雖然心里不太情愿,但還是答應(yīng)下來了,幫個忙而已嘛。 考慮到明天要...
    似笑非笑閱讀 288評論 0 0
  • 早上好 最近突然想明白一件事,我的心里駐著一個單蠢天真,樂善好施,時而沒心沒肺,時而多愁善感,時而機敏聰辯,時而木...
    彼岸花_81d9閱讀 404評論 0 0