ViewController 控制器详解

举报
SHQ5785 发表于 2024/05/16 09:26:18 2024/05/16
【摘要】 一、前言iOS 界面开发最重要的首属ViewController和View,ViewController是View的控制器,也就是一般的页面,用来管理页面的生命周期(它相当于安卓里的Activity,两者很像,但又有一些差异)。ViewController的特点是它有好几种。一种最基本的UIViewController,和另外三种容器:UINavigationController、UITa...

一、前言

iOS 界面开发最重要的首属ViewControllerViewViewControllerView的控制器,也就是一般的页面,用来管理页面的生命周期(它相当于安卓里的Activity,两者很像,但又有一些差异)。

ViewController的特点是它有好几种。一种最基本的UIViewController,和另外三种容器:UINavigationControllerUITabBarControllerUIPageViewController

所谓容器,就是它们本身不能单独用来显示,必须在里面放一个或几个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 将子控制器添加到导航控制器中的四种方式

第一种方式:

  1. 创建一个导航控制器:
UINavigationController *nav=[[UINavigationController alloc]init];

2.设置导航控制器为window的根视图:

self.window.rootViewController=nav;
  1. 添加:
YYOneViewController  *one = [[YYOneViewController alloc] init];

[nav pushViewController:one animated:YES];

第二种方式:

  1. 创建一个导航控制器:
UINavigationController *nav=[[UINavigationController alloc]init];
  1. 设置导航控制器为window根视图:
self.window.rootViewController=nav;
  1. 添加:
YYOneViewController  *one = [[YYOneViewController alloc] init];

[nav addChildViewController:one];

第三种方式:

  1. 创建一个导航控制器:
UINavigationController *nav=[[UINavigationController alloc]init];
  1. 设置导航控制器为window根视图:
self.window.rootViewController=nav;
  1. 添加:
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。可以配置左右或上下翻译,翻页效果可以配置滚动或者模拟翻书。

viewControllerBeforeviewControllerAfter回调方法控制页面切换。viewControllerBefore方法提供当前页面的前一个页面,viewControllerAfter方法提供当前页面的后一个页面。

注意⚠️:UIPageViewController有预加载机制,它会提前加载当前页面的前后页面。但是没有实现页面缓存机制,需要在外部做缓存。

如果页面非常多,但又是同一个类的实例,那么一般创建三个实例就够了,然后在viewControllerBeforeviewControllerAfter方法里循环使用这三个。

示例代码如下:

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]
    }
}

六、拓展阅读

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。