什么是 UIViewController
UIViewController: 使用数据( Model )来构造视图( View )的基本控制单元( Controller )
UIViewController间的关系
- 并列关系
- 容器包含关系
- 系统自带容器控制器
- UINavigationController — 导航控制器
- UITabBarController — 标签控制器
- 自定义容器控制器
- 系统自带容器控制器
容器控制器
// 获取 ViewController 的父控制器
.parentViewController
// 判断 ViewController 的视图是否加载
.viewLoaded
自定义容器控制器
操作 | 通知系统包含关系变更 | 调整视图关系 | 更改ViewController包含关系 | 调整视图关系 | 通知系统包含关系变更 |
---|---|---|---|---|---|
ViewController加入一个容器 | |- addChildViewController | - addSubview | - didMoveToParentViewController | ||
ViewController移除一个容器 | - willMoveToParentViewController | - removeFromSuperview | - removeFromParentViewController | \ |
- (void)addChildVc:(UIViewController*)vc view:(UIView *)view
{
//
BOOL needAddToParent = !vc.parentViewController;
//
if (needAddToParent) [self addChildViewController:vc];
vc.view.frame = view.bounds;
//
[view addSubview:vc.view];
//
if (needAddToParent) [vc didMoveToParentViewController:self];
}
- (void)removeChildVc:(UIViewController*)vc
{
//
[vc willMoveToParentViewController:nil];
//
if (![vc isViewLoaded]) {
//
[vc removeFromParentViewController];
}
else {
//
[vc.view removeFromSuperview];
//
[vc removeFromParentViewController];
}
}
UIViewController 的状态和转场
状态变化图:
状态和对应方法:
状态 | 方法 |
---|---|
初始化 | - alloc init |
启动界面初始化 | — loadView |
加载中 | — viewDidLoad |
启动即将完毕 | — viewWillAppear:animated: |
启动完毕 | — viewDidAppear:animated: |
- loadView —- 初始化UIViewController View
UIViewController缺省持有属于自身的view
self.view 的设置采用懒加载的方式,即调用 self.view 时才会调用 loadView 方法
// self.view的设置
- (void)loadView
{
//po self->_view 输出为nil
NSLog(@"Before Load View");
[super loadView];
//po self->_view 输出不为nil
NSLog(@"After Load View");
}
- viewDidLoad — view 加载完成
处理和view相关的功能 - viewWillAppear & viewDidAppear
- viewWillDisappear & viewDidDisappear
UIViewController 转场
转场 —— UIViewController 间的展示切换
系统默认转场 —— Push / Modal
[self.navigationController pushViewController:animated:];
[self.navigationController popViewController:animated:];
[self presentViewController:animated:completion:];
[self dismissViewControllerAnimated:completion:];
状态和转场的关系
- 有动画转场、无动画转场
有动画转场 viewWillAppear 和 viewDidAppear 有时间差,转场完毕后才 viewDidAppear。无动画转场有顺序但是中间几乎没有时间差 - 手势交互完成转场、手势交互未完成转场
手势拖动时 viewWillAppear ,等到完全完成转场 viewDidAppear,如果手势未完成则不会调用 viewDidAppear
UINavigationController
UINavigationController 是系统的 Push / Pop 转场控件,为栈结构( FIFO )
// 栈结构中的所有 viewController
.viewControllers
// 处于栈顶的 viewController
.topViewController
UINavigationController 有 .navigationBar 和 .toolbar (默认不显示,显示:[self setToolbarHidden:NO animated:NO])。navigationBar 为 UINavigationBar 对象(继承于 UIView),.items 属性也是栈结构,保存各个 viewController 对应的 UINavigationItem 对象( .navigationItem ),UINavigationItem 不是一个 view ,而是一个 model
UIViewController 对象在 UINavigationController 中布局的设置
UIViewController 有 .edgesForExtendedLayout 属性,类型为 UIRectEdge 枚举,枚举值为:
UIRectEdgeNone / UIRectEdgeTop / UIRectEdgeBottom / UIRectEdgeLeft / UIRectEdgeRight / UIRectEdgeAll
- 全屏布局
UINavigationController 的 view 有多大则 UIViewController 的 view 就有多大(默认, UIRectEdgeAll == UIRectEdgeTop | UIRectEdgeBottom | UIRectEdgeLeft | UIRectEdgeRight )
UIViewController 有 .edgesForExtendedLayout 属性,类型为 UIRectEdge 枚举,枚举值为:
UIRectEdgeNone / UIRectEdgeTop / UIRectEdgeBottom / UIRectEdgeLeft / UIRectEdgeRight / UIRectEdgeAll ,全屏决定 UIViewController 的 view 的范围 - 延伸到不透明的bar
UIViewController 有 .extendedLayoutIncludesOpaqueBars 属性,类型为 BOOL 值(默认为 NO ),决定了 UIViewController 的 view 是否延伸到不透明的 bar
// 全延伸
self.edgesForExtendedLayout = UIRectEdgeAll;
// 不延伸到不透明 bar
self.extendedLayoutIncludesOpaqueBars = NO;
// 设置为不透明
self.navigationController.navigationBar.translucent = NO;
// 这种情况下即使 self.edgesForExtendedLayout 为 ALL 也不做延伸布局
// 为 YES 则可强制在不透明的情况下也布局上去
//self.extendedLayoutIncludesOpaqueBars = YES;
自定义导航栏样式
UINavigationBar 自定义样式属性
// 背景图
backgroundImage
// 背景色
barTintColor
// 毛玻璃透明
translucent
// 阴影分割线,改分割线之前要先把背景图替换掉,不然没效果
shadowImage
// 设置切掉边界则不会显示 shadowImage
clipToBounds
// 标题样式
titleTextAttributes
// 标题位置
titleVerticalPosition
// 返回按钮颜色
tintColor
// backIndicatorImage 和 backIndicatorTransitionMaskImage需要同时设置
// 返回按钮图片
backIndicatorImage
// 返回按钮图片(在转场中使用)
backIndicatorTransitionMaskImage
// 隐藏导航栏
navigationBarHidden
// 隐藏导航栏事件
// 滑动隐藏
self.navigationController.hidesBarsOnSwipe = YES;
// 点击屏幕隐藏
self.navigationController.hidesBarsOnTap = YES;
// 弹出键盘隐藏
self.navigationController.hidesBarsWhenKeyboardAppears = YES;
- 状态栏颜色和导航栏颜色分不开 —— 当我们设置背景色时,状态栏( status bar )的颜色也一并被修改
更改状态栏文字颜色的方法 :- 全局统一设置
// 已废弃
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
同时在info.plist 中添加 View controller-based status bar 设置值为 NO
- 通过 ViewController 设置
// ViewController和NavigationController 都要设置
- (UIStatusBarStyle)preferredStatusBarStyle;
- UINavigationBar 的全局设置
[UINavigationBar appearance] 可获取 UINavigationBar 实例,对这个实例进行的修改是全局的
UINavigationItem 样式定义
UINavigationBar 作为 View,需要一个自定义的 Model( UINavigationItem ) ,通过 Model 的配置,展示自定义的界面
UINavigationItem 的属性有:
// 如果设置了 titleView ,则 title 不起作用
.title
.titleView
// 类型为 UIBarButtonItem
.backBarButtonItem
.leftBarButtonItem \ .rightBarButtonItem
.leftBarButtonItems \ .rightBarButtonItems
UIBarButtonItem ,自定义 Button 的 Model ,可以:自定义宽度、加入自定义视图、响应自定义行为
- initWithImage:style:target:action:
- initWithBarButtonSystemItem:target:action:
- initWithTitle:style:target:action:
- initWithCustomView:
// 调整返回按钮文字的位置
- setBackButtonTitlePositionAdjustment:forBarMetrics:
// 占位用,固定宽度,可用于排版
// 一般情况下 UIBarButtonItem 在 UINavigationBar 的位置或间距固定,可用一个空白的 UIBarButtonItem 来调整位置
UIBarButtonItem *negSpaceItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
negSpaceItem.width = -10;
- UIBarButtonItem 的全局设置
[UIBarButtonItem appearance] 可获取 UIBarButtonItem 实例,对这个实例进行的修改是全局的
POP 的手势的失效
iOS7 以后 UINavigationController 有一个侧滑 POP 的手势( .interactivePopGestureRecognizer ),手指在屏幕边缘滑动,系统会判断手指拖动出来的大小来决定是否要执行控制器的Pop操作。导致这个手势失效的方式有:
- 自定义返回按钮
- navigationBarHidden
- navigationItem.hidesBackButton
.interactivePopGestureRecognizer 为 UIGestureRecognizer 类型,UIGestureRecognizer 有属性 .delegate ,可指向一个实现了 <UIGestureRecognizerDelegate> 协议的实例
viewController 只需要将 UINavagationController 的 interactivePopGestureRecognizer 指向自己,即不会导致 POP 手势失效
// 在 viewDidAppear 中让 delegate 指向自己
self.originalDelegate = self.navigationController.interactivePopGestureRecognizer.delegate;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
// 在 viewWillDisappear 中让 delegate 指向原实例,才会不丢失
self.navigationController.interactivePopGestureRecognizer.delegate = self.originalDelegate;
self.originalDelegate = nil;
<UINavigationControllerDelegate>
- navigationController:willShowViewController:animated:
- navigationController:didShowViewController:animated:
\\ 导航统计需求
@interface NavStatistic ()
@property (nonatomic, assign) NSInteger currentCount;
@property (nonatomic, weak) UIViewController *currentPage;
@property (nonatomic, assign) NSTimeInterval currentShowTime;
@end
@implementation NavStatistic
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
BOOL isPush = NO;
if (navigationController.viewControllers.count > self.currentCount) {
isPush = YES;
}
if (isPush) {
if (self.currentPage) {
NSLog(@"首次展示页面:%@ 来自 %@", NSStringFromClass([viewController class]), NSStringFromClass([self.currentPage class]));
}
else {
NSLog(@"首次展示页面:%@", NSStringFromClass([viewController class]));
}
}
if (self.currentPage) {
NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];
NSTimeInterval duration = currentTime - self.currentShowTime;
NSLog(@"页面 %@ 展示时长 %f", NSStringFromClass([self.currentPage class]), duration);
}
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
self.currentCount = [navigationController.viewControllers count];
self.currentPage = viewController;
self.currentShowTime = [[NSDate date] timeIntervalSince1970];
}
UITabBarController
.viewControllers
selectedViewController
selectedIndex
UITabBarController 的 viewControllers 数组大于5个标签页面则从第5个开始收起来
UITabBar 自定义样式属性
// 背景图
.backgroundImage
// 背景色
.batTintColor
// 毛玻璃透明
.translucent
// 阴影分割线
.shadowImage
// item宽度和间距只可以在 iPad 上设置
// item宽度
.itemWidth
// item间距
.itemSpace
// 选中的颜色
.tintColor
// 选中的标识图片
.selectionIndicatorImage
// Bar的样式
.barStyle
UITabBarItem 样式定义
UITabBar 作为 View,需要一个自定义的 Model( UITabBarItem ) ,通过 Model 的配置,展示自定义的界面
- initWithTitle:image:selectedImage:
.title
.image
.selectedImage
.badgeValue
.titlePositionAdjustment
- setTitleTextAttributes:forState:
自定义 Tab 的行为
UITabBarItem 并没有提供自定义的 Action ,UITabBarController 有一个 .delegate ,对象需实现 <UITabBarControllerDelegate> 协议
- tabBarController:shouldSelectViewController:
- tabBarController:didSelectViewController:
通过这两个方法,我们可以自定义 Tab 的行为
标签页是 UINavigationController 情况下 Push 后 TabBar 隐藏
SecondViewController *vc = [[SecondViewController alloc] init];
vc.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:vc animated:YES];