iOS 一个滑动选择控件

作者: QiShare | 来源:发表于2019-03-18 18:19 被阅读87次

    级别: ★★☆☆☆
    标签:「iOS」「PagesContainer」「控件」
    作者: dac_1033
    审校: QiShare团队

    我们在移动项目开发中经常会用到滑动选择功能,例如在“米家”App的首页中,为了展示账户下所有智能设备,或者按类型、者房间展示部分相关设备,界面上做了如下设计:

    米家App首页滑动选择

    下面我们就来介绍一下这个滑动选择控件的实现过程。

    1. 需求分析

    上面动图中展示的界面并不复杂,我们可以看的出来,这个滑动选择功能主要分为两个部分:顶部滑动选择bar和底部可手动滑动的scrollView,并且这上下两部分可以联动。(底部scrollView中的子view)


    滑动选择界面的布局

    需求归纳如下:
    (1)顶部为滑动可选topBar;
    (2)topBar可手势左右滑动;
    (3)topBar中的可选项数量可扩展;
    (4)topBar中的选中项自动滚动至屏幕可见区域;
    (5)topBar中的每一项可点击选择,选中项样式有变化;
    (6)topBar中的选中项底部有个游标cursor,cursor的宽度可自适应;
    (7)下部为可左右滑动scrollView;
    (8)scrollView根据当前topBar选中项分屏展示,即topBar与scrollView联动;

    2. 控件的框架层次

    我们最终要将顶部topBar和下部的scrollView作为一个整体控件使用,因此最外层为pageContainer(继承于UIView)将二者封装起来,整个控件的初始化、设置方法都在pageContainer中。则整个控件的框架很简单,如下:

    滑动选择控件的框架

    3. 具体实现

    本问的实现的代码是由部门中的前辈封装,根据项目需求我们自己可以做一些相应的改动,首先谢谢那些前辈写出一个这么简单易用的控件,并提供给我们学习的机会。下面我们根据控件的框架层次结构,从下往上依次说明实现过程。

    3.1 TopBarButton

    本控件中的TopBarButton继承于UIButton,是控件顶部滑动栏中的一个item,重写UIButton的setSelected:方法就可以设置TopBarButton在选中时的状态。如果你的项目中对TopBarButton还有其他样式要求,可在该类中添加其他元素,并添加相应初始化方法。

    //////// .h文件
    @interface QiTopBarButton : UIButton
    
    @end
    
    
    //////// .m文件
    #import "QiTopBarButton.h"
    
    #define BADGE_WIDTH (13)
    #define TitleFontSize (13)
    #define ColorNormalDefault [UIColor blackColor]
    #define ColorSelectedDefault [UIColor redColor];
    
    @interface QiTopBarButton()
    
    @end
    
    @implementation QiTopBarButton
    
    - (instancetype)init {
        
        self = [super init];
        if (self) {
            _colorNormal = ColorNormalDefault;
            _colorSelected = ColorSelectedDefault;
            [self setTitleColor:_colorSelected forState:UIControlStateSelected];
            [self setTitleColor:_colorNormal forState:UIControlStateNormal];
            
            [self.titleLabel setFont:[UIFont systemFontOfSize:TitleFontSize]];
        }
        return self;
    }
    
    - (void)setSelected:(BOOL)selected {
        
        [super setSelected:selected];
        if (selected) {
            [self setTitleColor:_colorSelected forState:UIControlStateNormal];
            UIFont *font = [UIFont systemFontOfSize:TitleFontSize + 1 weight:UIFontWeightBold];
            [self.titleLabel setFont:font];
        } else {
            [self setTitleColor:_colorNormal forState:UIControlStateNormal];
            UIFont *font = [UIFont systemFontOfSize:TitleFontSize weight:normal];
            [self.titleLabel setFont:font];
        }
    }
    
    @end
    
    3.2 PagesContainerTopBar

    本控件中的PagesContainerTopBar继承于UIView,其中的子view主要包括scrollView、一组topBarButton、cursor。

    //////// .h文件
    #import <UIKit/UIKit.h>
    
    @protocol QiPagesContainerTopBarDelegate;
    
    @interface QiPagesContainerTopBar : UIView
    
    @property (nonatomic, weak) id<QiPagesContainerTopBarDelegate> target;
    
    // 设置相邻button之间的间距(两个button的title之间的距离)
    @property (nonatomic, assign) CGFloat buttonMargin;
    
    #pragma mark - update style
    
    - (void)setBackgroundImage:(UIImage *)image;
    
    - (void)setBackgroundImageHidden:(BOOL)isHidden;
    
    - (void)setCursorPosition:(CGFloat)percent;
    
    - (void)updateConentWithTitles:(NSArray *)titles;
    
    - (void)setIsButtonAlignmentLeft:(BOOL)isAlignmentLeft;
    
    - (void)setShowSeperateLines:(BOOL)showSeperateLines;
    
    - (void)setSelectedIndex:(NSInteger)idnex;
    
    - (NSInteger)getSelectedIndex;
    
    // 设置滑块的颜色
    - (void)setCursorColor:(UIColor *)color;
    // 设置滑块长度
    - (void)setCursorWidth:(CGFloat)width;
    // 设置滑块长度
    - (void)setCursorHeight:(CGFloat)height;
    // 设置按钮选中和未选中的颜色
    - (void)setTextColor:(UIColor *)normalColor andSelectedColor:(UIColor *)selectedColor;
    
    @end
    
    
    @protocol QiPagesContainerTopBarDelegate <NSObject>
    
    @optional
    // 选中topBar的一项
    - (void)topBarSelectIndex:(NSInteger)index;
    
    // 重复点击topBar时会调用该方法
    - (void)topBarSelectIndicator:(NSInteger)index;
    
    @end
    
    
    //////// .m文件
    #import "QiPagesContainerTopBar.h"
    #import "QiTopBarButton.h"
    
    //左右侧的间距
    #define MARGIN_HORI (0)
    #define CursorHeightDefault (1.5)
    #define BUTTON_MARGIN_DEFAULT   (20)
    
    @interface QiPagesContainerTopBar ()
    
    @property (nonatomic, strong) UIImageView *backgroundImageView;
    @property (nonatomic, strong) UIScrollView *scrollView;
    
    @property (nonatomic, strong) UIView *cursor;
    @property (nonatomic, assign) CGFloat cursorWidth;
    @property (nonatomic, assign) CGFloat cursorHeight;
    @property (nonatomic, strong) NSArray *arrayButtons;
    @property (nonatomic, assign) BOOL isButtonAlignmentLeft;
    @property (nonatomic, strong) NSArray *arraySeperateLines;
    @property (nonatomic, assign) BOOL showSeperateLines;
    
    @end
    
    @implementation QiPagesContainerTopBar
    
    - (id)initWithFrame:(CGRect)frame {
        
        self = [super initWithFrame:frame];
        if (self) {
            [self setupViews];
        }
        return self;
    }
    
    - (void)setupViews {
        
        _buttonMargin = BUTTON_MARGIN_DEFAULT;
        _cursorHeight = CursorHeightDefault;
        
        _backgroundImageView = [[UIImageView alloc] initWithFrame:self.bounds];
        [self addSubview:_backgroundImageView];
        
        _scrollView = [[UIScrollView alloc] init];
        _scrollView.showsHorizontalScrollIndicator = NO;
        _scrollView.showsVerticalScrollIndicator = NO;
        _scrollView.bounces = NO;
        [self addSubview:_scrollView];
        
        _cursor = [[UIView alloc] initWithFrame:CGRectZero];
        _cursor.backgroundColor = [UIColor redColor];
        _cursor.layer.cornerRadius = _cursorHeight / 2.0;
        [_scrollView addSubview:_cursor];
    }
    
    #pragma mark - 设置各个控件的位置
    - (void)layoutSubviews {
        
        [super layoutSubviews];
        
        CGSize size = self.frame.size;
        _backgroundImageView.frame = CGRectMake(0, 0, size.width, size.height);;
        _scrollView.frame = CGRectMake(0, 0, size.width, size.height);
        if ([_arrayButtons count] == 0) {
            return;
        }
        
        // 增加按钮两侧的间距
        CGFloat contentWidth = MARGIN_HORI * 2;
        for (int i=0; i<[_arrayButtons count]; i++) {
            UIButton *button = [_arrayButtons objectAtIndex:i];
            contentWidth += button.frame.size.width;
        }
        
        // 按钮未排满整屏宽度时
        if (!_isButtonAlignmentLeft && contentWidth < size.width) {
            CGFloat buttonWidth = floorf((size.width - MARGIN_HORI * 2) / [_arrayButtons count]);
            for (UIButton *button in _arrayButtons) {
                CGRect frame = button.frame;
                frame.size.width = buttonWidth;
                button.frame = frame;
            }
        }
        
        // 设置按钮位置
        NSInteger selectedIndex = 0;
        CGFloat xOffset = MARGIN_HORI;
        CGFloat buttonHeight = size.height;
        for (int i=0; i<[_arrayButtons count]; i++) {
            UIButton *button = [_arrayButtons objectAtIndex:i];
            CGRect frame = button.frame;
            frame.origin.x = xOffset;
            frame.origin.y = 0;
            frame.size.height = buttonHeight;
            button.frame = frame;
            xOffset += frame.size.width;
            if (button.selected) {
                selectedIndex = i;
            }
        }
        
        // 设置分割线位置
        for (int i=0; i<[_arraySeperateLines count]; i++) {
            UIView *line = [_arraySeperateLines objectAtIndex:i];
            line.hidden = !_showSeperateLines;
            
            UIButton *buttonPrev = [_arrayButtons objectAtIndex:i];
            UIButton *buttonNext = [_arrayButtons objectAtIndex:i+1];
            
            CGRect frame = line.frame;
            frame.origin.x = (CGRectGetMaxX(buttonPrev.frame) + CGRectGetMinX(buttonNext.frame))/2;
            line.frame = frame;
            
        }
        
        _scrollView.contentSize = CGSizeMake(xOffset + MARGIN_HORI, size.height);
        
        // 设置游标位置
        UIButton *buttonSelected = [_arrayButtons objectAtIndex:selectedIndex];
        CGRect frame = buttonSelected.frame;
        [buttonSelected.titleLabel sizeToFit];
        CGFloat cursorWidth = _cursorWidth != 0 ? _cursorWidth : buttonSelected.titleLabel.frame.size.width;
        _cursor.frame = CGRectMake(frame.origin.x + (frame.size.width - cursorWidth) / 2, CGRectGetMaxY(frame) - _cursorHeight, cursorWidth, _cursorHeight);
    }
    
    #pragma mark - 创建各个button
    
    - (void)updateConentWithTitles:(NSArray *)titles {
        
        for (UIButton *button in _arrayButtons) {
            [button removeFromSuperview];
        }
        
        if ([titles count] == 0) {
            return;
        }
        
        NSMutableArray *tempArray = [NSMutableArray array];
        for (int i=0; i<[titles count]; i++) {
            NSString *title = [titles objectAtIndex:i];
            UIButton *button = [self createCustomButtonWithTitle:title];
            button.titleLabel.font = [UIFont systemFontOfSize:14];
            button.tag = i;
            [button sizeToFit];
            CGRect frame = button.frame;
            frame.size.width += _buttonMargin;
            button.frame = frame;
            [_scrollView addSubview:button];
            [tempArray addObject:button];
        }
        _arrayButtons = [NSArray arrayWithArray:tempArray];
        [_scrollView bringSubviewToFront:_cursor];
        [self setSelectedIndex:0];
        
        CGFloat lineTop = CGRectGetHeight(self.frame) / 5;
        CGFloat lineHeight = CGRectGetHeight(self.frame) - lineTop * 2;
        tempArray = [NSMutableArray array];
        for (int i=0; i<[_arrayButtons count]-1; i++) {
            UIView *line = [[UIView alloc] initWithFrame:CGRectMake(0, lineTop, 0.8, lineHeight)];
            line.backgroundColor = [UIColor redColor];
            [_scrollView addSubview:line];
            [tempArray addObject:line];
        }
        _arraySeperateLines = [NSArray arrayWithArray:tempArray];
    }
    
    - (UIButton *)createCustomButtonWithTitle:(NSString *)title {
        
        UIButton *button = [[QiTopBarButton alloc] init];
        [button setTitle:title forState:UIControlStateNormal];
        [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
        return button;
    }
    
    /** 点击topbar的某一项 */
    - (void)buttonClicked:(id)sender {
        
        UIButton *button = (UIButton *)sender;
        NSInteger tag = button.tag;
        
        if (button.selected) {
            if (_target && [_target respondsToSelector:@selector(topBarSelectIndicator:)]) {
                [_target topBarSelectIndicator:tag];
            }
            return;
        }
    
        [self setSelectedIndex:tag];
        if (_target && [_target respondsToSelector:@selector(topBarSelectIndex:)]) {
            [_target topBarSelectIndex:tag];
        }
    }
    
    #pragma mark 设置按钮的位置
    - (void)setIsButtonAlignmentLeft:(BOOL)isAlignmentLeft {
        
        _isButtonAlignmentLeft = isAlignmentLeft;
    }
    
    - (void)setShowSeperateLines:(BOOL)showSeperateLines {
        
        _showSeperateLines = showSeperateLines;
    }
    
    #pragma mark 更新和设置位置
    - (void)setSelectedIndex:(NSInteger)index {
        
        if (index > [_arrayButtons count]) {
            return;
        }
        
        for (int i=0; i<[_arrayButtons count]; i++) {
            UIButton *button = [_arrayButtons objectAtIndex:i];
            button.selected = (i == index);
        }
        [self updateScrollViewPosition];
    }
    
    - (NSInteger)getSelectedIndex {
        
        NSInteger selectedIndex = 0;
        for (int i=0; i<[_arrayButtons count]; i++) {
            UIButton *button = [_arrayButtons objectAtIndex:i];
            if (button.selected) {
                selectedIndex = i;
            }
        }
        return selectedIndex;
    }
    
    - (void)scrollRectToCenter:(CGRect)frame {
        
        CGSize size = self.frame.size;
        CGSize contentSize = self.scrollView.contentSize;
        
        CGFloat targetX = frame.origin.x - (size.width - frame.size.width) / 2;
        CGFloat targetEndX = targetX + size.width;
        
        if (targetX < 0) {
            targetX = 0;
        }
        if (targetEndX > contentSize.width) {
            targetEndX = contentSize.width;
        }
        CGRect targetRect = CGRectMake(targetX, 0, targetEndX - targetX, frame.size.height);
        
        [self.scrollView scrollRectToVisible:targetRect animated:YES];
    }
    
    - (void)updateScrollViewPosition {
        
        CGSize size = self.frame.size;
        CGSize contentSize = self.scrollView.contentSize;
        if (size.width >= contentSize.width) {
            return;
        }
        
        CGRect frame = CGRectZero;
        for (int i=0; i<[_arrayButtons count]; i++) {
            UIButton *button = [_arrayButtons objectAtIndex:i];
            if (button.selected) {
                frame = button.frame;
            }
        }
        
        [self scrollRectToCenter:frame];
    }
    
    - (void)setCursorPosition:(CGFloat)percent {
        
        CGFloat indexOffset = percent * [_arrayButtons count];
        NSInteger preIndex = floorf(indexOffset);
        NSInteger nextIndex = ceilf(indexOffset);
        
        if (preIndex >= 0 && nextIndex <= [_arrayButtons count]) {
            UIButton *preBtn = [_arrayButtons objectAtIndex:preIndex];
            UIButton *nextBtn = [_arrayButtons objectAtIndex:nextIndex];
            
            CGFloat cursorWidth = _cursorWidth;
            if (_cursorWidth == 0) {
                cursorWidth = preBtn.titleLabel.frame.size.width + (indexOffset - preIndex) * (nextBtn.titleLabel.frame.size.width - preBtn.titleLabel.frame.size.width);
                CGRect frame = _cursor.frame;
                frame.size.width = cursorWidth;
                _cursor.frame = frame;
            }
            
            CGFloat cursorCenterX = preBtn.center.x + (indexOffset - preIndex) * (nextBtn.center.x - preBtn.center.x);
            _cursor.center = CGPointMake(cursorCenterX , _cursor.center.y);
        }
    }
    
    - (QiTopBarButton *)getCustomButtonAtIndex:(NSInteger)index {
        
        if (index >= 0 && index < [_arrayButtons count]) {
            QiTopBarButton *button = [_arrayButtons objectAtIndex:index];
            if ([button isKindOfClass:[QiTopBarButton class]]) {
                return button;
            }
        }
        return nil;
    }
    
    - (void)setBackgroundImage:(UIImage *)image {
        
        _backgroundImageView.image = image;
    }
    
    - (void)setBackgroundImageHidden:(BOOL)isHidden {
        
        _backgroundImageView.hidden = isHidden;
    }
    
    - (void)setCursorColor:(UIColor *)color {
        
        _cursor.backgroundColor = color;
    }
    
    - (void)setCursorWidth:(CGFloat)width {
        
        _cursorWidth = width;
    }
    
    - (void)setTextColor:(UIColor *)normalColor andSelectedColor:(UIColor *)selectedColor {
        
        for (QiTopBarButton *button in _arrayButtons) {
            button.colorNormal = normalColor;
            button.colorSelected = selectedColor;
            [button setTitleColor:normalColor forState:UIControlStateNormal];
            [button setTitleColor:selectedColor forState:UIControlStateSelected];
        }
    }
    
    @end
    
    
    3.3 PagesContainer

    PagesContainer是最外层的View,用户直接调用实例化PagesContainer并调用相应方法进行设置,即可使用该滑动选择控件。PagesContainer将顶部topBar和下部scrollView封装在内,topBar的点击事件与scrollView的滑动事件均传递到scrollViewDidScroll:方法中,再调用topBar的setCursorPosition:方法来设置游标的位置。

    //////// .h文件
    #import <UIKit/UIKit.h>
    
    @protocol QiPagesContainerDelegate;
    @class QiPagesContainerTopBar;
    
    @interface QiPagesContainer : UIView
    
    @property (nonatomic, weak) id<QiPagesContainerDelegate>delegate;
    
    /**
     设置相邻button之间的间距。间距是从该button的文字结束到下个button的文字开始之间的距离
     默认值是20
     */
    - (void)setButtonMargin:(CGFloat)margin;
    
    /**
     设置顶部的标题
     */
    - (void)updateContentWithTitles:(NSArray *)titles;
    
    /**
     设置顶部按钮位置
     */
    - (void)setIsButtonAlignmentLeft:(BOOL)isAlignmentLeft;
    
    /*!
     设置是否显示按钮间分割线
     */
    - (void)setShowSeperateLines:(BOOL)showSeperateLines;
    
    /**
     设置底部的View,每个View会占据该容器的大小
     */
    - (void)updateContentWithViews:(NSArray *)views;
    
    /**
     设置所应选择的页,不通知外部
     */
    - (void)setDefaultSelectedPageIndex:(NSInteger)index;
    
    /**
     设置所应选择的页
     */
    - (void)setSelectedPageIndex:(NSInteger)index;
    
    /**
     得到当前的页面
     */
    - (NSInteger)getCurrentPageIndex;
    
    /**
     得到当前正在显示的view
     */
    - (UIView *)getCurrentPageView;
    
    /**
     得到index对应的view
     */
    - (UIView *)getPageViewWithIndex:(NSInteger)index;
    
    /**
     获取顶部的tabBar
     */
    - (QiPagesContainerTopBar*)topBar;
    
    /**
     设置按钮选中和未选中的颜色
     */ 
    - (void)setTextColor:(UIColor *)normalColor andSelectedColor:(UIColor *)selectedColor;
    
    // 设置滑块的颜色
    - (void)setCursorColor:(UIColor *)color;
    // 设置滑块长度
    - (void)setCursorWidth:(CGFloat)width;
    // 设置滑块长度
    - (void)setCursorHeight:(CGFloat)height;
    
    @end
    
    
    @protocol QiPagesContainerDelegate <NSObject>
    
    @optional
    /** page切换 */
    - (void)pageContainder:(QiPagesContainer *)container selectIndex:(NSInteger)index;
    
    /** 点击当前的指示器 */
    - (void)onClickPageIndicator:(QiPagesContainer *)container selectIndex:(NSInteger)index;
    
    @end
    
    
    //////// .m文件
    #import "QiPagesContainer.h"
    #import "QiPagesContainerTopBar.h"
    #import "QiAllowPanGestureScrollView.h"
    
    #define TOPBAR_HEIGHT  34
    
    @interface QiPagesContainer () <UIScrollViewDelegate, QiPagesContainerTopBarDelegate>
    
    @property (nonatomic, strong) QiPagesContainerTopBar *topBar;
    @property (nonatomic, strong) UIScrollView *scrollView;
    @property (nonatomic, strong) UIView *bottomLineView;
    
    @property (nonatomic, strong) NSArray *arrayViews;
    @property (nonatomic, assign) NSInteger currentPageIndex;
    
    @end
    
    @implementation QiPagesContainer
    
    - (id)initWithFrame:(CGRect)frame {
        
        self = [super initWithFrame:frame];
        if (self) {
            [self setupViews];
        }
        return self;
    }
    
    - (void)setupViews {
        
        _topBar = [[QiPagesContainerTopBar alloc] initWithFrame:CGRectZero];
        _topBar.target = self;
        [self addSubview:_topBar];
    
        _scrollView = [[QiAllowPanGestureScrollView alloc] initWithFrame:CGRectZero];
        _scrollView.delegate = self;
        _scrollView.pagingEnabled = YES;
        _scrollView.showsHorizontalScrollIndicator = NO;
        [_scrollView setScrollsToTop:NO];
        [_scrollView setAlwaysBounceHorizontal:NO];
        [_scrollView setAlwaysBounceVertical:NO];
        [_scrollView setBounces:NO];
        [self addSubview:self.scrollView];
    }
    
    - (void)layoutSubviews {
        
        [super layoutSubviews];
        
        CGSize size = self.frame.size;
        CGFloat scrollViewHeight = size.height - TOPBAR_HEIGHT;
        _topBar.frame = CGRectMake(0, 0, size.width, TOPBAR_HEIGHT);
        _scrollView.frame = CGRectMake(0, TOPBAR_HEIGHT, size.width, scrollViewHeight);
        
        
        for (int i=0; i<[_arrayViews count]; i++) {
            UIView *v = [_arrayViews objectAtIndex:i];
            v.frame = CGRectMake(i*size.width, 0, size.width, scrollViewHeight);
        }
        _scrollView.contentSize = CGSizeMake(size.width * [_arrayViews count], scrollViewHeight);
    }
    
    - (void)setButtonMargin:(CGFloat)margin {
        
        _topBar.buttonMargin = margin;
    }
    
    #pragma mark - UIScrollViewDelegate
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
        
        if (scrollView.contentSize.width > 0) {
            [_topBar setCursorPosition:scrollView.contentOffset.x / scrollView.contentSize.width];
        }
    }
    
    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
        
        NSInteger pageIndex = scrollView.contentOffset.x / scrollView.frame.size.width;
        if (pageIndex != _currentPageIndex) {
            _currentPageIndex = pageIndex;
            [_topBar setSelectedIndex:pageIndex];
            [self notifyDelegateSelectedIndex:pageIndex];
        }
    }
    
    #pragma mark - update content
    - (void)setDefaultSelectedPageIndex:(NSInteger)index {
        
        if (index >= 0 && index <= [_arrayViews count] && index != _currentPageIndex) {
            [_topBar setSelectedIndex:index];
            _currentPageIndex = index;
            
            [_scrollView setContentOffset:CGPointMake(index * _scrollView.frame.size.width, 0) animated:YES];
        }
    }
    
    - (void)setSelectedPageIndex:(NSInteger)index {
        
        if (index >= 0 && index <= [_arrayViews count] && index != _currentPageIndex) {
            [_topBar setSelectedIndex:index];
            [self topBarSelectIndex:index];
        }
    }
    
    - (NSInteger)getCurrentPageIndex {
        
        return [_topBar getSelectedIndex];
    }
    
    - (UIView *)getCurrentPageView {
        
        return [_arrayViews objectAtIndex:[_topBar getSelectedIndex]];
    }
    
    - (UIView *)getPageViewWithIndex:(NSInteger)index {
        
        if (index<[_arrayViews count]) {
            return [_arrayViews objectAtIndex:index];
        } else {
            return nil;
        }
    }
    
    - (void)updateContentWithTitles:(NSArray *)titles {
        
        [_topBar updateConentWithTitles:titles];
    }
    
    - (void)setIsButtonAlignmentLeft:(BOOL)isAlignmentLeft {
        
        [_topBar setIsButtonAlignmentLeft:isAlignmentLeft];
    }
    
    - (void)setShowSeperateLines:(BOOL)showSeperateLines {
        
        [_topBar setShowSeperateLines:showSeperateLines];
    }
    
    - (void)updateContentWithViews:(NSArray *)views {
        
        for (UIView *view in _arrayViews) {
            [view removeFromSuperview];
        }
        if ([views count] == 0) {
            return;
        }
        _arrayViews = [NSArray arrayWithArray:views];
        
        for (int i=0; i<[views count]; i++) {
            UIView *view = [views objectAtIndex:i];
            [_scrollView addSubview:view];
        }
        [self layoutSubviews];
    }
    
    #pragma mark - QIPagesContainerTopBarDelegate
    - (void)topBarSelectIndex:(NSInteger)index {
        
        if (index < [_arrayViews count]) {
            _currentPageIndex = index;
    
            [_scrollView setContentOffset:CGPointMake(index * _scrollView.frame.size.width, 0) animated:YES];
            [self notifyDelegateSelectedIndex:index];
        }
    }
    /** 重复点击topBar时会调用该方法 */
    - (void)topBarSelectIndicator:(NSInteger)index {
        
        if (index < [_arrayViews count]) {
            _currentPageIndex = index;
            [_scrollView setContentOffset:CGPointMake(index * _scrollView.frame.size.width, 0) animated:YES];
    
            if (_delegate && [_delegate respondsToSelector:@selector(onClickPageIndicator:selectIndex:)]) {
                [_delegate onClickPageIndicator:self selectIndex:index];
            }
    
        }
    }
    
    - (void)notifyDelegateSelectedIndex:(NSInteger )index {
        
        if (_delegate && [_delegate respondsToSelector:@selector(pageContainder:selectIndex:)]) {
            [_delegate pageContainder:self selectIndex:index];
        }
    }
    
    #pragma mark - 设置按钮选中和未选中的颜色
    - (void)setTextColor:(UIColor *)normalColor andSelectedColor:(UIColor *)selectedColor {
        
        [_topBar setTextColor:normalColor andSelectedColor:selectedColor];
    }
    
    #pragma mark - 设置滑块的颜色
    - (void)setCursorColor:(UIColor *)color {
        
        [_topBar setCursorColor:color];
    }
    
    #pragma mark - 设置滑块长度
    - (void)setCursorWidth:(CGFloat)width {
        
        [_topBar setCursorWidth:width];
    }
    
    #pragma mark - 设置滑块长度
    - (void)setCursorHeight:(CGFloat)height {
        
        [_topBar setCursorHeight:height];
    }
    
    #pragma mark - 获取顶部的tabBar 
    - (QiPagesContainerTopBar*)topBar{
        return _topBar;
    }
    
    @end
    
    3.3 控件在项目中的调用

    1)实现QiPagesContainerDelegate协议,监听控件的选择事件:

    #pragma mark - IHPagesContainerDelegate
    
    - (void)pageContainder:(QiPagesContainer *)container selectIndex:(NSInteger)index {
        
    }
    
    
    - (void)onClickPageIndicator:(QiPagesContainer *)container selectIndex:(NSInteger)index {
        
    }
    

    2)初始化控件,并设置控件样式:

    - (void) setupViews {
        
        CGFloat margin = 0;
        CGSize size = self.view.frame.size;
        
        NSArray *titles = [NSArray arrayWithObjects:@"我的设备", @"卧室", @"厨房厨房厨房厨房", @"门厅", nil];
        NSMutableArray *tempTableViews = [NSMutableArray array];
        _pageContainer = [[QiPagesContainer alloc] initWithFrame:CGRectMake(margin, 0, size.width - margin * 2, size.height)];
        _pageContainer.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        [_pageContainer setBackgroundColor:[UIColor lightGrayColor]];
        _pageContainer.delegate = self;
        [_pageContainer updateContentWithTitles:titles];
        [_pageContainer setIsButtonAlignmentLeft:YES];
        [_pageContainer setCursorHeight:3.0];
        [_pageContainer setCursorColor:[UIColor whiteColor]];
        [_pageContainer setTextColor:[UIColor whiteColor] andSelectedColor:[UIColor whiteColor]];
        
        UIView *topBar = (UIView *)[_pageContainer topBar];
        for (int i=0; i<[titles count]; i++) {
            UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height - topBar.frame.size.height)];
            subView.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:0.3];
            [tempTableViews addObject:subView];
        }
        [_pageContainer updateContentWithViews:tempTableViews];
        
        [self.view addSubview:_pageContainer];
    }
    

    自定义控件展示:


    自定义滑动选择控件

    4. 关于cursor大小及位置设置的说明

    cursor被封装在PagesContainerTopBar内,设置cursor的位置及大小的方法是setCursorPosition:,在设置之前游标之前,我们可以获取到的信息:

    • setCursorPosition:位于PagesContainer中的scrollViewDidScroll:方法之中,即PagesContainer中的scrollView滚动时就会调用这个设置游标的方法,传入的参数是一个百分比scrollView.contentOffset.x / scrollView.contentSize.width;
    • 顶部的PagesContainerTopBar点击选中事件、下部的scrollView滚动事件,均会走scrollViewDidScroll:回调方法

    我们要满足的需求:

    • 在设置cursor的位置时,游标的宽度大小根据位于游标之前按钮(preBtn)宽度和之后按钮(nextBtn)宽度动态变化,即靠近preBtn时cursor的宽就越接近preBtn的宽,靠近nextBtn时就越接近nextBtn的宽。

    实现思路:

    1)获取当前索引的偏移量(float型),并计算并找出preBtn和nextBtn:

    CGFloat indexOffset = percent * [_arrayButtons count];
    NSInteger preIndex = floorf(indexOffset);
    NSInteger nextIndex = ceilf(indexOffset);
    
    UIButton *preBtn = [_arrayButtons objectAtIndex:preIndex];
    UIButton *nextBtn = [_arrayButtons objectAtIndex:nextIndex];
    

    2)ursorWidth的最终取值是与选定按钮的title等宽,ursorWidth的值始终以preBtn为标准,并根据当前滑动偏移量indexOffset-preIndex与按钮的title长度只差的乘积进行变化:

    // 如果cursor的长度是按比例变化的
    ursorWidth = preBtn.titleLabel.frame.size.width + (indexOffset - preIndex) * (nextBtn.titleLabel.frame.size.width - preBtn.titleLabel.frame.size.width);
    

    3)控件底部的scrollView滑动时会不断触发scrollViewDidScroll:,便也会多次调用setCursorPosition:方法传入位置,cursorWidth实时变化并更新至cursor,并设置cursor的确切位置,这样就可以看到顺畅的cursor位置及宽度变化:

    CGRect frame = _cursor.frame;
    frame.size.width = cursorWidth;
     _cursor.frame = frame;
            
    CGFloat cursorCenterX = preBtn.center.x + (indexOffset - preIndex) * (nextBtn.center.x - preBtn.center.x);
    _cursor.center = CGPointMake(cursorCenterX , _cursor.center.y);
    

    工程源码GitHub地址


    小编微信:可加并拉入《QiShare技术交流群》。

    关注我们的途径有:
    QiShare(简书)
    QiShare(掘金)
    QiShare(知乎)
    QiShare(GitHub)
    QiShare(CocoaChina)
    QiShare(StackOverflow)
    QiShare(微信公众号)

    推荐文章:
    iOS 常用调试方法:LLDB命令
    iOS 常用调试方法:断点
    iOS 常用调试方法:静态分析
    iOS消息转发
    iOS 自定义拖拽式控件:QiDragView
    iOS 自定义卡片式控件:QiCardView
    iOS Wireshark抓包
    iOS Charles抓包
    奇舞周刊

    相关文章

      网友评论

        本文标题:iOS 一个滑动选择控件

        本文链接:https://www.haomeiwen.com/subject/hnvbmqtx.html