什么是 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];