自定义标签控制器-头条网易

.高仿头条网页的标签控制器 核心代码

  • .h 设置属性: 比如jumIndex 初始化跳转那个界面 、刷新当前子控制器、切换准确下标控制器
@property(nonatomic,assign)NSInteger jumIndex;
/**
 刷新当前子控制器
 */
- (void)refreshCurrentSonVC;
  • 导入占位图头文件 滑动时候 有动画
#import "BSTBitmapView.h"
  • 遵守代理 ScrollView代理
<UIScrollViewDelegate>
  • 分类属性
@property(nonatomic,strong)UIScrollView *titleBG;
@property(nonatomic,strong)UIScrollView *contentBG;
@property(nonatomic,strong)NSMutableArray<UILabel *> *titleBtnArrs;
@property(nonatomic,strong)UILabel *preLab;
@property(nonatomic,strong)UIView *selectedItemBottomLine;
//记录当前子控制器index
@property(nonatomic,assign)NSInteger currentIndex;
  • 懒加载
#pragma mark - Lazy懒加载区域
- (NSMutableArray<UILabel *> *)titleBtnArrs
{
    if (!_titleBtnArrs)
    {
        _titleBtnArrs = [NSMutableArray array];
    }
    return _titleBtnArrs;
}
  • UI方面 先创建子控制器 然后创建头部滑动标题
#pragma mark - UI
- (void)createTitleBG
{
    //准备滑动标题
    NSMutableArray<NSString *> *nameArrs = [NSMutableArray array];
    [nameArrs addObject:@"圈子"];
    [nameArrs addObject:@"话题"];
    [nameArrs addObject:@"话题话题话题话题话题话题话题话题话题话题"];
    [nameArrs addObject:@"话题话题话题话题话题话题话题话题"];
    [nameArrs addObject:@"话题话题话题话题话题话题话题话题话题话题"];
    //titleBG
    UIScrollView *titleBG = [[UIScrollView alloc]initWithFrame:CGRectMake(0, kTopHeight, WIDTH, 42)];
    [self.view addSubview:titleBG];
    titleBG.backgroundColor = [UIColor whiteColor];
    titleBG.showsVerticalScrollIndicator = NO;
    titleBG.showsHorizontalScrollIndicator = NO;
    //titleBG.delegate = self;
    //titleBG.bounces = NO;
    self.titleBG = titleBG;
    //滑动小按钮下面的横线 提前创建 比小按钮早添加到titleBG
    UIView *selectedItemBottomLine = [[UIView alloc]init];
    [titleBG addSubview:selectedItemBottomLine];
    selectedItemBottomLine.backgroundColor = RGB(252, 181, 111, 1);
    self.selectedItemBottomLine = selectedItemBottomLine;
    
    UITapGestureRecognizer *jumIndexTap;
    CGFloat leftMargin = 13*ADAPTER_WIDTH,orinX = leftMargin,itemMargin = 25*ADAPTER_WIDTH;
    //for循环布局
    for (NSInteger index = 0; index < nameArrs.count; index ++) {
        NSString *content = nameArrs[index];
        CGFloat itemWidth = [content sizeWithFont:[UIFont systemFontOfSize:16*ADAPTER_WIDTH weight:UIFontWeightMedium]].width;
        
        UILabel *lab = [[UILabel alloc]init];
        [titleBG addSubview:lab];
        lab.text = content;
        lab.font = [UIFont systemFontOfSize:16*ADAPTER_WIDTH weight:UIFontWeightMedium];
        lab.textColor = kColor51;
        lab.frame = CGRectMake(orinX, 14, itemWidth, 17*ADAPTER_WIDTH);
        orinX = index == (nameArrs.count - 1) ? (lab.right + leftMargin) : (lab.right + itemMargin);
        //添加点击事件
        lab.tag = index;
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(clickItem:)];
        lab.userInteractionEnabled = YES;
        [lab addGestureRecognizer:tap];
        //加入数组中
        [self.titleBtnArrs addObject:lab];
        if (index == self.jumIndex)
        {
            self.preLab = lab;
            selectedItemBottomLine.frame = CGRectMake(lab.left+2, lab.bottom-4, lab.width-4, 4);
            jumIndexTap = tap;//绑定要跳转的
        }
    }
    titleBG.contentSize = CGSizeMake(orinX, 0);
    //为什么在循环之外写呢 因为 头部没有创建完整 进行跳转 头部如果过长 会出现 标题不能居中问题
    if (self.titleBtnArrs.count > self.jumIndex) {
        [self clickItem:jumIndexTap];
    }
}
- (void)createContentBG
{
    UIScrollView *contentBG = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 42+kTopHeight, WIDTH, HEIGHT - kTopHeight - 42)];
    [self.view addSubview:contentBG];
    contentBG.backgroundColor = [UIColor whiteColor];
    contentBG.showsVerticalScrollIndicator = NO;
    contentBG.showsHorizontalScrollIndicator = NO;
    contentBG.pagingEnabled = YES;
    contentBG.bounces = NO;
    contentBG.delegate = self;
    self.contentBG = contentBG;
    //添加控制器
    BaseVC *vc = [[BaseVC alloc]init];
    [self addChildViewController:vc];
    BaseVC *vc1 = [[BaseVC alloc]init];
    [self addChildViewController:vc1];
    BaseVC *vc2 = [[BaseVC alloc]init];
    [self addChildViewController:vc2];
    BaseVC *vc3 = [[BaseVC alloc]init];
    [self addChildViewController:vc3];
    BaseVC *vc4 = [[BaseVC alloc]init];
    [self addChildViewController:vc4];
    //这里可以添加VC的回调block之类的
    //扩大滑动范围
    contentBG.contentSize = CGSizeMake(WIDTH * self.childViewControllers.count, 0);
    for (NSInteger index = 0; index < self.childViewControllers.count; index ++)
    {
        BSTBitmapView *bitmapView = [[BSTBitmapView alloc]initWithFrame:CGRectMake(index*WIDTH, 0, WIDTH, contentBG.height)];
        [self.contentBG addSubview:bitmapView];
        bitmapView.type = BitmapViewTypeLoading;
        bitmapView.tag = 100+index;
    }
}
  • 主控制器viewDidLoad调用
    [self createContentBG];
    [self createTitleBG];
  • 其他核心代码 都在下面
#pragma mark - 点击事件
- (void)clickItem:(UITapGestureRecognizer *)sender
{
    //如果animated 是YES 会走scrollView 多次 2个界面会好看些
    [self.contentBG setContentOffset:CGPointMake(sender.view.tag * WIDTH, 0) animated:NO];
    //处理控制器
    [self setUpSubController:sender.view.tag];
    //处理头部title
    [self refreshTitleLab:self.titleBtnArrs[sender.view.tag]];
}
#pragma mark - SCrollView代理方法
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    NSInteger index = scrollView.contentOffset.x / WIDTH;
    //处理控制器
    [self setUpSubController:index];
    //处理头部title
    [self refreshTitleLab:self.titleBtnArrs[index]];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    NSInteger index = scrollView.contentOffset.x / WIDTH;
    CGFloat tmpOffsetX = scrollView.contentOffset.x / WIDTH;
    CGFloat scaleRight = tmpOffsetX - index;
    CGFloat scaleLeft = 1 - scaleRight;
    if (index + 1 <= self.titleBtnArrs.count - 1)
    {
        if (self.titleBtnArrs.count == 0) {
          return;
         }
        //NSLog(@"%ld,%f,%f",index,scaleRight,scaleLeft);
        //self.selectedItemBottomLine.frame = CGRectMake(currentLab.left+2, self.selectedItemBottomLine.top, self.selectedItemBottomLine.width, self.selectedItemBottomLine.height);
        UILabel *leftLab = self.titleBtnArrs[index];
        UILabel *rightLab = self.titleBtnArrs[index+1];
        if (scaleRight == 0)
        {
            //NSLog(@"到达临界值...");
            return;
        }
        if (self.currentIndex == index)
        {
            //NSLog(@"向右");
            self.selectedItemBottomLine.frame = CGRectMake(leftLab.left+2, self.selectedItemBottomLine.top, leftLab.width-4+(scaleRight*(rightLab.left-leftLab.right)), self.selectedItemBottomLine.height);
        }
        else
        {
            //NSLog(@"向左");
            self.selectedItemBottomLine.frame = CGRectMake(rightLab.left+2-(scaleLeft*(rightLab.left-leftLab.right)), self.selectedItemBottomLine.top, leftLab.width-4+(scaleLeft*(rightLab.left-leftLab.right)), self.selectedItemBottomLine.height);
        }
    }
}
#pragma mark - 添加子控制器视图
- (void)setUpSubController:(NSInteger)index
{
    self.currentIndex = index;
    BaseVC *vc = self.childViewControllers[index];
    //做一些 vc的初始化
    //vc.viewHeight = self.contentBG.height;
    if (vc.view.superview)
    {
        return;
    }
    vc.view.frame = CGRectMake(index *WIDTH, 0, WIDTH, self.contentBG.height);
    [self.contentBG addSubview:vc.view];
    
    //清除占位图
    BSTBitmapView *bitMapView = [self.contentBG viewWithTag:100+index];
    if (bitMapView != nil)
    {
        [bitMapView removeFromSuperview];
    }
}
#pragma mark - 矫正滑动标题的居中问题
- (void)refreshTitleLab:(UILabel *)currentLab
{
    self.preLab.textColor = kColor51;
    self.preLab = currentLab;
    
    [UIView animateWithDuration:0.2 delay:0 usingSpringWithDamping:0.6 initialSpringVelocity:1.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        self.selectedItemBottomLine.frame = CGRectMake(currentLab.left+2, self.selectedItemBottomLine.top, currentLab.width-4, self.selectedItemBottomLine.height);
    } completion:^(BOOL finished) {
        
    }];
    
    //标题居中
    CGFloat maxOffset = self.titleBG.contentSize.width - self.titleBG.width;
    maxOffset = MAX(0, maxOffset);
    CGFloat offset = self.preLab.centerX - (self.titleBG.width * 0.5);
    offset = MAX(0, offset);
    offset = MIN(maxOffset, offset);
    [self.titleBG setContentOffset:CGPointMake(offset, 0) animated:YES];
}
#pragma mark - 公开方法
- (void)refreshCurrentSonVC
{
    BaseVC *vc = self.childViewControllers[self.currentIndex];
    //[vc loadNewData];
}
  • 真对需要全局改动 则需要加入如下代码
.h
/**
 刷新第一个控制器 并且设置当前的子控制器 并切换到第一个控制器
 */
- (void)refreshFirstController;
.m
- (void)refreshFirstController
{
    //遍历改变所有globalChangeRegion
    [self.childViewControllers enumerateObjectsUsingBlock:^(__kindof UIViewController * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([obj isMemberOfClass:[HomePageListsItemVC class]]) {
            HomePageListsItemVC *itemVC = (HomePageListsItemVC *)obj;
            itemVC.globalChangeRegion = YES;
        }
    }];
    //如果animated 是YES 会走scrollView 多次 2个界面会好看些
    [self.contentBG setContentOffset:CGPointMake(0 * WIDTH, 0) animated:NO];
    //处理控制器
    [self setUpSubController:0];
    //处理头部title
    [self refreshTitleLab:self.titleBtnArrs[0]];
}

子控制器 .h
@property(nonatomic,assign)BOOL globalChangeRegion;//默认是NO YES 则刷新
父类控制器
@property(nonatomic,assign)BOOL globalChangeRegion;//默认是NO YES 则刷新
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    if (self.globalChangeRegion) {
        self.globalChangeRegion = NO;
        [self.managerVC refreshFirstController];
    }
}

细节-遍历 增加、减少控制器

删除childViewControllers 最好用enumerateObjectsUsingBlock方法

for循环删除不干净.png

细节-增加、减少控制器 改变控制器frame

这里也需要切换一下.png
更换控制器需要进行更改通知名字.png

针对滑动效果 平滑渐变

if (self.currentIndex == index)
{
    //NSLog(@"向右");
    CGFloat orignX = (leftLab.left + leftLab.right - self.selectedItemBottomLine.width)*0.5;
    CGFloat orignX1 = (rightLab.left + rightLab.right - self.selectedItemBottomLine.width)*0.5;
    CGFloat maxWidth = orignX1 - orignX;
    self.selectedItemBottomLine.frame = CGRectMake(orignX + maxWidth * scaleRight, self.selectedItemBottomLine.top, self.selectedItemBottomLine.width, self.selectedItemBottomLine.height);
    //必须先计算然后赋值 不然颜色就不行
    //rgba(36, 199, 126, 1) rgba(51, 51, 51, 1)
    CGFloat x = 51 + (-14*scaleRight);
    CGFloat y = 51 + 148*scaleRight;
    CGFloat z = 51 + 75*scaleRight;
    CGFloat xxx = 36 - (-14*scaleRight);
    CGFloat yyy = 199 - 148*scaleRight;
    CGFloat zzz = 126 - 75*scaleRight;
    leftLab.textColor = rgba(xxx, yyy, zzz, 1);
    rightLab.textColor = rgba(x, y, z, 1);
}
else
{
    NSLog(@"向左");
    CGFloat orignX = (leftLab.left + leftLab.right - self.selectedItemBottomLine.width)*0.5;
    CGFloat orignX1 = (rightLab.left + rightLab.right - self.selectedItemBottomLine.width)*0.5;
    CGFloat maxWidth = orignX1 - orignX;
    self.selectedItemBottomLine.frame = CGRectMake(orignX1 - maxWidth * scaleLeft, self.selectedItemBottomLine.top, self.selectedItemBottomLine.width, self.selectedItemBottomLine.height);
    //必须先计算然后赋值 不然颜色就不行
    //rgba(36, 199, 126, 1) rgba(51, 51, 51, 1)
    CGFloat x = 51 + (-14*scaleLeft);
    CGFloat y = 51 + 148*scaleLeft;
    CGFloat z = 51 + 75*scaleLeft;
    CGFloat xxx = 36 - (-14*scaleLeft);
    CGFloat yyy = 199 - 148*scaleLeft;
    CGFloat zzz = 126 - 75*scaleLeft;
    rightLab.textColor = rgba(xxx, yyy, zzz, 1);
    leftLab.textColor = rgba(x, y, z, 1);
}

字体变大 无法有效果 所以这边使用transform(transform超出自身一倍以上字体像素变大 变模糊 所以用正常和缩小)

补充 子控制器添加 (下面都是单独测试)

[self.view addSubview:self.vc.view];
self.vc.view.frame = CGRectMake(50, kTopHeight + 20, kMainScreenWidth - 100, 400);
NSLog(@"%d",self.vc.isViewLoaded);
NSLog(@"%@",self.vc.view.superview);
NSLog(@"%@",self.vc.view);
  • 判断isViewLoaded 并不会额外方法调用
  • 判断superview 会调用viewDidLoadisViewLoaded为YES
  • 主动给子控制器设置view的frame 也会调用viewDidLoad
  • addSubView 添加
init
viewDidLoad
viewWillAppear
viewDidAppear
  • removeFromSuperview 移除
viewWillDisappear
viewDidDisappear

补充添加到主控制器

  • 导航push
 init
 
 willMoveToParentViewController <DTBaseNavigationController: 0x109826200>
 viewDidLoad
 viewWillAppear
 viewDidAppear
 didMoveToParentViewController <DTBaseNavigationController: 0x107819e00>
 
 willMoveToParentViewController (null)
 viewWillDisappear
 viewDidDisappear
 didMoveToParentViewController (null)
 
 dealloc
  • 添加和移除控制器
[self addChildViewController:self.vc];
这个时候主控制多了一个子控制器
且只会调用willMoveToParentViewController 所以少了didMoveToParentViewController 这个得主动加


[self.vc removeFromParentViewController];
这个时候主控制少了这个子控制器
且只会调用 didMoveToParentViewController 所以少了willMoveToParentViewController
  • 补充完善吧(为了迎合push方案生命周期)
    1.添加控制器
[self addChildViewController:self.vc];
[self.view addSubview:self.vc.view];
[self.vc didMoveToParentViewController:self];

2.移除

[self.vc willMoveToParentViewController:nil];
[self.vc.view removeFromSuperview];
[self.vc removeFromParentViewController];
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。