ViewController 控制器详解
一、前言
iOS 界面开发最重要的首属ViewController
和View
,ViewController
是View
的控制器,也就是一般的页面,用来管理页面的生命周期(它相当于安卓里的Activity
,两者很像,但又有一些差异)。
ViewController
的特点是它有好几种。一种最基本的UIViewController
,和另外三种容器:UINavigationController
、UITabBarController
、UIPageViewController
。
所谓容器,就是它们本身不能单独用来显示,必须在里面放一个或几个UIViewController
。
不同容器有不同的页面管理方式和展示效果:
UINavigationController
用于导航栏管理页面;UITabBarController
用于底部tab管理页面;UIPageViewController
用于切换器管理页面;
容器还可以嵌套,比如把UITabBarController
放进UINavigationController
里面,这样在tab页面里,可以用启动导航栏样式的二级子页面。
二、UIViewController
这是最简单的页面,没有导航栏。
使用present
方法展示,展示时从底部弹起,可以用下滑手势关闭,也可以多次启动叠加多个页面。
代码实现如下:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
title = "\(self.hash)"
var label = UIButton(frame: CGRect(x: 10, y: 100, width: 300, height: 100))
label.setTitle("present ViewController", for: .normal)
view.addSubview(label)
label.addTarget(self, action: #selector(presentVC), for: .touchUpInside)
label = UIButton(frame: CGRect(x: 10, y: 200, width: 300, height: 100))
label.setTitle("present NavigationController", for: .normal)
view.addSubview(label)
label.addTarget(self, action: #selector(presentNC), for: .touchUpInside)
label = UIButton(frame: CGRect(x: 10, y: 300, width: 300, height: 100))
label.setTitle("push ViewController", for: .normal)
view.addSubview(label)
label.addTarget(self, action: #selector(pushVC), for: .touchUpInside)
label = UIButton(frame: CGRect(x: 10, y: 400, width: 300, height: 100))
label.setTitle("present TabbarController", for: .normal)
view.addSubview(label)
label.addTarget(self, action: #selector(presentTC), for: .touchUpInside)
label = UIButton(frame: CGRect(x: 10, y: 500, width: 300, height: 100))
label.setTitle("present PageViewController", for: .normal)
view.addSubview(label)
label.addTarget(self, action: #selector(presentPC), for: .touchUpInside)
}
@objc func presentVC() {
let vc = ViewController()
vc.view.backgroundColor = .darkGray
present(vc, animated: true)
}
@objc func presentNC() {
let vc = ViewController()
vc.view.backgroundColor = .gray
let nc = UINavigationController(rootViewController: vc)
present(nc, animated: true)
}
@objc func presentTC() {
let tc = MyTabbarController()
tc.view.backgroundColor = .blue
let nc = UINavigationController(rootViewController: tc)
present(nc, animated: true)
}
@objc func presentPC() {
let pc = MyPageViewController()
pc.view.backgroundColor = .red
let nc = UINavigationController(rootViewController: pc)
present(nc, animated: true)
}
@objc func pushVC() {
let vc = ViewController()
vc.view.backgroundColor = .purple
if let nc = navigationController {
nc.pushViewController(vc, animated: true)
} else {
print("navigationController nil!")
}
}
}
三、UINavigationController
这是最常用的页面导航方式,顶部展示导航栏,有标题、返回按钮。
使用pushViewController
方法展示,展示时从右往左出现,可以用右滑手势关闭,也可以多次启动叠加多个页面。
注意⚠️:UINavigationController
用来管理一组UIViewController
,这些UIViewController
共用一个导航栏。
一般来说,UINavigationController
能很好地控制导航栏上面的元素显示和转场效果。
如果需要定制导航栏元素,尽量修改UIViewController
的导航栏,不要直接修改UINavigationController
的导航栏。
示例代码如下:
#import "MeetingVC.h"
@property (nonatomic, strong) MeetingVC *meetVC;
@property (nonatomic, strong) BPGeneralContainerWebPageViewController *webPageVC;
// 视图入栈
self.meetVC = [[MeetingVC alloc] init];
[self.navigationController pushViewController:self.meetVC animated:YES];
// 视图出栈
[self.webPageVC.navigationController popViewControllerAnimated:YES];
其中,self.navigationController
是当前页面。
3.1 UINavigationController导航控制器初始化 & 导航控制器栈的push和pop跳转理解
(1)导航控制器初始化的时候一般都有一个根视图控制器,导航控制器相当于一个栈,里面装的是视图控制器,最先进去的在最下面,最后进去的在最上面。在最上面的那个视图控制器的视图就是这个导航控制器对外展示的界面,也就是用户看到的界面。
(2)我们需要把导航控制器加载到APP中,需要把这个导航控制器设置为window的根视图控制器(都是控制器类,可以赋值),这样就相当于加载到了window里。
(3)我们要在栈中新增或者删除一个视图控制器,就需要得到导航控制器,一般在栈中的所有视图控制器都有一个self.navigationController
,意思是我的导航控制器,也就是这个视图控制器所在的导航控制器,这样就拿到了导航控制器。
(4)栈中新增视图控制器用pushViewController
,其实就是push进去一个,这样对于用户而言就是打开一个新界面了。
(5)栈中删除一个视图控制器用popViewControllerAnimated
,当然这个pop只能pop最上面的那个,对于用户而言相当于从当前视图回到上一级视图。
(6)其实这个push和pop对于用户而言都是打开和跳转页面的一个操作。而pop有更多地操作方法,如一下子pop掉只剩下一个根视图控制器,那么就相当于从好几层直接回到最原始的主页面。也可以指定pop几个,以跳转到指定的页面。
(7)最重要的应该就是这个push和pop方法,而pop有很多种,这个理解后就不难记忆。
如果一级一级的返回示例如下:
[self.navigationController popViewControllerAnimated:Yes];
返回根页面示例如下 :
self.navigationController popToRootViewController]
返回指定的某级页面示例如下:
第N级
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:N] animated:YES];
或指定控制器
//遍历控制器for (UIViewController *controller in self.navigationController.viewControllers)
{
if ([controller isKindOfClass:[你要跳转到的Controller class]]) {
[self.navigationController popToViewController:controller animated:YES];
}}
一次性pop到上上一级
// 获取当前视图层级
int index = (int)[[self.navigationController viewControllers]indexOfObject:self];
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:(index -2)] animated:YES];
3.2 将子控制器添加到导航控制器中的四种方式
第一种方式:
- 创建一个导航控制器:
UINavigationController *nav=[[UINavigationController alloc]init];
2.设置导航控制器为window的根视图:
self.window.rootViewController=nav;
- 添加:
YYOneViewController *one = [[YYOneViewController alloc] init];
[nav pushViewController:one animated:YES];
第二种方式:
- 创建一个导航控制器:
UINavigationController *nav=[[UINavigationController alloc]init];
- 设置导航控制器为window根视图:
self.window.rootViewController=nav;
- 添加:
YYOneViewController *one = [[YYOneViewController alloc] init];
[nav addChildViewController:one];
第三种方式:
- 创建一个导航控制器:
UINavigationController *nav=[[UINavigationController alloc]init];
- 设置导航控制器为window根视图:
self.window.rootViewController=nav;
- 添加:
YYOneViewController *one = [[YYOneViewController alloc] init];
nav.viewControllers=@[one];(添加到导航控制器的栈中)
说明:nav.childViewControllers
属性是只读的,因此不能像下面这样写。nav.childViewControllers = @[one];
第四种方式:
YYOneViewController *one=[[YYOneViewController alloc]init];
UINavigationController *nav=[[UINavigationController alloc]initWithRootViewController:one];
完整示例代码如下:
#import "AppDelegate.h"
#import "ViewController.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//1.窗口初始化
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
//2.视图控制器
ViewController *vc = [[ViewController alloc] init];
//VC.view.backgroundColor = [UIColor cyanColor];
//[self.window makeKeyAndVisible];
//3.创建一个导航控制器对象,并将rootVC作为导航控制器的根视图控制器
UINavigationController *navCtr = [[UINavigationController alloc] initWithRootViewController:vc];
//简易更改外观
[[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:@"navBg.png"] forBarMetrics:UIBarMetricsDefault];
//4.将导航控制器设置为根视图控制器
self.window.rootViewController =navCtr;
//5.关键步骤:让当前窗口作为keyWindow(唯一性)主窗口并且可见
[self.window makeKeyAndVisible];
return YES;
}
@end
ViewController.m文件如下:
#import "ViewController.h"
#import "FirstViewController.h"
@interface ViewController ()
@end
@implementation ViewController
/*
导航控制器->视图控制器->视图
当根控制器设置为导航控制器时
UINavigationController 是用于构建分层应用程序的主要工具,主要采用栈形式来实现视图。任何类型的视图控制器都可放入栈中。在设计导航控制器时需要指定根视图即用户看到的第一个视图。根视图控制器是被导航控制器推入到栈中的第一个视图控制器。当用户查看下一个试图时,栈中将加入一个新的视图控制器,它所控制的视图将展示给用户。我们可以通过导航按钮来操作分层的应用程序,用它来控制视图的推入或推出
*/
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"ViewController";
self.view.backgroundColor = [UIColor purpleColor];
UIBarButtonItem *rightBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(myAdd)];
self.navigationItem.rightBarButtonItem = rightBtn;
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.frame = CGRectMake(90, 90, 200, 50);
[btn setTitle:@"Go to the next" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
btn.backgroundColor = [UIColor whiteColor];
[self.view addSubview:btn];
[btn addTarget:self action:@selector(btnPushClick) forControlEvents:UIControlEventTouchUpInside];
}
#pragma mark - 自定义方法
#pragma mark -UIBarButtonItem的右耳目关联方法
- (void)myAdd
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Touch" message:@"添加某种功能" delegate:self cancelButtonTitle:@"Good Job" otherButtonTitles: nil];
[alert show];
}
#pragma mark - 按钮关联方法-压栈
- (void)btnPushClick
{
//看到的是栈顶视图,想看下一个必需把它压进来
FirstViewController *firstVC = [[FirstViewController alloc] init];
[self.navigationController pushViewController:firstVC animated:YES];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
FirstViewController.m文件如下:
#import "FirstViewController.h"
@interface FirstViewController ()
@end
@implementation FirstViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"FirstViewController";
self.view.backgroundColor = [UIColor lightGrayColor];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.frame = CGRectMake(90, 90, 200, 50);
[btn setTitle:@"Go to the next" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
btn.backgroundColor = [UIColor whiteColor];
[self.view addSubview:btn];
[btn addTarget:self action:@selector(btnPushClick) forControlEvents:UIControlEventTouchUpInside];
}
#pragma mark - 自定义方法
#pragma mark - 按钮关联方法
- (void)btnPushClick
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Congrautulations" message:@"当前为最后一页" delegate:self cancelButtonTitle:@"取消" otherButtonTitles: nil];
[alert show];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
四、UITabBarController
这个一般用来做主页面的展示,下面配置多个tab,用于切换页面。
示例代码如下:
class MyTabbarController: UITabBarController {
init() {
super.init(nibName: nil, bundle: nil)
self.tabBar.backgroundColor = .gray
let vc1 = ViewController()
vc1.tabBarItem.image = UIImage(named: "diamond")
vc1.tabBarItem.title = "tab1"
vc1.view.backgroundColor = .red
let vc2 = ViewController()
vc2.tabBarItem.image = UIImage(named: "diamond")
vc2.tabBarItem.title = "tab2"
vc2.view.backgroundColor = .blue
let vc3 = ViewController()
vc3.tabBarItem.image = UIImage(named: "diamond")
vc3.tabBarItem.title = "tab3"
vc3.view.backgroundColor = .purple
self.viewControllers = [
vc1,
vc2,
vc3,
]
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
五、UIPageViewController
这个用来做翻页的页面,比如电子书或者广告banner。可以配置左右或上下翻译,翻页效果可以配置滚动或者模拟翻书。
用viewControllerBefore
和viewControllerAfter
回调方法控制页面切换。viewControllerBefore
方法提供当前页面的前一个页面,viewControllerAfter
方法提供当前页面的后一个页面。
注意⚠️:UIPageViewController
有预加载机制,它会提前加载当前页面的前后页面。但是没有实现页面缓存机制,需要在外部做缓存。
如果页面非常多,但又是同一个类的实例,那么一般创建三个实例就够了,然后在viewControllerBefore
和viewControllerAfter
方法里循环使用这三个。
示例代码如下:
class MyPageViewController: UIPageViewController, UIPageViewControllerDataSource {
lazy var vcs = [
ViewController(),
ViewController(),
ViewController(),
ViewController(),
ViewController(),
]
init() {
super.init(transitionStyle: .scroll, navigationOrientation: .horizontal)
self.dataSource = self
let vc1 = ViewController()
vc1.view.backgroundColor = .red
let vc2 = ViewController()
vc2.view.backgroundColor = .blue
let vc3 = ViewController()
vc3.view.backgroundColor = .purple
let vc4 = ViewController()
vc4.view.backgroundColor = .gray
vcs = [vc1,vc2,vc3,vc4
]
self.setViewControllers([vcs[0]], direction: .forward, animated: false)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let i = (vcs.firstIndex(of: viewController as! ViewController) ?? 0) - 1
if i < 0 {
return nil
}
return vcs[i]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let i = (vcs.firstIndex(of: viewController as! ViewController) ?? 0) + 1
if i >= vcs.count {
return nil
}
return vcs[i]
}
}
六、拓展阅读
- 点赞
- 收藏
- 关注作者
评论(0)