iOS 自定义Tab页

在iOS里面可以用UISegmentedControl控件来表示Tab页,但其样式难以修改,我们一般会自定义Tab页。

1. 自定义Tab页

在这里我们首先定义UKTabItemView用来显示其中的标签页。

// 标签页代理
@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

自定义UKTabView,包含若干个UKTabItemView,选中的选项卡字体和颜色会有变化,下面的提示也会变亮。

@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

UIViewController里面,我们定义一个UKTabView,并添加三个选项卡

UKTabView *tabView = [[UKTabView alloc] initWithFrame:CGRectMake(10, 100, 320, 50)]
[tabView setItems:@[@"选项1", @"选项2", @"选项3"] selection:0];

[self.view addSubview:self.tabView];

效果如下
在这里插入图片描述

2. 与UIScrollView的互动

一个Tab页往往下面会有互动的界面,比如说UIScrollViewUICollectionView等,这里我们以UIScrollView来举例说明。一般这里的互动有两种,一种是Tab选项卡被选中后UIScrollView跟着变化,另一种是UIScrollView滚动后Tab选项卡跟着变化。

我们先添加一个显示图片的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];
}

添加UKTabView的代理,监听每次Tab选项卡变化

#pragma mark - UKTabViewDelegate -
- (void)onTabViewSelected:(UKTabView *)tabView position:(NSInteger)position {
    [self.scrollView setContentOffset:CGPointMake(320 * position, 0) animated:YES];
}

添加UIScrollView的代理,当UIScrollView的滚动结束时,修改Tab选项卡的状态

#pragma mark - UIScrollViewDelegate -
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    CGFloat width = scrollView.contentOffset.x;
    NSInteger page = width/320 + 0.5;

    [self.tabView setSelection:page];
}

效果如下
在这里插入图片描述

3. 动态添加Tab选项卡

动态添加UKTabItemView,我们需要修改前面所有UKTabItemView的间距

- (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. 提示栏

在上面的例子里面,提示栏都是包含在UITabItemView里面的,有时候我们可能需要提示栏有动态移动的效果,那么我们就把提示栏在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];
        }
    }
}

我们在scrollViewWillBeginDragging方法 里面区分UIScrollView的滚动是由手势触发的还是代码触发的。在scrollViewDidScroll方法里面,如果是手势触发的移动,状态栏按照比例跟着移动。

#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;
}

效果如下

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的示例代码,展示了如何生成一个自定义TabBar: 首先,创建一个名为CustomTabBarController的自定义视图控制器,作为TabBar的容器: ```swift import UIKit class CustomTabBarController: UIViewController { // 自定义TabBar视图 let customTabBar = CustomTabBar() override func viewDidLoad() { super.viewDidLoad() // 添加自定义TabBar视图 view.addSubview(customTabBar) // 设置自定义TabBar的位置和大小 customTabBar.frame = CGRect(x: 0, y: view.frame.height - 100, width: view.frame.width, height: 100) // 设置自定义TabBar的按钮点击事件 customTabBar.button1.addTarget(self, action: #selector(button1Tapped), for: .touchUpInside) customTabBar.button2.addTarget(self, action: #selector(button2Tapped), for: .touchUpInside) customTabBar.button3.addTarget(self, action: #selector(button3Tapped), for: .touchUpInside) } // 按钮1点击事件 @objc func button1Tapped() { // 切换到第一个视图控制器 selectedIndex = 0 } // 按钮2点击事件 @objc func button2Tapped() { // 切换到第二个视图控制器 selectedIndex = 1 } // 按钮3点击事件 @objc func button3Tapped() { // 切换到第三个视图控制器 selectedIndex = 2 } } ``` 接下来,创建一个名为CustomTabBar的自定义TabBar视图,用于显示TabBar按钮: ```swift import UIKit class CustomTabBar: UIView { // TabBar按钮 let button1 = UIButton() let button2 = UIButton() let button3 = UIButton() override init(frame: CGRect) { super.init(frame: frame) // 设置按钮的样式、位置和大小 button1.setTitle("Tab 1", for: .normal) button1.frame = CGRect(x: 0, y: 0, width: frame.width / 3, height: frame.height) button2.setTitle("Tab 2", for: .normal) button2.frame = CGRect(x: frame.width / 3, y: 0, width: frame.width / 3, height: frame.height) button3.setTitle("Tab 3", for: .normal) button3.frame = CGRect(x: (frame.width / 3) * 2, y: 0, width: frame.width / 3, height: frame.height) // 添加按钮到自定义TabBar视图 addSubview(button1) addSubview(button2) addSubview(button3) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } ``` 最后,在AppDelegate中设置CustomTabBarController为应用程序的主视图控制器: ```swift import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) // 创建自定义TabBar视图控制器 let customTabBarController = CustomTabBarController() // 设置自定义TabBar视图控制器为主视图控制器 window?.rootViewController = customTabBarController window?.makeKeyAndVisible() return true } } ``` 在这个示例中,我们创建了一个CustomTabBarController作为自定义TabBar的容器,并在其中添加了CustomTabBar视图。CustomTabBar视图中包含了三个按钮,分别用于切换到不同的视图控制器。你可以根据需要进行修改和扩展,以满足你的具体需求。 希望对你有所帮助!如果你有任何进一步的问题,请继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值