iOS14开发-UIViewController

介绍

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

推荐阅读更多精彩内容