SwiftUI和iOS核心类UIViewController
SwiftUI和iOS核心类UIViewController

SwiftUI和iOS核心类UIViewController

UIViewController 是 iOS 开发中非常核心的类,用来表示一个屏幕上的界面或页面。在App 中,每一个界面(比如设置页、详情页、登录页)背后其实都是一个 UIViewController 或其子类。

UIViewController 就是“页面”,控制着页面的视图、生命周期事件和交互行为。

简单来说:UIViewController 是一个控制器对象,它负责管理一块区域的用户界面(UI)和相关行为逻辑。

比如:

一个登录页是一个 LoginViewController

一个相册页面是一个 PhotoGalleryViewController

一个设置页是一个 SettingsViewController

可以把它理解为「页面控制中心」:在这个页面上看到的 UI 元素、交互逻辑,甚至跳转行为,全部都由这个控制器来负责。

一个 UIViewController 通常负责:

1、显示界面:加载并管理它的视图。

2、生命周期管理:控制视图的显示、消失、加载,如viewDidLoad()、viewWillAppear()等。

3、导航和切换界面:通过present()或navigationController.push()进行页面跳转。

4、响应用户交互:管理按钮、手势等事件响应。

5、和系统协作:如响应状态栏变化、深色模式、内存警告等。

管理内容

1、视图层级结构

它默认管理一个视图(view),可以往这个视图中添加按钮、图片、标签、表格等 UI 元素。

override func viewDidLoad() {
    super.viewDidLoad()
    let label = UILabel()
    label.text = "欢迎回来"
    label.frame = CGRect(x: 100, y: 200, width: 200, height: 50)
    view.addSubview(label)
}

2、生命周期管理

UIViewController 生命周期是理解 iOS 开发中视图控制流程的关键。

以控制器从创建 ➝ 显示 ➝ 消失为例:

1、init(coder:) / init(nibName:bundle:):控制器被创建时调用,用于初始化变量,不涉及视图。

2、loadView():手动创建view树(如不适应storyboard),通常无需重写,除非想要自定义整个视图层级。

3、viewDidLoad():视图加载完毕,只执行一次,用于初始化视图、绑定数据、添加子视图。

4、viewWillAppear(_:):视图即将出现在屏幕上,每次出现前调用,用于准备UI(如刷新数据、控制导航栏显示)。

5、viewDidAppear(_:):视图已完全显示在屏幕上,启动动画、请求网络、开启监听。

6、viewWillDisappear(_:):视图即将被移除/覆盖,保存状态、关闭键盘、停止动画。

7、viewDidDisappear(_:):视图完全移除或被覆盖,释放资源、移除监听器。

8、deinit:控制器被销毁时,释放资源、取消通知等(只在非强引用循环下释放)。

3、用户交互与事件响应

它可以处理按钮点击、手势、通知、键盘事件等。

4、页面跳转(导航)

通过 present、dismiss 或 UINavigationController 实现页面切换。

let vc = ProfileViewController()
self.present(vc, animated: true)

或在导航控制器中:

navigationController?.pushViewController(vc, animated: true)

常用子类

UIViewController 是 iOS 应用中所有“页面”的基类,Apple 和开发者通常会通过其子类来实现特定类型的页面或行为。

1、UIViewController:基础控制器,最常用的基类,适合自定义任意页面。

2、UITableViewController:列表控制器,用于展示表格数据,比如设置页、联系人列表等。

3、UICollectionViewController:网格控制器,用于展示网格或卡片布局,比如相册、商城等。

4、UIPageViewController:分页控制器,用于左右滑动翻页,比如欢迎页、图文阅读器等。

5、UINavigationController:导航控制器,提供“返回”功能,管理页面的导航栈。

6、UITabBarController:标签栏控制器,底部有多个 tab 页的结构,如微信、微博首页结构。

7、UIAlertController:警告弹窗控制器,用于弹出警告、确认、选择等弹窗。

8、UISplitViewController:分栏控制器,iPad 上常用于主/从界面结构(左侧菜单 + 右侧内容)。

控制器切换方式

在 UIKit 中,UIViewController 提供多种方式来显示另一个控制器,包括但不限于:

1、present(_:animated:completion:):模态弹出一个控制器(全屏或部分屏),覆盖当前页面,适用于登陆、设置等页面。

let vc = SomeViewController()
present(vc, animated: true)

可以通过 override func present(…) 来拦截弹出行为。

override func present(_ viewControllerToPresent: UIViewController, animated: Bool, completion: (() -> Void)? = nil) {
    // 拦截系统弹出行为
    if viewControllerToPresent is UIAlertController {
        print("拦截了系统弹窗")
        return
    }
    super.present(viewControllerToPresent, animated: animated, completion: completion)
}

2、dismiss(animated:completion:):关闭当前模态控制器,返回上一个控制器(页面)。

dismiss(animated: true)

通常不重写,但是可以控制它何时触发,比如延迟、验证输入等。

3、navigationController?.pushViewController(_:animated:):使用导航控制器压栈(跳转到下一个控制器),适合练习浏览内容(比如新闻详情页)。

let detailVC = DetailViewController()
navigationController?.pushViewController(detailVC, animated: true)

拦截用法(一般在NavigationController的子类中):

override func pushViewController(_ viewController: UIViewController, animated: Bool) {
    print("将跳转到 \(viewController)")
    super.pushViewController(viewController, animated: animated)
}

4、navigationController?.popViewController(animated:):从导航控制器中出栈(返回上一页)。

navigationController?.popViewController(animated: true)

通常不重写,但可以添加拦截逻辑,例如弹出确认框、保存草稿等。

5、addChild(_:) + view.addSubview(…):将一个控制器作为子控制器添加到当前控制器,比如分页标签控制器,每个标签是一个子控制器。

addChild(childVC)
view.addSubview(childVC.view)
childVC.didMove(toParent: self)

可以包装成组件化结构,但不建议重写系统方法,更多是组合使用。

6、transition(from:to:duration:options:animations:completion:):两个子控制器之间的切换(比如tab的切换),用于嵌套场景。

transition(from: currentVC, to: nextVC, duration: 0.3, options: [.transitionFlipFromLeft], animations: nil, completion: nil)

用于自定义容器控制器,适合自定义动画,但一般不重写,而是直接调用。

在SwiftUI中使用UIViewController

    通常不会直接使用 UIViewController,而是会自己定义一个子类。

    然后在 AppDelegate/SceneDelegate 里设置它为根控制器,或者在当前控制器中使用 present 或 push 跳转它。

1、UIViewController生命周期示例

下面将在Swift中展示UIViewController生命周期。

1、创建UIViewController子类代码:

class DemoViewController: UIViewController {
    override func loadView() {
        super.loadView()
        print("loadView")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        print("viewDidLoad")
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        print("viewWillAppear")
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        print("viewDidAppear")
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        print("viewWillDisappear")
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        print("viewDidDisappear")
    }

    deinit {
        print("deinit called - ViewController 被释放")
    }
}	

2、SwiftUI包装器:

struct DemoViewControllerWrapper: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> DemoViewController {
        return DemoViewController()
    }

    func updateUIViewController(_ uiViewController: DemoViewController, context: Context) {
        // 不需要处理更新
    }
}

3、在SwiftUI中使用:

struct ContentView: View {
    var body: some View {
        DemoViewControllerWrapper()
            .edgesIgnoringSafeArea(.all)
        
    }
}

Xcode在进入视图后,输出:

loadView
viewDidLoad
viewWillAppear
viewDidAppear

与生命周期中的loadView()、viewDidLoad()、viewWillAppear(_:)和viewDidAppear(_:)对应。

2、UIViewController拦截提示框示例

我们可以仿照《惊人开发技巧:轻松更换 App 图标,无需系统弹窗!》一文中的拦截弹窗代码,在SwiftUI中使用UIViewController 并拦截 present 行为。

通过 SwiftUI 的 UIViewControllerRepresentable 来包装 UIViewController,并展示如何通过重写 present(_:animated:completion:) 拦截弹窗行为(比如系统的 UIAlertController 弹窗)。

实现效果:在SwiftUI中点击按钮,尝试弹出一个UIAlertController,但是这个弹窗会被拦截,不会真正显示出来。

1、创建拦截控制器

import UIKit

class InterceptingViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white

        let button = UIButton(type: .system)
        button.setTitle("尝试弹出 Alert", for: .normal)
        button.addTarget(self, action: #selector(showAlert), for: .touchUpInside)
        button.tintColor = .red
        button.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(button)
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }

    @objc func showAlert() {
        let alert = UIAlertController(title: "警告", message: "你不该看到我!", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "好", style: .default))
        present(alert, animated: true)
    }

    // ✅ 拦截弹出行为
    override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
        if viewControllerToPresent is UIAlertController {
            print("⚠️ 被拦截了 UIAlertController,不弹出")
            return
        }
        super.present(viewControllerToPresent, animated: flag, completion: completion)
    }
}

2、SwiftUI 包装器

import SwiftUI

struct InterceptingViewControllerRepresentable: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> InterceptingViewController {
        return InterceptingViewController()
    }

    func updateUIViewController(_ uiViewController: InterceptingViewController, context: Context) {
        // 不需要更新内容
    }
}

3、在SwiftUI中使用

import SwiftUI

struct ContentView: View {
    var body: some View {
        InterceptingViewControllerRepresentable()
            .edgesIgnoringSafeArea(.all)
    }
}

在SwiftUI中可以看到“尝试弹出 Alert”的按钮。

当点击“尝试弹出 Alert”按钮时,Xcode会输出:

⚠️ 被拦截了 UIAlertController,不弹出

并且,Xcode视图中不会弹出警告框。

当隐藏重写的present代码后,

// override func present() {} // 隐藏重写的present

就会看到Xcode弹出的警告框:

和SwiftUI区别

SwiftUI 没有 UIViewController,它是声明式编程,使用 View 来替代控制器和视图两层结构。

但底层运行时 SwiftUI 其实仍然使用 UIViewController 来渲染页面,可以通过 UIHostingController 把 SwiftUI 嵌入到 UIKit 中。

总结

UIViewController 是每个页面的“控制中心”,它配合 view 来显示界面、处理交互。在 App 中跳转、弹窗、导航几乎都绕不开它。

本文简单的讲解了生命周期和部分控制器方法。但是UIViewController涉及的内容其实很广:

1、生命周期:控制器从创建、显示到销毁的过程。

2、控制器切换:present/dismiss、push/pop、child controller 管理。

3、视图控制:view、viewDidLoad()、布局、子视图管理。

4、容器控制:UINavigationController、UITabBarController、UIPageViewController 等。

5、状态栏、屏幕方向控制:状态栏样式、横竖屏控制等。

6、内存警告管理:didReceiveMemoryWarning()。

7、动画与过渡:自定义转场动画、转场代理。

8、响应事件:手势、按钮点击、Input 响应链管理。

9、SwiftUI 互操作:通过 UIViewControllerRepresentable 嵌入 SwiftUI。

相关文章

1、Swift的桥接类型NSString:https://fangjunyu.com/2025/04/07/swift%e7%9a%84%e6%a1%a5%e6%8e%a5%e7%b1%bb%e5%9e%8bnsstring/

2、惊人开发技巧:轻松更换 App 图标,无需系统弹窗!:https://mp.weixin.qq.com/s/-wGkKPRTz7aYvMxsN3PcFg

扩展知识

SwiftUI包装器

为什么需要创建SwiftUI包装器来调用UIViewController?

因为SwiftUI和UIKit是两个不同的 UI 框架

SwiftUI 是苹果2019年推出的新声明式 UI 框架。

UIKit 是从 iOS 2 就开始使用的老牌 UI 框架,控制器以 UIViewController 为核心。

两者之间架构完全不同,SwiftUI 不能直接管理 UIKit 的控制器。

所以,SwiftUI 想要使用 UIKit 的东西(如 UIViewController),就需要一个桥接器(桥梁)。

这个桥接器就是:

UIViewControllerRepresentable

它是一个 SwiftUI 协议,允许任何一个 UIViewController 以 SwiftUI 组件的方式嵌入 SwiftUI 界面中。

是如何调用的?(底层工作原理)

创建一个结构体实现 UIViewControllerRepresentable 协议,它必须实现两个函数:

func makeUIViewController(context: Context) -> UIViewController
func updateUIViewController(_: UIViewController, context: Context)

1、makeUIViewController:当 SwiftUI 界面第一次显示时调用,返回要嵌入的 UIKit 控制器。可以在这里返回 DemoViewController()。

2、updateUIViewController:当 SwiftUI 状态更新时调用。可以在这里同步数据更新,比如状态驱动刷新。

在SwiftUI底层:

1、调用 makeUIViewController,拿到UIViewController。

2、将它放进一个SwiftUI容器里展示出来。

3、当视图销毁时,它会自动回收这个控制器。

在SwiftUI中添加对应输出,并结合在生命周期的代码:

struct DemoViewControllerWrapper:UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> DemoViewController  {
        print("进入 makeUIViewController 方法")
        return DemoViewController()
    }

    func updateUIViewController(_ uiViewController: DemoViewController , context: Context) {
        print("进入 updateUIViewController 方法")
        // 不需要处理更新
    }

}
struct ContentView: View {
    var body: some View {
        DemoViewControllerWrapper()
            .edgesIgnoringSafeArea(.all)
        
    }
}

输出的内容为:

进入 makeUIViewController 方法
loadView
viewDidLoad
进入 updateUIViewController 方法
viewWillAppear
viewDidAppear

1、在SwiftUI桥接机制的入口,首先打印了makeUIViewController的输出。

SwiftUI创建了UIViewController实例,比如DemoViewController()。

2、系统自动调用UIViewController生命周期的loadView和viewDidLoad。

当返回控制器后,UIKit系统会自动:

1)调用loadView:加载视图层级。

2)调用viewDidLoad:视图已经加载完毕,可以做初始化。

3、SwiftUI继续调用updateUIViewController方法。

SwiftUI 在首次渲染和状态更新时都会调用这个方法来同步状态。

这是一个“同步点”,并不直接触发任何生命周期方法。

4、控制器即将显示,UIKit自动调用,

控制器开始真正出现在屏幕上时,UIKit继续走标准流程:

1)viewWillAppear:即将出现在屏幕上。

2)viewDidAppear:已经出现。

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注