iOS Develops Wheels | Scroll Menu Bar

Keywords: less


iu

Packaging purposes:

The rolling menu bar is very common in practical projects. In order to avoid repeating wheel building, we have a wave of summary and encapsulation today. Although this custom control has a lot of demo s on the Internet, I think it is only in my heart that I encapsulate it.


Similar to this

The results to be achieved are as follows:

1. Do not scroll when the buttons are less, and support scrolling when the options are more.
2. Click on the button, the button is highlighted and the position is adjusted.
3. The button click event will expose the corresponding callback interface.
4. In addition to manually clicking on the selected button, you can also select a button through code to provide interaction with other controls such as UITableView.

thinking

  • Put the buttons in turn on a scrollView
  • Click on the button to dynamically adjust the position of the selected button by adjusting the content Offset of scrollView
  • Click on the button to transfer the value back to the calling agent or block
  • Exposing an interface or property for interaction with other controls

Detailed code

h file

#import <UIKit/UIKit.h>

@class CQScrollMenuView;
@protocol CQScrollMenuViewDelegate <NSObject>

/**
 Callback when the menu button is clicked

 @param scrollMenuView With a single view
 @param index index of the button clicked
 */
- (void)scrollMenuView:(CQScrollMenuView *)scrollMenuView clickedButtonAtIndex:(NSInteger)index;

@end

@interface CQScrollMenuView : UIScrollView

@property (nonatomic,weak) id <CQScrollMenuViewDelegate> menuButtonClickedDelegate;
/** Menu Title Array */
@property (nonatomic,strong) NSArray *titleArray;
/** index of the currently selected button */
@property (nonatomic,assign) NSInteger currentButtonIndex;

@end

m file

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

@interface CQScrollMenuView ()

@end

@implementation CQScrollMenuView{
    /** Control used to record the last creation */
    UIView *_lastView;
    /** The horizontal line under the button */
    UIView *_lineView;
}

#pragma mark-override construction method
/** Rewrite Construction Method */
- (instancetype)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
        self.showsHorizontalScrollIndicator = NO;
        _currentButtonIndex = 0; // The default currently selected button is the first
    }
    return self;
}

#pragma mark - assignment header array
/** Assignment header array */
- (void)setTitleArray:(NSArray *)titleArray{
    _titleArray = titleArray;

    // Remove all child controls first
    for (UIView *subView in self.subviews) {
        [subView removeFromSuperview];
    }

    // Empty lastView
    _lastView = nil;

    // Traversing the header array
    [_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];

        // Width adaptation
        [menuButton sizeToFit];
        menuButton.height = self.height;

        // If not, the width of button's label at initialization is 0.
        [menuButton layoutIfNeeded];

        // Select the state when the first button is defaulted
        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 the total width of the content does not exceed itself, divide the modules equally.
    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; // Open, prevent extreme situations
                if (i == 0) {
                    _lineView.width = button.titleLabel.width;
                    _lineView.centerX = button.centerX;
                    _lineView.maxY = self.height;
                }
                i ++;
            }
        }
    }
}

#pragma mark - menu button click
/** Menu button click */
- (void)menuButtonClicked:(UIButton *)sender{
    // Change the selected state of the button
    for (UIButton *button in self.subviews) {
        if ([button isMemberOfClass:[UIButton class]]) {
            button.selected = NO;
        }
    }
    sender.selected = YES;

    // Move the clicked button to the middle
    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);
            }];
        }
    }

    // Change the position and width of the underline
    [UIView animateWithDuration:0.3 animations:^{
        _lineView.width = sender.titleLabel.width;
        _lineView.centerX = sender.centerX;
    }];

    // Agent Execution Method
    if ([self.menuButtonClickedDelegate respondsToSelector:@selector(scrollMenuView:clickedButtonAtIndex:)]) {
        [self.menuButtonClickedDelegate scrollMenuView:self clickedButtonAtIndex:(sender.tag - 100)];
    }
}

#pragma mark - assigns the currently selected button
/** Assign the currently selected button */
- (void)setCurrentButtonIndex:(NSInteger)currentButtonIndex{
    _currentButtonIndex = currentButtonIndex;

    // Change the selected state of the button
    UIButton *currentButton = [self viewWithTag:(100 + currentButtonIndex)];
    for (UIButton *button in self.subviews) {
        if ([button isMemberOfClass:[UIButton class]]) {
            button.selected = NO;
        }
    }
    currentButton.selected = YES;

    // Move the clicked button to the middle
    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);
            }];
        }
    }

    // Change the width and position of the underline
    [UIView animateWithDuration:0.3 animations:^{
        _lineView.width = currentButton.titleLabel.width;
        _lineView.centerX = currentButton.centerX;
    }];
}

@end

Usage method

Initialization

    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 Extended Edition",@"button2",@"button3",@"button4",@"button5",@"button6",@"button7",@"button8",@"button9",@"button10",@"button11"];

Agent Method

#pragma mark - Delegate - menu bar
// Callback when the menu button is clicked
- (void)scrollMenuView:(CQScrollMenuView *)scrollMenuView clickedButtonAtIndex:(NSInteger)index{
    // Table View scrolls to the corresponding group
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:index];
    [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
}

Set the specified button to the selected state

// Callback when the UITableView group header is to be displayed
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section{
    // linkage
    self.menuView.currentButtonIndex = section;
}

Matters needing attention

After the button initializes the assignment title, it needs to call the layoutIfNeeded method to get the label width of the button.

summary

After self-encapsulation, similar controls can basically be modified on this basis in the future. Another advantage of self-encapsulation is that it is easy to see your own code, and it is absolutely faster to make changes than to modify someone else's code. And most importantly, encapsulating custom controls can really improve your coding level.

A small demo linked to a menu bar and tableView

This is demo.

Posted by sem_db on Thu, 20 Jun 2019 14:52:53 -0700