iOS14开发-UIViewController

yungfan· 2021-02-22

介绍

UIViewController 可以理解为 App 的界面,负责管理 UIView 中显示的内容和用户的交互,主要有以下作用:

  • 负责创建和管理 UIView。
  • 响应用户与视图的交互。
  • 负责界面的切换与传值。
  • 响应设备的方向变化。
  • 有一些特殊的视图控制器(导航控制器、标签栏控制器)可以更加方便和规范地管理 UIView。

创建

storyboard

  • 初始化箭头指向的 UIViewController。
let vc = UIStoryboard(name: "storyboard名", bundle: nil).instantiateInitialViewController()
  • 初始化其他的 UIViewController。
let vc = UIStoryboard(name: "storyboard名", bundle: nil).instantiateViewController(withIdentifier: "Storyboard ID")

纯代码

let vc = UIViewController()

xib

这种方式本质是 xib 创建 UIView,然后让这个 UIView 成为 UIViewController 的默认 View。

  • 创建 UIViewController 的时候勾选了Also create XIB file,可以直接通过下面两种方式初始化:
// 方式一
let vc = UIViewController()

// 方式二
let vc = UIViewController(nibName: "xib的名字", bundle: nil)
  • 如果 UIViewController 与 xib 分别创建,直接使用上面的两种方式会报错,因为这种方式还需要自己处理 2 件事: (1)将 xib 文件 的File’s Owner的类绑定为 UIViewController。 (2)将File’s Ownerview属性设置为xib文件(拽线设置即可)。

view属性

入门知识里初步介绍了 UIViewController 与其属性view的关系,其实它们之间的关系没有那么简单,需要进一步分析。

生命周期顺序

init、init(nibName...)(初始化、分配内存)—> loadView(加载view)—> viewDidLoad(view已经加载)—> viewWillAppear(view即将显示)—> viewWillLayoutSubviews(将要布局子view)—> viewDidLayoutSubviews(已经布局子view)—> viewDidAppear(view已经显示)—> viewWillDisappear(view即将消失)—> viewDidDisappear(view已经消失)—> dealloc(释放内存)

延迟加载

UIViewController 的 view 的延迟加载:第一次使用的时候才会去加载,并不是创建 UIViewController 时加载。

  • 验证:通过纯代码跳转时发现屏幕黑色且卡顿,设置颜色后正常。

loadView方法

  • 用于创建 UIViewController 的 view。
  • 当 UIViewController 访问 view 时如果发现为 nil,就会调用 loadView 方法。
  • loadView 方法执行完会自动执行 viewDidLoad。
  • loadView 方法大概的实现思路如下:
func loadView() {   
    // 如果UIViewController是通过storyboard创建的,从storyboard中加载视图来创建view
    if storyboard创建 {
        // ...
        return
    }

     // 如果UIViewController是通过xib创建的,从xib中加载视图来创建view
    if xib创建 {
        // ...
        return
    }

    // 如果上面都不是,则会创建一个普通的view视图
    let view = UIView(frame: UIScreen.main.bounds)    
    self.view = view
}

重写loadView方法

  • 该方法要么不重写,如果重写一定要注意:
    • 必须在方法里给 UIViewController 的 view 赋值。
    • 不要调用super.loadView()
    • 不要手动调用该方法。
override func loadView() {
    let myView = UIView(frame: UIScreen.main.bounds)
    view = myView
}
  • 一旦重写,其他创建 view 的方式都会失效,因为决定 UIViewController 的 view 优先级为:loadView > storyboard > nibName > xib。

跳转

从一个 UIViewController 跳转到另一个 UIViewController 有两种方式,分别为模态跳转导航跳转

模态跳转

storyboard

  • 直接拽线,选择Present Modally,这根线是一个 UIStoryboardSegue 对象(简称 Segue),可以设置相关的属性。
  • 自动型 Segue
    • 直接跳转,无需条件。
    • 通过当前 UIViewController 某个具体的控件(如按钮)拽线到另一个 UIViewController。
  • 手动型 Segue
    • 从当前 UIViewController 拽线到另一个 UIViewController,需要给这根线设置identifier
    • 在程序中需要跳转的地方调用performSegue(withIdentifier: , sender:)方法完成跳转。

纯代码

  • 跳转界面:present
  • 返回界面:dismiss
  • iOS 13 之后,模态跳转并非全屏显示,如果需要全屏显示,需要手动设置。

两个概念

  • presentedViewController: 被 present 的控制器。
  • presentingViewController:正在 presenting 的控制器。

导航跳转

这种操作的前提是 UIViewController 包含在 UINavigationController 中。

storyboard

  • 直接拽线,选择Show
  • 自动型 Segue 和 手动型 Segue 跟模态跳转一样。

纯代码

  • 跳转界面
    • navigationController?.pushViewController
  • 返回界面
    • 左上角的返回按钮。
    • 屏幕边缘滑动。
    • navigationController?.popViewController

传值

顺向传值

顺向传值即按照 UIViewController 跳转的顺序进行传值,比如控制器A跳转到控制器B,A向B的传值就是顺向传值。顺向传值只需要在目标控制器中声明需要接收的参数,然后在源控制器中进行传值即可。

  • storyboard 方式。
  • 代码方式。

逆向传值

逆向传值即按照 UIViewController 跳转的顺序反向进行传值,比如控制器A跳转到控制器B,控制器B在返回控制器A时进行传值,这种方式就是逆向传值。逆向传值不能像顺向传值那样简单进行,需要借助于下面三种方式。

代理

代理模式需要弄清楚被代理对象和代理对象,然后按照下面的规范进行。

  • 被代理对象(需要传值的 UIViewController)
    • 声明协议,在协议中定义传值方法,方法的参数个数与类型取决于需要传值的个数和类型。
    • UIViewController 中声明一个代理属性。
    • 在需要传值的地方调用代理属性的方法完成传值。
  • 代理对象(接收值的 UIViewController)
    • 实现被代理对象声明的协议,实现协议中的方法,拿到传过来的值进行使用。
    • 需要设置当前的 UIViewController 为被代理 UIViewController 中的代理属性。

闭包

可以理解为代理模式中协议的闭包替代,比代理模式更简单。

  • 需要传值的 UIViewController
    • 声明一个闭包属性,闭包的参数个数与类型取决于需要传值的个数和类型,闭包的返回值一般为 Void。
    • 在需要传值的地方调用闭包完成传值。
  • 接收值的 UIViewController
    • 实现需要传值的 UIViewController 中的闭包属性,在闭包的实现中拿到传过来的值进行使用。

通知

  • 接收值的 UIViewController 通过监听通知捕获传过来的值。
 NotificationCenter.default.addObserver(self, selector: #selector(handlerNoti), name: NSNotification.Name("abc"), object: nil)
  • 需要传值的 UIViewController 将值通过通知的方式发送出去。
NotificationCenter.default.post(name: NSNotification.Name("abc"), object: nil, userInfo: ["info" : inputTf.text!])
  • 需要先监听,后发送
  • iOS 9 之后 NSNotificationCenter 无需手动移除观察者。

常见ViewController

UIAlertController

  • 警告(对话框)控制器。
  • 用一个对话框进行信息的提示,通过模态形式弹出。
  • 有两种样式:alertactionSheet
  • 按钮通过 UIAlertAction 添加,有 3 种样式:defaultcanceldestructive,一个 UIAlertController 中只能有一个cancel样式的 UIAlertAction。
  • 基本使用
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func showAlert(_ sender: Any) {
        let alertVC = UIAlertController(title: "温馨提示", message: "天气转凉,大家注意保暖,小心感冒", preferredStyle: .alert)

        let ok = UIAlertAction(title: "OK", style: .default) { _ in
            print("点击了ok")
        }

        let cancel = UIAlertAction(title: "Cancel", style: .cancel) { _ in
            print("点击了cancel")
        }

        alertVC.addAction(ok)
        alertVC.addAction(cancel)

        present(alertVC, animated: true, completion: nil)
    }

    @IBAction func showSheet(_ sender: Any) {
        let alertVC = UIAlertController(title: "选择头像", message: "请选择合适的方式来处理", preferredStyle: .actionSheet)

        let ok = UIAlertAction(title: "相册", style: .default) { _ in
            print("用户选择了相册")
        }

        let des = UIAlertAction(title: "拍照", style: .destructive) { _ in
            print("用户选择了拍照")
        }

        let cancel = UIAlertAction(title: "取消", style: .cancel) { _ in
            print("点击了取消")
        }

        alertVC.addAction(ok)
        alertVC.addAction(des)
        alertVC.addAction(cancel)

        present(alertVC, animated: true, completion: nil)
    }
}
  • 登录案例:用 UIAlertController 代替 print 打印。

UINavigationController

  • 导航控制器。
  • 可以展示多个 UIViewController,这些 UIViewController 是层级关系。
  • 它的 View 由三部分组成,最上面的UINavigationBar,最下面默认隐藏的UIToolBar,中间是 UIViewController 的view
  • 通过栈管理 UIViewController:先进后出。
    • pushViewController:压栈。
    • popViewController:出栈。
  • 通过 UINavigationItem 设置 title、leftBarButtonItem、rightBarButtonItem等。

UINavigationBar和UINavigationItem的关系

  • UINavigationBar是 UINavigationController 的属性,其属性设置会影响内部所有的 UIViewController。
  • UINavigationItem是 UIViewController 的属性,用于配置当前 UIViewController 显示时UINavigationBar上显示的内容。
  • UINavigationBar内部也维持一个栈,栈中存放的是一个个 UINavigationItem。当一个 UIViewController push 到 UINavigationController 时,它的UINavigationItem也会被 push 进 UINavigationBar的栈。因此UINavigationBar的栈和 UINavigationController 的栈一一对应。

UINavigationBar 的内容显示

标题
  • 如果当前 UIViewController 设置了titleView属性,则展示标题视图。
  • 如果当前 UIViewController 设置了title属性,则显示标题文字。
  • 如果都没设置,则显示空白。
  • iOS11 之后可以设置大标题。可以通过 storyboard 直接设置,也可以通过如下的代码设置:
// 所有界面显示大标题
navigationController?.navigationBar.prefersLargeTitles = true
// 当前界面是否显示大标题,never表示不显示大标题即显示小标题        
navigationItem.largeTitleDisplayMode = .never
右侧按钮
  • 如果当前 UIViewController 设置了rightBarButtonItem属性,则显示右侧按钮,否则显示空白。
左侧按钮
  • 如果当前 UIViewController 设置了leftBarButtonItem属性,则显示左侧按钮。
  • 如果前一个 UIViewController 设置了backButtonItem属性,则显示返回按钮。
  • 如果前一个 UIViewController 设置了title属性,则显示标题文字封装的返回按钮。
  • 如果以上都未设置,则展示文字Back封装的返回按钮。

注意:默认情况下返回按钮和左侧按钮是不同时显示的,只显示返回按钮而不显示左侧按钮。

返回按钮
  • 如果当前 UIViewController 设置了leftBarButtonItem属性,则默认的返回按钮会被替代,自带的返回和从屏幕边缘滑动返回的效果失效,此时只能通过popViewController返回。
  • 如果前一个 UIViewController 设置了backButtonItem属性或设置了backButtonTitle,可以起到更改返回按钮文字和图片的目的,但是返回按钮的<图标会一直存在,这种方式自带的返回和从屏幕边缘滑动返回的效果依然有效。
颜色问题
  • UINavigationBar 的颜色:可以通过 UINavigationBar 的barTintColor设置。
  • UINavigationBar 上面内容的渲染颜色:默认情况下,按钮或系统图片按钮都会渲染成蓝色,可以通过 UINavigationBar 的tintColor设置。

案例

  • storyboard 使用。
  • 纯代码使用。
  • 自定义使用。

UITabBarController

  • 标签栏控制器。
  • 可以展示多个 UIViewController,这些 UIViewController 是平级关系。但展示的 UIViewController 最多不超过5个,否则会折叠。
  • 它的 View 由两部分组成,上面是 UIViewController 的view,下面是UITabBar
  • 通过addChildViewController添加 UIViewController,通过UIViewController 的UITabBarItem属性设置展示的文字、默认图片、选中图片和角标。
  • 默认已经实现了UITabBarDelegate

UITabBarControllerDelegate

  • UITabBarController 还提供一个代理属性,通过它可以设置一个代理 UITabBarControllerDelegate。
  • 监听切换 UIViewController
    • 通过 UITabBarDelegate 的tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem)方法。
    • 通过 UITabBarControllerDelegate 的tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController)方法。

颜色问题

UITabBar的颜色

可以通过 UITabBar 的barTintColor设置。

渲染颜色
  • 图片一般由设计师统一设计,需要设置标题文字颜色以适应图片。
  • 方式一:每个 UIViewController 单独设置。
// 默认文字颜色
vc.tabBarItem.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.white], for: .normal)
// 选中文字颜色
vc.tabBarItem.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.orange], for: .highlighted)
  • 方式二:Appearance统一设置。
let item = UITabBarItem.appearance()
// 默认文字颜色
item.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.white], for: .normal)
// 选中文字颜色
item.setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.orange], for: .highlighted)
  • 方式三:iOS 10 之后可以统一设置选中和未选中颜色。(推荐使用
// 选中的图片文字颜色
vc.tabBarController?.tabBar.tintColor = UIColor.orange
// 未选中的文字颜色
vc.tabBarController?.tabBar.unselectedItemTintColor = UIColor.white

// 角标的背景色
vc.tabBarItem.badgeColor = UIColor.orange
// 角标的颜色
vc.tabBarItem.badgeTextAttributes(for: .normal) = UIColor.white

案例

  • storyboard 使用。
  • 纯代码使用。
  • 自定义使用。

其他

  • UITableViewController:表视图控制器,集成了 UITableView 的视图控制器。
  • UICollectionViewController:集合视图控制器,集成了 UICollectionView 的视图控制器。