探索视图层次
本文是关于UIView
, UIViewController
和UIWindow
如何连接的。 我将说明如何遍历视图,视图控制器和响应者链的层次结构。 我们将通过推送,模式和嵌入演示来探索应用程序的结构外观。 并在Xcode中构建控制台版本的Debug View Hierarchy工具。
UI工具
Xcode中的Debug View Hierarchy捕获用户界面的快照,并允许浏览和检查视图对象。
有时控制台更方便。 我们可以使用私有方法对UI进行内部检查。
-[UIView recursiveDescription]
-[UIViewController _printHierarchy]
您可以暂停应用程序执行,并在控制台中使用表达式来调用recursiveDescription
和_printHierarchy
。
(lldb) expr -l objc -O --
[[[[UIApplication sharedApplication] keyWindow] recursiveDescription]
(lldb) expr -l objc -O --
[[[[[UIApplication sharedApplication] keyWindow] rootViewController] _printHierarchy]
-l objc -O --
用于将上下文从Swift切换到Objective-C。
View Hierarchy
应用程序通常包含许多视图和一个以上的视图控制器,从而建立了两个层次结构:
-
UIView
层次结构是一棵树,它的根位于窗口中(UIWindow
是UIView
的子类)。 我们可以使用subviews
和superview
属性遍历此树。 -
UIViewController
层次结构从窗口的rootViewController
开始。 视图控制器可以提供其他视图控制器或作为其他视图控制器的容器,从而创建两个不同的层次结构。
呈现的视图控制器
当我们使用Present Modally
或Preset As Popover
segue或present(_:animated:completion:)
方法显示视图控制器时,我们创建了presenting → Presented连接。
呈现的视图控制器建立了一个类似的堆栈:我们呈现的最后一个视图控制器是可见的,通常首先被关闭。
呈现的视图控制器在一个双向链表中与presentedViewController
和presentingViewController
属性连接。
容器视图控制器
容器视图控制器管理它拥有的其他视图控制器。 在情节提要中,我们使用Show
and Show Detail
segue,也可以使用addChild(_:)
方法,创建父级 → 子级连接。
这形成一棵树,可以遍历children
和parent
属性。
许多常见的视图控制器是容器: UINavigationController
, UITabBarController
, UISplitViewController
, UIPageViewController
。
组合是可能的,即当容器提供不同的视图控制器时。 但是,不可能同时在容器中显示视图控制器。
当一个容器出现时,我们得到了两种结构的组合,提出的视图控制器可以形成另一棵树。
在事件处理方面,两个层次结构之间的差异变得明显。 在树中,同级是等效的,在栈中,顶部优先。
响应链
UIKit使用响应者链来接收和处理事件。 这将帮助我们遍历视图和视图控制器层次结构。
响应者链将视图中的响应者对象连接到应用程序委托:
UIView → UIViewController → UIWindow → UIApplication → UIApplicationDelegate
响应者链中可以有许多视图和视图控制器。 我们可以使用next
属性遍历响应者链:
- 对于视图,
next
是它的超级视图,或者如果视图是根视图,则是视图控制器;否则,它是视图控制器。 - 对于视图控制器,
next
是显示视图控制器或窗口。
响应者链是一个列表,我们可以编写一个简单的函数来遍历它:
通用层次结构
让我们看一下故事板,其中创建了一些常见的segue和层次结构。
我们有push , modal和embed segue。 初始视图控制器是UINavigationController
。
我们可以使用上面的控制台和命令来暂停应用执行和自省视图层次,以查看结构。 我使用了不同的类名来更好地说明它。
在根场景中,我们可以跟踪视图控制器树中的路径:
导航→[根→[黄色]]
视图层次结构与导航控制器引入的其他视图相似。
我们可以看到事件如何在响应者链中从黄色视图传播到应用程序委托。
推送表示由导航控制器实现,我们可以在视图控制器层次结构中看到这一点:
导航→[根→[黄色],青色]
视图层次结构已更改,因为导航控制器仅在导航堆栈中显示顶视图控制器。
在日志中显示了标有+的视图控制器,以将其与子视图控制器区分开:
导航→[根→[黄色]] +洋红色
视图层次结构仅包含可见视图控制器的视图和一些私有视图。
构建调试视图层次结构
视图,视图控制器层次结构和响应者链都是构建“调试视图层次结构”工具所需的全部内容。 运作方式如下:
-
traverseHierarchy
方法是使用封闭的访客设计模式的变化来构建的; - 遍历从窗口开始,使用“深度优先搜索”遍历层次结构中的视图和视图控制器。 这样,层次结构中的一个分支就在下一个分支之前完全遍历了。
- 仅处理可见的视图;
- 遵循响应者链规则来连接视图和视图控制器。
traverseHierarchy
可用于在控制台中打印出视图层次结构:
(lldb) expr UIApplication.shared.keyWindow?.traverseHierarchy {响应者,_ in print(responder)}
通过实现一种便捷方法:
Voilà:
(lldb) expr UIWindow.printKeyWindowHierarchy()