In iOS, the UISegmentedControl control can be used to represent the Tab page, but its style is difficult to modify, and we usually customize the Tab page.
1. Customize the Tab page
Here we first define UKTabItemView
the tabs used to display it.
// 标签页代理
@protocol UKTabItemViewDelegate <NSObject>
- (void)onTabItemViewSelected:(UKTabItemView *)tabItemView;
@end
@interface UKTabItemView : UIView
@property(nonatomic, weak) id<UKTabItemViewDelegate> delegate;
// 设置标签页标题
- (void)setText:(NSString *)text;
// 设置标签页状态
- (void)setSelected:(BOOL)selected;
@end
@interface UKTabItemView ()
@property(nonatomic, strong) UIButton *itemButton;
@property(nonatomic, strong) UIView *indicatorView;
@end
@implementation UKTabItemView
- (instancetype)init {
self = [super init];
if (self) {
[self setupInitialUI];
}
return self;
}
- (void)setupInitialUI {
[self addSubview:self.itemButton];
[self.itemButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.bottom.equalTo(self);
}];
[self addSubview:self.indicatorView];
[self.indicatorView mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(self);
make.height.equalTo(@2);
make.centerX.equalTo(self);
make.width.equalTo(@60);
}];
}
- (void)setText:(NSString *)text {
[self.itemButton setTitle:text forState:UIControlStateNormal];
}
- (void)setSelected:(BOOL)selected {
[self.itemButton setSelected:selected];
self.indicatorView.hidden = !selected;
if (selected) {
[self.itemButton.titleLabel setFont:[UIFont systemFontOfSize:17]];
} else {
[self.itemButton.titleLabel setFont:[UIFont systemFontOfSize:15]];
}
}
- (UIButton *)itemButton {
if (!_itemButton) {
_itemButton = [[UIButton alloc] init];
[_itemButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[_itemButton setTitleColor:[UIColor blueColor] forState:UIControlStateSelected];
[_itemButton.titleLabel setFont:[UIFont systemFontOfSize:15]];
[_itemButton addTarget:self action:@selector(onItemClick:) forControlEvents:UIControlEventTouchUpInside];
}
return _itemButton;
}
- (void)onItemClick:(UIButton *)sender {
if (self.delegate) {
[self.delegate onTabItemViewSelected:self];
}
}
- (UIView *)indicatorView {
if (!_indicatorView) {
_indicatorView = [[UIView alloc] init];
_indicatorView.layer.backgroundColor = [UIColor blueColor].CGColor;
_indicatorView.layer.cornerRadius = 1;
_indicatorView.layer.masksToBounds = YES;
_indicatorView.hidden = YES;
}
return _indicatorView;
}
@end
Customization UKTabView
, including several UKTabItemView
, the font and color of the selected tab will change, and the prompt below will also become brighter.
@protocol UKTabViewDelegate <NSObject>
- (void)onTabViewSelected:(UKTabView *)tabView position:(NSInteger)position;
@end
@interface UKTabView : UIView
@property(nonatomic, weak) id<UKTabViewDelegate> delegate;
- (void)setItems:(NSArray<NSString *> *)items selection:(NSInteger)selection;
- (void)setSelection:(NSInteger)selection;
@end
@interface UKTabView() <UKTabItemViewDelegate>
@property(nonatomic, assign) NSInteger selection;
@property(nonatomic, strong) NSMutableArray<UKTabItemView *> *tabItemViews;
@end
@implementation UKTabView
- (instancetype)init {
self = [super init];
if (self) {
[self setupInitialUI];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupInitialUI];
}
return self;
}
- (void)setupInitialUI {
_selection = -1;
self.tabItemViews = [[NSMutableArray alloc] init];
}
- (void)setItems:(NSArray<NSString *> *)items selection:(NSInteger)selection {
[self.tabItemViews removeAllObjects];
UKTabItemView *lastItemView = nil;
for (NSString *item in items) {
UKTabItemView *tabItemView = [[UKTabItemView alloc] init];
[tabItemView setText:item];
[self addSubview:tabItemView];
// 所有的选项卡都等分排列
[tabItemView mas_makeConstraints:^(MASConstraintMaker *make) {
if (lastItemView) {
make.left.equalTo(lastItemView.mas_right);
} else {
make.left.equalTo(self);
}
make.top.bottom.equalTo(self);
make.width.equalTo(self).multipliedBy(1.0/item.length);
}];
lastItemView = tabItemView;
[self internalAddTabItemView:tabItemView];
}
[self setSelection:selection];
}
- (void)internalAddTabItemView:(UKTabItemView *)itemView {
// 添加itemView,并用tag记录位置
itemView.tag = self.tabItemViews.count;
[self.tabItemViews addObject:itemView];
itemView.delegate = self;
}
- (void)setSelection:(NSInteger)selection {
if (selection >= 0) {
if (selection != self.selection) {
if (self.selection >= 0) {
[self.tabItemViews[self.selection] setSelected:NO];
}
_selection = selection;
[self.tabItemViews[self.selection] setSelected:YES];
}
}
}
#pragma mark - UKTabItemViewDelegate -
- (void)onTabItemViewSelected:(UKTabItemView *)tabItemView {
[self setSelection:tabItemView.tag];
[self.delegate onTabViewSelected:self position:tabItemView.tag];
}
@end
Inside UIViewController
, we define a UKTabView
, and add three tabs
UKTabView *tabView = [[UKTabView alloc] initWithFrame:CGRectMake(10, 100, 320, 50)]
[tabView setItems:@[@"选项1", @"选项2", @"选项3"] selection:0];
[self.view addSubview:self.tabView];
The effect is as follows
2. Interaction with UIScrollView
A Tab page often has an interactive interface below it, such as UIScrollView
, UICollectionView
etc. UIScrollView
Here will illustrate it with an example. Generally, there are two kinds of interactions here, one is that the Tab tab is selected and then UIScrollView
changes, and the other is that UIScrollView
the Tab tab changes after scrolling.
Let's add a display image first UIScrollView
,
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(10, 170, 320, 150)];
scrollView.contentSize = CGSizeMake(320*3, 150);
scrollView.pagingEnabled = YES;
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.delegate = self;
[self.view addSubview: scrollView];
for (int index = 1; index <= 3; index++) {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(320 * (index - 1), 0, 320, 150)];
imageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"switcher%d", index]];
[scrollView addSubview:imageView];
}
The added UKTabView
agent listens to each tab change
#pragma mark - UKTabViewDelegate -
- (void)onTabViewSelected:(UKTabView *)tabView position:(NSInteger)position {
[self.scrollView setContentOffset:CGPointMake(320 * position, 0) animated:YES];
}
The added UIScrollView
agent UIScrollView
modifies the state of the Tab tab when the scrolling ends
#pragma mark - UIScrollViewDelegate -
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
CGFloat width = scrollView.contentOffset.x;
NSInteger page = width/320 + 0.5;
[self.tabView setSelection:page];
}
The effect is as follows
3. Dynamically add tabs
Dynamically added UKTabItemView
, we need to modify all the previous UKTabItemView
spacing
- (void)addItemView:(UKTabItemView *)itemView {
NSInteger len = self.subviews.count;
for (UIView *view in self.subviews) {
[view mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(self).multipliedBy(1.0/(len + 1));
}];
}
[self addSubview:itemView];
[itemView mas_makeConstraints:^(MASConstraintMaker *make) {
if (len == 0) {
make.left.equalTo(self);
} else {
make.left.equalTo(self.subviews[len - 1].mas_right);
}
make.top.bottom.equalTo(self);
make.width.equalTo(self).multipliedBy(1.0/(len + 1));
}];
[self internalAddTabItemView:itemView];
}
4. Prompt bar
In the above example, the prompt bar is included in UITabItemView
it. Sometimes we may need the prompt bar to have a dynamic moving effect, so we define the prompt bar in UKTabView
.
// 设置提示栏的宽度、高度和颜色等
- (void)setIndicatorWidth:(NSInteger)width height:(NSInteger)height radius:(NSInteger)radius color:(UIColor *)color {
self.indicatorWidth = width;
self.indicatorHeight = height;
self.indicatorRadius = radius;
self.indicatorColor = color;
if (width > 0) {
self.indicatorLayer.fillColor = self.indicatorColor.CGColor;
[self.layer addSublayer:self.indicatorLayer];
} else {
[self.indicatorLayer removeFromSuperlayer];
}
}
// 修改当前选项卡后,重新绘制提示栏
- (void)setSelection:(NSInteger)selection {
if (selection >= 0) {
if (selection != self.selection) {
if (self.selection >= 0) {
[self.tabItemViews[self.selection] setSelected:NO];
}
_selection = selection;
[self.tabItemViews[self.selection] setSelected:YES];
}
[self drawIndicatorView];
}
}
// ratio为偏移度
- (void)setSelection:(NSInteger)selection offsetRatio:(CGFloat)ratio {
if (selection >= 0) {
self.offsetRatio = ratio;
[self setSelection:selection];
}
}
// 绘制提示栏,我们利用CALayer的隐式动画来给提示栏添加动态效果
// 每次添加选项卡后,提示栏宽度都会被清空
// 提示栏宽度不能超过选项卡本身宽度
- (void)drawIndicatorView {
if (self.indicatorWidth > 0 && self.frame.size.width > 0 && self.tabItemViews.count > 0) {
CGFloat itemWidth = self.frame.size.width*1.0/self.tabItemViews.count;
BOOL initialized = self.indicatorActualWidth != 0;
CGFloat startX = itemWidth * self.selection + itemWidth * self.offsetRatio;
if (!initialized) {
self.indicatorActualWidth = self.indicatorWidth;
if (itemWidth <= self.indicatorWidth) {
self.indicatorActualWidth = itemWidth;
}
}
if (self.indicatorActualWidth < itemWidth) {
startX += (itemWidth - self.indicatorActualWidth) / 2;
}
// 绘制选项卡
if (!initialized) {
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, self.indicatorActualWidth, self.indicatorHeight) cornerRadius:self.indicatorRadius];
self.indicatorLayer.path = path.CGPath;
}
// 如果有偏移量,去除CALayer隐式动画
BOOL anim = self.offsetRatio == 0;
if (!anim) {
[CATransaction begin];
[CATransaction setDisableActions:true];
}
self.indicatorLayer.frame = CGRectMake(startX, self.frame.size.height - self.indicatorHeight, self.indicatorActualWidth, self.indicatorHeight);
if (!anim) {
[CATransaction commit];
}
}
}
The scrolling we scrollViewWillBeginDragging
distinguish in the method UIScrollView
is triggered by gestures or code. In scrollViewDidScroll
the method, if the movement is triggered by a gesture, the status bar will move in proportion.
#pragma mark - UIScrollViewDelegate -
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
self.dragging = YES;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (self.dragging) {
CGFloat width = scrollView.contentOffset.x;
NSInteger page = width/320 + 0.5;
[self.tabView setSelection:page offsetRatio:(width/320 - page)];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
CGFloat width = scrollView.contentOffset.x;
NSInteger page = width/320 + 0.5;
[self.tabView setSelection:page];
self.dragging = NO;
}
The effect is as follows