UIViewController

什么是 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 的状态和转场

状态变化图:

UIViewController_第1张图片
状态变化图

状态和对应方法:

状态 方法
初始化 - 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 )的颜色也一并被修改
    更改状态栏文字颜色的方法 :
    1. 全局统一设置
// 已废弃
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];

同时在info.plist 中添加 View controller-based status bar 设置值为 NO

  1. 通过 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 ,可指向一个实现了 协议的实例
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;

- 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 ,对象需实现 协议
- tabBarController:shouldSelectViewController:
- tabBarController:didSelectViewController:
通过这两个方法,我们可以自定义 Tab 的行为

标签页是 UINavigationController 情况下 Push 后 TabBar 隐藏

SecondViewController *vc = [[SecondViewController alloc] init];
vc.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:vc animated:YES];

参考

Model-View-Controller
Customizing UINavigationBar

你可能感兴趣的:(UIViewController)