转载

iOS开发造轮子 | 滚动菜单栏

原文

iOS开发造轮子 | 滚动菜单栏

封装目的:

滚动菜单栏在实际项目中十分常见,为避免重复造轮子今天来一波总结及封装。虽然这种自定义控件在网上就有很多demo,但我觉得用自己封装的心里才踏实。

iOS开发造轮子 | 滚动菜单栏

类似这种

要达到的效果:

1.按钮比较少时不用滚动,选项比较多时支持滚动。

2.点击按钮,被点按钮高亮并且调整位置。

3.按钮点击事件会有相应的回调接口暴露出去。

4.除了手动点击选定按钮,也可以通过代码选定某个按钮,以提供与其它控件如UITableView的联动。

思路

  • 在一个scrollView上依次放按钮

  • 点击按钮,通过调整scrollView的contentOffset来达到动态调整选定按钮位置的目的

  • 点击按钮的传值回调用代理或block

  • 暴露一个接口或属性用于与其他控件的联动

详细代码

.h文件

#import @class CQScrollMenuView;
@protocol CQScrollMenuViewDelegate /**
 菜单按钮点击时回调

 @param scrollMenuView 带单view
 @param index 所点按钮的index
 */
- (void)scrollMenuView:(CQScrollMenuView *)scrollMenuView clickedButtonAtIndex:(NSInteger)index;

@end

@interface CQScrollMenuView : UIScrollView

@property (nonatomic,weak) id  menuButtonClickedDelegate;
/** 菜单标题数组 */
@property (nonatomic,strong) NSArray *titleArray;
/** 当前选择的按钮的index */
@property (nonatomic,assign) NSInteger currentButtonIndex;

@end

.m文件

#import "CQScrollMenuView.h"
#import "UIView+frameAdjust.h"

@interface CQScrollMenuView ()

@end

@implementation CQScrollMenuView{
    /** 用于记录最后创建的控件 */
    UIView *_lastView;
    /** 按钮下面的横线 */
    UIView *_lineView;
}

#pragma mark - 重写构造方法
/** 重写构造方法 */
- (instancetype)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
        self.showsHorizontalScrollIndicator = NO;
        _currentButtonIndex = 0; // 默认当前选择的按钮是第一个
    }
    return self;
}

#pragma mark - 赋值标题数组
/** 赋值标题数组 */
- (void)setTitleArray:(NSArray *)titleArray{
    _titleArray = titleArray;

    // 先将所有子控件移除
    for (UIView *subView in self.subviews) {
        [subView removeFromSuperview];
    }

    // 将lastView置空
    _lastView = nil;

    // 遍历标题数组
    [_titleArray enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        UIButton *menuButton = [[UIButton alloc]init];
        [self addSubview:menuButton];
        if (_lastView) {
            menuButton.frame = CGRectMake(_lastView.maxX + 10, 0, 100, self.height);
        }else{
            menuButton.frame = CGRectMake(0, 0, 100, self.height);
        }

        menuButton.tag = 100 + idx;
        [menuButton.titleLabel setFont:[UIFont systemFontOfSize:14]];
        [menuButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [menuButton setTitleColor:[UIColor redColor] forState:UIControlStateSelected];
        [menuButton setTitle:obj forState:UIControlStateNormal];
        [menuButton addTarget:self action:@selector(menuButtonClicked:) forControlEvents:UIControlEventTouchUpInside];

        // 宽度自适应
        [menuButton sizeToFit];
        menuButton.height = self.height;

        // 这句不能少,不然初始化时button的label的宽度为0
        [menuButton layoutIfNeeded];

        // 默认第一个按钮时选中状态
        if (idx == 0) {
            menuButton.selected = YES;
            _lineView = [[UIView alloc]init];
            [self addSubview:_lineView];
            _lineView.bounds = CGRectMake(0, 0, menuButton.titleLabel.width, 2);
            _lineView.center = CGPointMake(menuButton.centerX, self.height - 1);
            _lineView.backgroundColor = [UIColor redColor];
        }

        _lastView = menuButton;
    }];

    self.contentSize = CGSizeMake(CGRectGetMaxX(_lastView.frame), CGRectGetHeight(self.frame));

    // 如果内容总宽度不超过本身,平分各个模块
    if (_lastView.maxX < self.width) {
        int i = 0;
        for (UIButton *button in self.subviews) {
            if ([button isMemberOfClass:[UIButton class]]) {
                button.width = self.width / _titleArray.count;
                button.x = i * button.width;
                button.titleLabel.adjustsFontSizeToFitWidth = YES; // 开启,防极端情况
                if (i == 0) {
                    _lineView.width = button.titleLabel.width;
                    _lineView.centerX = button.centerX;
                    _lineView.maxY = self.height;
                }
                i ++;
            }
        }
    }
}

#pragma mark - 菜单按钮点击
/** 菜单按钮点击 */
- (void)menuButtonClicked:(UIButton *)sender{
    // 改变按钮的选中状态
    for (UIButton *button in self.subviews) {
        if ([button isMemberOfClass:[UIButton class]]) {
            button.selected = NO;
        }
    }
    sender.selected = YES;

    // 将所点击的button移到中间
    if (_lastView.maxX > self.width) {
        if (sender.x >= self.width / 2 && sender.centerX <= self.contentSize.width - self.width/2) {
            [UIView animateWithDuration:0.3 animations:^{
                self.contentOffset = CGPointMake(sender.centerX - self.width / 2, 0);
            }];
        }else if (sender.frame.origin.x < self.width / 2){
            [UIView animateWithDuration:0.3 animations:^{
                self.contentOffset = CGPointMake(0, 0);
            }];
        }else{
            [UIView animateWithDuration:0.3 animations:^{
                self.contentOffset = CGPointMake(self.contentSize.width - self.width, 0);
            }];
        }
    }

    // 改变下横线的位置和宽度
    [UIView animateWithDuration:0.3 animations:^{
        _lineView.width = sender.titleLabel.width;
        _lineView.centerX = sender.centerX;
    }];

    // 代理方执行方法
    if ([self.menuButtonClickedDelegate respondsToSelector:@selector(scrollMenuView:clickedButtonAtIndex:)]) {
        [self.menuButtonClickedDelegate scrollMenuView:self clickedButtonAtIndex:(sender.tag - 100)];
    }
}

#pragma mark - 赋值当前选择的按钮
/** 赋值当前选择的按钮 */
- (void)setCurrentButtonIndex:(NSInteger)currentButtonIndex{
    _currentButtonIndex = currentButtonIndex;

    // 改变按钮的选中状态
    UIButton *currentButton = [self viewWithTag:(100 + currentButtonIndex)];
    for (UIButton *button in self.subviews) {
        if ([button isMemberOfClass:[UIButton class]]) {
            button.selected = NO;
        }
    }
    currentButton.selected = YES;

    // 将所点击的button移到中间
    if (_lastView.maxX > self.width) {
        if (currentButton.x >= self.width / 2 && currentButton.centerX <= self.contentSize.width - self.width/2) {
            [UIView animateWithDuration:0.3 animations:^{
                self.contentOffset = CGPointMake(currentButton.centerX - self.width / 2, 0);
            }];
        }else if (currentButton.x < self.width / 2){
            [UIView animateWithDuration:0.3 animations:^{
                self.contentOffset = CGPointMake(0, 0);
            }];
        }else{
            [UIView animateWithDuration:0.3 animations:^{
                self.contentOffset = CGPointMake(self.contentSize.width - self.width, 0);
            }];
        }
    }
1
    // 改变下横线的宽度和位置
    [UIView animateWithDuration:0.3 animations:^{
        _lineView.width = currentButton.titleLabel.width;
        _lineView.centerX = currentButton.centerX;
    }];
}

@end

使用方法

初始化

    self.menuView = [[CQScrollMenuView alloc]initWithFrame:CGRectMake(0, 20, self.view.width, 30)];
    [self.view addSubview:self.menuView];
    self.menuView.menuButtonClickedDelegate = self;
    self.menuView.titleArray = @[@"button0",@"button加长加长版",@"button2",@"button3",@"button4",@"button5",@"button6",@"button7",@"button8",@"button9",@"button10",@"button11"];

代理方法

#pragma mark - Delegate - 菜单栏
// 菜单按钮点击时回调
- (void)scrollMenuView:(CQScrollMenuView *)scrollMenuView clickedButtonAtIndex:(NSInteger)index{
    // tableView滚动到对应组
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:index];
    [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
}

将指定按钮设为选中状态

// UITableView的组头将要展示时回调
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section{
    // 联动
    self.menuView.currentButtonIndex = section;
}

注意事项

button初始化赋值title后需调用layoutIfNeeded方法才能获取到button的label宽度。

总结

自己动手封装过后将来遇到这种类似的控件基本都可以在这个基础上稍加修改即可,自己封装还有个好处就是:看自己的代码不费力,要想做出修改绝对比修改别人的代码来得快。还有一点最重要的,封装自定义控件真的可以有效提高自己的编码水平。

一个菜单栏和tableView联动的小demo

这里是demo

正文到此结束
Loading...