自定义视图里如何找到控制器并跳转 H5 页面

41 阅读3分钟

当你在 iOS 中编写自定义视图(UIView 子类),却需要在里面触发“跳转页面、展示弹窗”等操作时,往往会遇到一个问题:视图自身不掌握导航权限,必须让 UIViewController 出面。这篇文章分享一个常见技巧:如何在视图内部沿着响应者链(Responder Chain)找到控制器,并安全地完成页面跳转。


背景:为什么视图不能直接跳转?

  • UIView 只负责显示和响应触摸,它不负责页面导航。
  • “跳转新页面”需要通过控制器的导航栈(navigationController)才能完成。
  • 因此,当你在视图内部执行跳转逻辑时,第一步就是找到负责当前界面的 UIViewController。

响应者链是什么?

可以把响应者链想成事件在界面里的“接力通道”,它的顺序通常是:

  • 最底层的控件(按钮、手势识别器等)
  • 该控件的父视图
  • 包含它的视图控制器(UIViewController)
  • 控制器的父控制器(导航控制器、Tab 控制器等)
  • UIWindow
  • UIApplication 或 AppDelegate

当我们沿着 .next 往上走,就是在这条链上一级一级往上爬。

举个最简单的结构例子,看看响应者链是怎么排队的。假设界面是这样:

  • 外层是一个 UIViewController:HelpCenterViewController
  • 控制器的根视图是一个 UILayoutView
  • 根视图里放了一个点击区域 HelpCenterView
  • HelpCenterView 里面还有一个按钮 UIButton

这几个对象的响应者链顺序就是:

UIButton

→ HelpCenterView

→ UILayoutView

→ HelpCenterViewController

→ UINavigationController

→ UIWindow

→ UIApplication

→ AppDelegate

事件会从 UIButton 开始,沿着 next 一级级往上递交。如果手势没在按钮里处理,它就交给 HelpCenterView;该视图如果处理不了,就继续交给它的父视图 UILayoutView;再往上是 HelpCenterViewController,然后是它所在的 UINavigationController,再到 UIWindow、UIApplication,最后到 AppDelegate。findViewController() 就是沿着这条链一路往上走,一旦遇到 UIViewController(这里就是 HelpCenterViewController),就返回给调用者使用。


实战:在 HelpCenterView 里点击跳转 H5

// 点击“帮助中心”后的处理

@objc private func handleTap() {

    guard let viewController = findViewController() else { return }

    

    let vc = SSHTMLController()

    vc.url = SSHTMLURL + "/app/helpCenter"

    vc.hidesBottomBarWhenPushed = true

    viewController.navigationController?.pushViewController(vc, animated: true)

}

// 沿响应者链查找控制器

private func findViewController() -> UIViewController? {

    var responder: UIResponder= self

    while responder != nil {

        if let viewController = responder as? UIViewController {

            return viewController

        }

        responder = responder?.next

    }

    return nil

}

执行流程拆解

  1. 点击发生

用户轻点“帮助中心”,系统调用 handleTap(),表示有点击事件需要处理。

  1. 寻找控制器

handleTap() 先调用 findViewController():

  • 从当前视图 (self) 起步,把它当作 responder。
  • 一直沿着 responder?.next 往上找:若遇到 UIViewController 就立即返回。
  • 如果走到链顶仍没找到,responder 会变成 nil,函数返回 nil,表示这里没有可用的控制器。
  1. 准备目标页面

找到控制器后,创建一个 SSHTMLController,并把 url 设为帮助中心的网页地址,同时设定 hidesBottomBarWhenPushed = true,确保跳转后底部 TabBar 会隐藏。

  1. 执行跳转

通过控制器的 navigationController?.pushViewController 把新页面推上导航栈,完成界面切换。


什么时候会用到 findViewController()?

  • 把点击逻辑写在自定义视图内部,而不是控制器。
  • 需要在视图里弹出提示框、分享面板,或者跳转到其他页面。
  • 只要操作必须由控制器发起,就需要先找到它。

小结

  • 自定义视图若要执行“控制器级别”的操作,就必须沿响应者链找到控制器。
  • findViewController() 通过 responder?.next 一步步向上,直到 UIViewController 或链条尽头。
  • 找到控制器后再执行导航逻辑,既安全又符合 MVC 的职责分离。

把这个模式掌握好,你就能在更多自定义视图的场景里从容应对跳转、弹窗等需求了