iOS 一个滑动选择控件

推荐阅读:iOS开发——BAT面试题合集(持续更新中)

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

image

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

1. 需求分析

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

image

需求归纳如下:

(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中。则整个控件的框架很简单,如下:

image

3. 具体实现

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

3.1 TopBarButton

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

<pre class="prettyprint hljs objectivec" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">//////// .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

  • (id)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]];
    
      // 设置其他子view、属性等
    

    }
    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

复制代码</pre>

3.2 PagesContainerTopBar

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

<pre class="prettyprint hljs objectivec" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">//////// .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)init {

    self = [super init];
    if (self) {
    [self setup];
    }
    return self;
    }

  • (id)initWithFrame:(CGRect)frame {

    self = [super initWithFrame:frame];
    if (self) {
    [self setup];
    }
    return self;
    }

  • (id)initWithCoder:(NSCoder *)aDecoder {

    self = [super initWithCoder:aDecoder];
    if (self) {
    [self setup];
    }
    return self;
    }

  • (void)setup {

    _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

复制代码</pre>

3.3 PagesContainer

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

<pre class="prettyprint hljs objectivec" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">//////// .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 setup];
    }
    return self;
    }

  • (id)init {

    self = [super init];
    if (self) {
    [self setup];
    }
    return self;
    }

  • (id)initWithCoder:(NSCoder *)aDecoder {

    self = [super initWithCoder:aDecoder];
    if (self) {
    [self setup];
    }
    return self;
    }

  • (void)setup {

    _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];

    [self layoutSubviews];
    }

  • (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
复制代码</pre>

3.3 控件在项目中的调用

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

<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">#pragma mark - IHPagesContainerDelegate

  • (void)pageContainder:(QiPagesContainer *)container selectIndex:(NSInteger)index {

}

  • (void)onClickPageIndicator:(QiPagesContainer *)container selectIndex:(NSInteger)index {

}
复制代码</pre>

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

<pre class="prettyprint hljs objectivec" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">- (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];

}
复制代码</pre>

自定义控件展示:

image

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:

<pre class="prettyprint hljs objectivec" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">CGFloat indexOffset = percent * [_arrayButtons count];
NSInteger preIndex = floorf(indexOffset);
NSInteger nextIndex = ceilf(indexOffset);

UIButton *preBtn = [_arrayButtons objectAtIndex:preIndex];
UIButton *nextBtn = [_arrayButtons objectAtIndex:nextIndex];
复制代码</pre>

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

<pre class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">// 如果cursor的长度是按比例变化的
ursorWidth = preBtn.titleLabel.frame.size.width + (indexOffset - preIndex) * (nextBtn.titleLabel.frame.size.width - preBtn.titleLabel.frame.size.width);
复制代码</pre>

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

<pre class="prettyprint hljs objectivec" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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);
复制代码</pre>

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