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

状态变化图:

状态变化图

状态和对应方法:

状态 方法
初始化 - 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 ,可指向一个实现了 <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];

参考

Model-View-Controller
Customizing UINavigationBar

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容