Level: ★★☆☆ Tag: “iOS” “PagesContainer” “control” author: Dac_1033 Review: QiShare team

We often use the sliding selection function in the development of mobile projects. For example, on the home page of “Mijia” App, in order to display all smart devices under the account, or display some related devices by type and room, the following design is made on the interface:

Here we will introduce the implementation process of the sliding selection control.

1. Requirements analysis

The interface shown in the above GIF is not complicated, as we can see, the sliding selection function is mainly divided into two parts: the sliding selection bar at the top and the manual sliding scrollView at the bottom, and the upper and lower parts can be linked. (Child View in bottom scrollView)

The requirements are summarized as follows: (1) The top is optional topBar with sliding; (2) topBar can swipe left and right with gestures; (3) The number of options in topBar is extensible; (4) The selected item in topBar automatically scrolls to the visible area of the screen; (5) Each item in topBar can be selected by clicking, and the style of the selected item is changed; (6) There is a cursor at the bottom of the selected item in topBar, and the width of the cursor can be adaptive; (7) The lower part is a scrollView that can slide left and right; (8) scrollView displays in separate screens according to the current topBar selection, that is, the linkage between topBar and scrollView;

2. Control framework level

We end up using the topBar and the bottom scrollView as a whole control, so the outermost layer encapsulates them as pageContainer (inherited from UIView), and the entire control is initialized and set in pageContainer. The whole control frame is very simple, as follows:

3. Implementation

The code of the implementation of this question is encapsulated by the senior department, according to the needs of the project we can do some corresponding changes, first of all, thank those seniors to write such a simple and easy to use control, and provide us with the opportunity to learn. Below we explain the implementation process from the bottom up according to the framework hierarchy of controls.

3.1 TopBarButton

TopBarButton in this control inherits from UIButton and is an item in the slider at the top of the control. Override setSelected: method of UIButton to set TopBarButton’s state when selected. If your project has additional style requirements for TopBarButton, you can add additional elements to the class and initialize them accordingly.

/ / / / / / / /. H file @ interface QiTopBarButton: UIButton @ the end / / / / / / / /. M files#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]]; // Set other child views, attributes, etc.}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

Copy the code
3.2 PagesContainerTopBar

The PagesContainerTopBar in this control inherits from UIView, whose child views mainly include scrollView, a set of topBarButton, cursor.

/ / / / / / / /. H file#import <UIKit/UIKit.h>@protocol QiPagesContainerTopBarDelegate; @interface QiPagesContainerTopBar : UIView @property (nonatomic, weak) id<QiPagesContainerTopBarDelegate> target; @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; // Set slider color - (void)setCursorColor:(UIColor *)color; // Set slider length - (void)setCursorWidth:(CGFloat)width; // Set slider length - (void)setCursorHeight:(CGFloat)height; // Set button selected and unselected colors - (void)setTextColor:(UIColor *)normalColor andSelectedColor:(UIColor *)selectedColor; @ end @ protocol QiPagesContainerTopBarDelegate < NSObject > @ optional topBar a - / / selected (void)topBarSelectIndex:(NSInteger)index; - (void)topBarSelectIndicator (NSInteger)index; @end ////////. M file#import "QiPagesContainerTopBar.h"
#import "QiTopBarButton.h"// Left and right side spacing#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];
    }
    returnself; } - (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. The cornerRadius = _cursorHeight / 2.0; [_scrollView addSubview:_cursor]; }#pragma mark - Sets the position of each control
- (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; } // Increase the spacing between buttons CGFloat contentWidth = MARGIN_HORI * 2;for(int i=0; i<[_arrayButtons count]; i++) { UIButton *button = [_arrayButtons objectAtIndex:i]; contentWidth += button.frame.size.width; } // Buttons do not line up the full screen widthif(! _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; } // Set button position 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; }} // Set the position of the splitter linefor(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); / / set the cursor position 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 - Creates each 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]; [buttonsetTitle:title forState:UIControlStateNormal];
    [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
    returnbutton; } / / - (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 sets the position of the button
- (void)setIsButtonAlignmentLeft:(BOOL)isAlignmentLeft {
    
    _isButtonAlignmentLeft = isAlignmentLeft;
}

- (void)setShowSeperateLines:(BOOL)showSeperateLines {
    
    _showSeperateLines = showSeperateLines;
}

#pragma mark Updates and sets the location
- (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]]) {
            returnbutton; }}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

Copy the code
3.3 PagesContainer

PagesContainer is the outermost View, and the user can directly invoke the instantiation of PagesContainer and call the corresponding method to set up the sliding selection control. PagesContainer encapsulates the top topBar and the bottom scrollView, passing the topBar click event and the scrollView slide event to scrollViewDidScroll: method, Call topBar’s setCursorPosition: method to set the cursor position.

/ / / / / / / /. H file#import <UIKit/UIKit.h>@protocol QiPagesContainerDelegate; @class QiPagesContainerTopBar; @interface QiPagesContainer : UIView @property (nonatomic, weak) id<QiPagesContainerDelegate>delegate; /** Sets the spacing between adjacent buttons. The spacing is the distance between the end of the button text and the beginning of the next button text. The default is 20 */ - (void).setButtonMargin:(CGFloat)margin; /** set top title */ - (void)updateContentWithTitles:(NSArray *)titles; /** Set top button position */ - (void)setIsButtonAlignmentLeft:(BOOL)isAlignmentLeft; / *! */ - (void)setShowSeperateLines:(BOOL)showSeperateLines; /** set the bottom View, each View will occupy the container size */ - (void)updateContentWithViews:(NSArray *)views; /** Set the page to be selected without notifying external */ - (void)setDefaultSelectedPageIndex:(NSInteger)index; /** Sets the page to be selected */ - (void)setSelectedPageIndex:(NSInteger)index; /** Get the current page */ - (NSInteger)getCurrentPageIndex; /** Get the current view */ - (UIView *)getCurrentPageView; View */ - (UIView *)getPageViewWithIndex:(NSInteger)index; /** Get top tabBar */ - (QiPagesContainerTopBar*)topBar; /** Sets selected and unselected colors for buttons */ - (void)setTextColor:(UIColor *)normalColor andSelectedColor:(UIColor *)selectedColor; // Set slider color - (void)setCursorColor:(UIColor *)color; // Set slider length - (void)setCursorWidth:(CGFloat)width; // Set slider length - (void)setCursorHeight:(CGFloat)height; @end @protocol QiPagesContainerDelegate <NSObject> @optional /** page toggle */ - (void)pageContainder:(QiPagesContainer) *)container selectIndex:(NSInteger)index; /** Click on the current indicator */ - (void)onClickPageIndicator (QiPagesContainer *) Container selectIndex (NSInteger)index; @end ////////. M file#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; [_topBarsetSelectedIndex:pageIndex]; [self notifyDelegateSelectedIndex:pageIndex]; }}#pragma mark - update content
- (void)setDefaultSelectedPageIndex:(NSInteger)index {
    
    if(index >= 0 && index <= [_arrayViews count] && index ! = _currentPageIndex) { [_topBarsetSelectedIndex: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) { [_topBarsetSelectedIndex: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]; */ - (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 - Sets button selected and unselected colors
- (void)setTextColor:(UIColor *)normalColor andSelectedColor:(UIColor *)selectedColor {
    
    [_topBar setTextColor:normalColor andSelectedColor:selectedColor];
}

#pragma mark - Sets the slider's color
- (void)setCursorColor:(UIColor *)color {
    
    [_topBar setCursorColor:color];
}

#pragma mark - Sets the slider length
- (void)setCursorWidth:(CGFloat)width {
    
    [_topBar setCursorWidth:width];
}

#pragma mark - Sets the slider length
- (void)setCursorHeight:(CGFloat)height {
    
    [_topBar setCursorHeight:height];
}

#pragma mark - Get the top tabBar
- (QiPagesContainerTopBar*)topBar{
    return _topBar;
}

@end
Copy the code
3.3 Control invocation in the project

1) Implement the QiPagesContainerDelegate protocol to listen to the selection event of the control:

#pragma mark - IHPagesContainerDelegate

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


- (void)onClickPageIndicator:(QiPagesContainer *)container selectIndex:(NSInteger)index {
    
}
Copy the code

2) Initialize the controller and set the control style:

- (void) setupViews {
    
    CGFloat margin = 0;
    CGSize size = self.view.frame.size;
    
    NSArray *titles = [NSArray arrayWithObjects:@"My device"The @"Bedroom"The @"Kitchen kitchen kitchen kitchen kitchen"The @"Hall", 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]; [_pageContainersetCursorColor:[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 Alpha blue: arc4random () % 255/255.0:0.3]; [tempTableViews addObject:subView]; } [_pageContainer updateContentWithViews:tempTableViews]; [self.view addSubview:_pageContainer]; }Copy the code

Custom control display:

4. Description of cursor size and position setting

The cursor is enclosed within the PagesContainerTopBar. To set the position and size of the cursor, set setCursorPosition:

  • SetCursorPosition: in the scrollViewDidScroll: method in PagesContainer, that’s what’s called when the scrollView in PagesContainer scrolls, Incoming parameter is a percentage of the scrollView. ContentOffset. X/scrollView. ContentSize width;
  • The PagesContainerTopBar at the top click on the selected event, the scrollView at the bottom scroll the event,ScrollViewDidScroll: callback.

The needs we need to meet:

  • When setting the cursor position, the width of the cursor changes dynamically according to the width of the buttons (preBtn) before the cursor and the width of the buttons (nextBtn) after the cursor, that is, when the cursor is close to preBtn, the width of the cursor is closer to the width of preBtn, and when the cursor is close to nextBtn, the width of the cursor is closer to the width of nextBtn.

Implementation idea:

1) Get the offset of the current index (float type) and calculate and find preBtn and nextBtn:

CGFloat indexOffset = percent * [_arrayButtons count];
NSInteger preIndex = floorf(indexOffset);
NSInteger nextIndex = ceilf(indexOffset);

UIButton *preBtn = [_arrayButtons objectAtIndex:preIndex];
UIButton *nextBtn = [_arrayButtons objectAtIndex:nextIndex];
Copy the code

2) The final ursorWidth is the same width as the title of the selected button. The ursorWidth is always preBtn *** and varies according to the product of the current sliding offset Indexoffset-preindex and the title length of the button:

/ / if the cursor is in proportion to the length of the change of ursorWidth = preBtn. TitleLabel. Frame. The size, width + (indexOffset - preIndex) * (nextBtn.titleLabel.frame.size.width - preBtn.titleLabel.frame.size.width);Copy the code

3) When the scrollView at the bottom of the control slides, scrollViewDidScroll: will be continuously triggered, and setCursorPosition will be called for many times. The cursorWidth will change and update to cursor in real time, and the exact position of cursor will be set. You can see the cursor position and width change smoothly:

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);
Copy the code

Project source GitHub address


Xiaobian wechat: You can add and pull into “QiShare Technical Exchange Group”.

QiShare(Simple book) QiShare(digging gold) QiShare(Zhihu) QiShare(GitHub) QiShare(CocoaChina) QiShare(StackOverflow) QiShare(wechat public account)

Recommended article: common iOS debugging methods: LLDB command Common iOS debugging methods: breakpoint Common iOS debugging methods: static analysis iOS message forwarding iOS custom drag-and-drop control: QiDragView iOS custom card control: QiCardView iOS Wireshark Capture iOS Charles Capture Bag dance weekly