.高仿头条网页的标签控制器 核心代码
- .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 会调用
viewDidLoad
且isViewLoaded
为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];