一、为什么苹果在iOS5添加了addChildViewController?
能想到的其它的一些使用场景或者说是优势吧
- 不以navVC管理的,transition需要动画的 (例如某些login模块相关有注册,忘记密码,修改密码等)
- 容器vc
- 左右联动,或上下联动
- 条件显示,一个vc不是一直显示的view,且该view比较复杂,业务功能比较多
不管怎样,开发过程中还是看具体使用场景,addChildViewController本质优点还是清晰易管理,但是滥用的话也会造成vc耦合度太高的问题
二、该demo代码实现的功能
1.addChildViewController使用
2.选中下划线标识(两种实现方案)
3.切换标签下划线动画
4.头部标签headscrollview抽离封装
5.childViewController里子view布局时机注意事项
三、addChildViewController使用(代码有详细的注释和注意点)
主容器VC--MainViewController
//
// MainViewController.m
// 测试addChildViewController
// 1、选中下划线标识(两种实现方案) 2、切换标签下划线动画 3、headscrollview抽离封装
// Created by caohx on 2021/1/13.
//
#import "MainViewController.h"
#import "FirstViewController.h"
#import "SecondViewController.h"
#import "ThirdViewController.h"
#import "HeadTitlesScrollView.h"
static CGFloat headHeight = 40;
@interface MainViewController ()
@property (nonatomic, strong) HeadTitlesScrollView *headScrollView;
@property (nonatomic, strong) UIViewController *currentVC;
@property (nonatomic, strong) FirstViewController *firstVC;
@property (nonatomic, strong) SecondViewController *secondVC;
@property (nonatomic, strong) ThirdViewController *thirdVC;
@end
@implementation MainViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor grayColor];
self.automaticallyAdjustsScrollViewInsets = NO;
[self setupSubViews];
[self requestData];
}
/// 请求网络数据
- (void)requestData {
//nothing
}
/// 初始化并布局子view
- (void)setupSubViews {
NSArray *headTitles = @[@"第一个",@"第二个",@"第三个",@"第一个",@"第二个",@"第三个"];
//初始换headTitleview
self.headScrollView = [[HeadTitlesScrollView alloc] initWithFrame:CGRectMake(0, 0, ScreenW, 40) titles:headTitles];
self.headScrollView.backgroundColor = [UIColor blueColor];
[self.view addSubview:self.headScrollView];
//点击头部标签回调事件
__weak typeof(self) weakSelf = self;
self.headScrollView.blockClickHeadTitle = ^(NSInteger btnTag) {
[weakSelf clickHeadBtn:btnTag];
};
//初始化子vc
CGFloat originY = headHeight;
CGFloat childVCHeight = ScreenH - headHeight;
self.firstVC = [FirstViewController new];
self.firstVC.view.frame = CGRectMake(0, originY, ScreenW, childVCHeight);
self.secondVC = [SecondViewController new];
self.secondVC.view.frame = CGRectMake(0, originY, ScreenW-100, childVCHeight-100);
self.thirdVC = [ThirdViewController new];
self.thirdVC.view.frame = CGRectMake(0, originY, ScreenW, childVCHeight);
//默认添加第一个子vc
[self.view addSubview:self.firstVC.view]; //这个不能删除
[self addChildViewController:self.firstVC];
self.currentVC = self.firstVC;
}
/// 处理点击某个头部标签,展示对应的vc
/// @param btnTag 正在被点击按钮的tag
- (void)clickHeadBtn:(NSInteger)btnTag {
//点击的是同一个则返回
if (((self.currentVC == self.firstVC) && btnTag == 100) ||
((self.currentVC == self.secondVC) && btnTag == 101) ||
((self.currentVC == self.thirdVC) && btnTag == 102)) {
return;
}
//改变子vc
switch (btnTag) {
case 100:
//显示第一个vc
[self replaceOldVC:self.currentVC toNewVC:self.firstVC];
break;
case 101:
//显示第二个vc
[self replaceOldVC:self.currentVC toNewVC:self.secondVC];
break;
case 102:
//显示第三个vc
[self replaceOldVC:self.currentVC toNewVC:self.thirdVC];
break;
default:
break;
}
}
- (void)replaceOldVC:(UIViewController*)oldVC toNewVC:(UIViewController*)newVC {
[self addChildViewController:newVC];
[self transitionFromViewController:self.currentVC toViewController:newVC duration:1 options:UIViewAnimationOptionTransitionCrossDissolve animations:nil completion:^(BOOL finished) {
if (finished) {
//下面两句删除不会影响
[self.view addSubview:newVC.view];
[newVC didMoveToParentViewController:self];
//移除旧的的vc
[oldVC willMoveToParentViewController:nil];
[oldVC removeFromParentViewController];
self.currentVC = newVC;
}else {
self.currentVC = oldVC;
}
}];
}
@end
抽离封装的HeadTitlesScrollView
#import "HeadTitlesScrollView.h"
//以下都可以暴露给外面,动态设置
static CGFloat headScrollViewHeight = 40;
static CGFloat headScrollViewBtnWidth = 100;
static CGFloat lineWidth = 50.f;
//按钮的样式:颜色,字体,背景色等
//底部线的样式:颜色,高度,宽度等
@interface HeadTitlesScrollView () <UIScrollViewDelegate>
@property (nonatomic, strong) UIScrollView *mainScrollView;
@property (nonatomic, strong) UILabel *bLine;
@property (nonatomic, strong) NSArray *arrTitles;
@property (nonatomic, strong) NSMutableArray *mutArrBtns;
@property (nonatomic, strong) NSMutableArray *mutArrBottomLines;
@end
@implementation HeadTitlesScrollView
#pragma mark- 初始化
- (instancetype)initWithFrame:(CGRect)frame titles:(NSArray*)titles {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor whiteColor];
self.arrTitles = titles.copy;
self.mutArrBtns = [NSMutableArray array];
self.mutArrBottomLines = [NSMutableArray array];
headScrollViewHeight = self.frame.size.height;
[self setupSubViews];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
NSAssert(NO, @"请使用initWithFrame:titles:初始化");
return [self initWithFrame:CGRectZero titles:@[]];
}
- (instancetype)initWithFrame:(CGRect)frame {
return [self initWithFrame:frame titles:@[]];
}
#pragma mark- 设置并布局子view
- (void)setupSubViews {
//主scrollview
self.mainScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, ScreenW, headScrollViewHeight)];
self.mainScrollView.backgroundColor = [UIColor blueColor];
CGFloat contentSizeWidth = self.arrTitles.count*headScrollViewBtnWidth;
self.mainScrollView.contentSize = CGSizeMake(contentSizeWidth, 0);
// self.mainScrollView.pagingEnabled = YES;
self.mainScrollView.scrollEnabled = YES;
self.mainScrollView.bounces = NO;
self.mainScrollView.showsHorizontalScrollIndicator = NO;
[self addSubview:self.mainScrollView];
//头部scrollview添加button 和 button初始化
for (int i = 0; i<self.arrTitles.count; i++) {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.frame = CGRectMake(i*headScrollViewBtnWidth, 0, headScrollViewBtnWidth, headScrollViewHeight);
[btn setTitle:self.arrTitles[i] forState:UIControlStateNormal];
[btn setTitleColor:[UIColor purpleColor] forState:UIControlStateNormal];
btn.titleLabel.textAlignment = NSTextAlignmentCenter;
btn.tag = 100+i;
[self.mainScrollView addSubview:btn];
[btn addTarget:self action:@selector(clickHeadBtn:) forControlEvents:UIControlEventTouchUpInside];
// //选中标示线方案一:每一个btn对应一个下划线,通过hidden来控制
// UILabel *bottomLine = [UILabel new];
// bottomLine.backgroundColor = [UIColor blackColor];
// CGFloat lineOriginY = headScrollViewBtnWidth*i + (headScrollViewBtnWidth - lineWidth)/2.0;
// bottomLine.frame = CGRectMake(lineOriginY, headScrollViewHeight-2, lineWidth, 2);
// [self.mainScrollView addSubview:bottomLine];
// //默认第一个选中,显示下划线标识线
// bottomLine.hidden = !(i == 0);
// [self.mutArrBottomLines addObject:bottomLine];
}
//选中标示线方案二:一个line 通过改变offset.x来改变选中标识
UILabel *bottomLine = [UILabel new];
bottomLine.backgroundColor = [UIColor blackColor];
CGFloat lineOriginX = headScrollViewBtnWidth*0 + (headScrollViewBtnWidth - lineWidth)/2.0;
bottomLine.frame = CGRectMake(lineOriginX, headScrollViewHeight-2, lineWidth, 2);
[self.mainScrollView addSubview:bottomLine];
self.bLine = bottomLine;
}
/// 回调点击标签事件
/// @param currClickedBtn 当前被点击的btn
- (void)clickHeadBtn:(UIButton*)currClickedBtn {
NSInteger currIndex = currClickedBtn.tag -100;
// //改变选中下划线 + 方案一
// [self.mutArrBottomLines enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// UILabel *currLine = obj;
// currLine.hidden = !(idx == currIndex);
// }];
//改变选中下划线 + 方案二
CGFloat lineOriginX = headScrollViewBtnWidth*currIndex + (headScrollViewBtnWidth - lineWidth)/2.0;
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.bLine.frame = CGRectMake(lineOriginX, headScrollViewHeight-2, lineWidth, 2);
} completion:nil];
if (self.blockClickHeadTitle) {
self.blockClickHeadTitle(currClickedBtn.tag);
}
}
#pragma mark- UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
//监听contentoffset 改变标识下划线位置
}
@end
childViewController里子view布局时机注意
#import "SecondViewController.h"
@interface SecondViewController () <UITableViewDataSource,UITableViewDelegate>
@property (nonatomic, strong) UITableView *mainTb;
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor purpleColor];
// //不能在这布局;该vc的view此时还没布局完成,取的会是主容器view的frame
// [self.view addSubview:self.mainTb];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
//子view正确布局时机
[self.view addSubview:self.mainTb];
}
#pragma mark- UITableViewDatasource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 20;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellid"];
cell.textLabel.text = @"test";
return cell;
}
#pragma mark- getter
- (UITableView *)mainTb {
if (!_mainTb) {
_mainTb = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) style:UITableViewStylePlain];
_mainTb.delegate = self;
_mainTb.dataSource = self;
_mainTb.rowHeight = 100;
[_mainTb registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cellid"];
}
return _mainTb;
}
- (void)dealloc
{
NSLog(@"%s",__func__);
}
@end
效果图
结束语
addChildViewController参考链接
本来想放demo地址的,但是觉得还是自己敲一遍比较好,主要代码都在上面,能力有限 有什么不对的地方,欢迎指正[Salute]