《View Controller》

视图控制器的作用

有两种类型的视图控制器:

  • Content 视图控制器管理你的应用程序的一个离散的内容,是你创建的主要类型的视图控制器。
  • Container 视图控制器从其他视图控制器(称为子视图控制器)收集信息,并以方便导航的方式呈现,或以不同方式呈现这些视图控制器的内容。

大多数应用程序都是两种类型的视图控制器的混合体。

视图管理

视图控制器最重要的作用是管理视图的层次结构。每个视图控制器都有一个根视图,它包含了视图控制器的所有内容。在这个根视图中,你可以添加你需要的视图来显示你的内容。图1-1说明了视图控制器和其视图之间的内在关系。视图控制器总是有一个对其根视图的引用,每个视图都有对其子视图的强引用。

image: ../Art/VCPG_ControllerHierarchy_fig_1-1_2x.png

一个内容视图控制器自己管理其所有的视图。容器视图控制器管理它自己的视图和一个或多个子视图控制器的根视图。容器不管理其子代的内容。它只管理根视图,根据容器的设计来确定它的大小和位置。图1-2说明了分割视图控制器和它的子视图之间的关系。分割的视图控制器管理其子视图的整体大小和位置,但子视图控制器管理这些视图的实际内容。

image: ../Art/VCPG_ContainerViewController_fig_1-2_2x.png

数据管理

视图控制器是它所管理的视图和你的应用程序的数据之间的一个中介。UIViewController类的方法和属性可以让你管理你的应用程序的视觉呈现。当你子类化UIViewController时,你可以在子类中添加任何你需要管理数据的变量。添加自定义的变量可以创建一种类似于图1-3的关系,即视图控制器对你的数据和用于呈现该数据的视图都有引用。在两者之间来回移动数据是你的责任。

image: ../Art/VCPG_CustomSubclasses_fig_1-3_2x.png

你应该在你的视图控制器和数据对象中始终保持干净的责任分离。大多数确保数据结构完整性的逻辑都属于数据对象本身。视图控制器可能会验证来自视图的输入,然后以数据对象要求的格式打包输入,但你应该尽量减少视图控制器在管理实际数据中的作用。

用户互动

视图控制器是响应者对象,能够处理响应者链上的事件。尽管它们能够这样做,但视图控制器很少直接处理触摸事件。相反,视图通常会处理自己的触摸事件,并将结果报告给相关的委托对象或目标对象的一个方法,这个对象通常是视图控制器。所以视图控制器中的大多数事件都是使用委托方法或动作方法来处理的。

资源管理

视图控制器对它的视图和它所创建的任何对象承担所有责任。UIViewController类自动处理视图管理的大多数方面。例如,UIKit会自动释放任何不再需要的视图相关资源。

当可用的空闲内存不足时,UIKit会要求应用程序释放它们不再需要的任何资源。它的一种方式是调用你的视图控制器的 didReceiveMemoryWarning 方法。使用该方法来删除那些你不再需要或以后可以轻松重新创建的对象的引用。例如,你可以使用该方法来删除缓存的数据。当发生低内存情况时,尽可能多地释放内存,这一点很重要。消耗太多内存的应用程序可能会被系统直接终止以恢复内存。

视图控制器的层次结构

你的应用程序的视图控制器之间的关系定义了每个视图控制器所需的行为。UIKit希望你能以规定的方式使用视图控制器。保持适当的视图控制器关系可以确保自动行为在需要时被传递给正确的视图控制器。如果你破坏了规定的包含和展示关系,你的应用程序的一部分将停止按照预期的方式运行。

根视图控制器

根视图控制器是视图控制器层次结构的锚。每个窗口都有一个根视图控制器,其内容充满了该窗口。根视图控制器定义了用户看到的初始内容。图2-1显示了根视图控制器和窗口之间的关系。因为窗口本身没有可见的内容,视图控制器的视图提供了所有的内容。

image: ../Art/VCPG-root-view-controller_2-1_2x.png

容器视图控制器

容器视图控制器可以让你从更容易管理和可重复使用的部分组装出复杂的界面。容器视图控制器将一个或多个子视图控制器的内容与可选的自定义视图混合在一起,以创建其最终界面。例如,一个UINavigationController对象将子视图控制器的内容与导航栏和可选的工具栏一起显示,这些内容由导航控制器管理。UIKit包括几个容器视图控制器,包括UINavigationController、UISplitViewController和UIPageViewController。

image: ../Art/VCPG-container-acting-as-root-view-controller_2-2_2x.png

容器视图控制器的视图总是充满了给它的空间。容器视图控制器通常作为根视图控制器安装在窗口中(如图2-2所示),但它们也可以以模式呈现或作为其他容器的子代安装。容器负责对其子视图进行适当的定位。在图中,容器将两个子视图并排放置。尽管这取决于容器的接口,但子视图控制器可能对容器和任何同级视图控制器有最小的了解。

定义你的子类

使用UIViewController的自定义子类来展示你的应用程序的内容。大多数自定义视图控制器都是内容视图控制器–也就是说,它们拥有所有的视图,并对这些视图中的数据负责。相比之下,一个容器视图控制器并不拥有它的所有视图;它的一些视图是由其他视图控制器管理的。定义内容和容器视图控制器的大部分步骤都是一样的。

对于内容视图控制器,最常见的父类如下。

  • 当你的视图控制器的主视图是一个表时,请专门使用UITableViewController。

  • 当你的视图控制器的主视图是一个集合视图时,特别使用UICollectionViewController。

  • 所有其他的视图控制器使用UIViewController。

有效地管理内存

尽管内存分配的大部分方面都是由你来决定的,但表4-1列出了UIViewController中你最有可能分配或取消内存的方法。大多数的去分配涉及到删除对象的强引用。要删除一个对象的强引用,请将指向该对象的属性和变量设置为nil。

Task Methods Discussion
Allocate critical data structures required by your view controller. Initialization methods Your custom initialization method (whether it is named init or something else) is always responsible for putting your view controller object into a known good state. Use these methods to allocate whatever data structures are needed to ensure proper operation.
Allocate or load data to be displayed in your view. viewDidLoad Use the viewDidLoad method to load any data objects you intend to display. By the time this method is called, your view objects are guaranteed to exist and to be in a known good state.
Respond to low-memory notifications. didReceiveMemoryWarning Use this method to deallocate all noncritical objects associated with your view controller. Deallocate as much memory as you can.
Release critical data structures required by your view controller. dealloc Override this method only to perform any last-minute cleanup of your view controller class. The system automatically releases objects stored in instance variables and properties of your class, so you do not need to release those explicitly.

实现一个容器视图控制器

容器视图控制器是一种将多个视图控制器的内容合并到一个用户界面的方法。容器视图控制器最常被用来促进导航和基于现有内容创建新的用户界面类型。UIKit中的容器视图控制器的例子包括UINavigationController、UITabBarController和UISplitViewController,它们都有助于在用户界面的不同部分之间进行导航。

image: ../Art/VCPG-presented-view-controllers_2-3_2x.png

设计一个自定义的容器视图控制器

几乎在每个方面,容器视图控制器都和其他内容视图控制器一样,管理着一个根视图和一些内容。不同的是,容器视图控制器从其他视图控制器中获取部分内容。它得到的内容仅限于其他视图控制器的视图,它将这些视图嵌入到自己的视图层次中。容器视图控制器设置任何嵌入视图的大小和位置,但原始视图控制器仍然管理这些视图中的内容。

在设计你自己的容器视图控制器时,一定要了解容器和所含视图控制器之间的关系。视图控制器的关系可以帮助告知他们的内容应该如何出现在屏幕上,以及你的容器如何在内部管理他们。在设计过程中,要问自己以下问题。

  • 容器的作用是什么,它的子代扮演什么角色?
  • 有多少个孩子同时显示?
  • 同级别的视图控制器之间的关系是什么(如果有的话)?
  • 子代视图控制器是如何被添加到容器中或从容器中移除的?
  • 子代的大小或位置可以改变吗?在什么条件下会发生这些变化?
  • 容器自己是否提供任何装饰性或与导航有关的视图?
  • 容器和它的子代之间需要什么样的通信?除了UIViewController类定义的标准事件外,容器是否需要向其子代报告特定的事件?
  • 容器的外观可以用不同的方式配置吗?如果可以,如何配置?

在你定义了各种对象的角色之后,容器视图控制器的实现就相对简单了。UIKit的唯一要求是,你要在容器视图控制器和任何子视图控制器之间建立一个正式的父子关系。父-子关系确保子代收到任何相关的系统消息。除此之外,大部分真正的工作发生在布局和管理所包含的视图期间,这对每个容器来说都是不同的。你可以将视图放置在容器内容区的任何地方,并按你想要的方式确定这些视图的大小。你还可以在视图层次中添加自定义视图,以提供装饰或帮助导航。

例子:导航控制器

一个UINavigationController对象支持通过一个分层数据集进行导航。一个导航界面一次呈现一个子视图控制器。界面顶部的导航条显示数据层次结构中的当前位置,并显示一个后退按钮,以便向后移动一个层次。向下的数据层次的导航是留给子视图控制器的,可能涉及到表格或按钮的使用。

视图控制器之间的导航是由导航控制器及其子控制器共同管理的。当用户与子视图控制器的按钮或表行交互时,子视图控制器会要求导航控制器推送一个新的视图控制器到视图中。子视图控制器处理新视图控制器的内容配置,但导航控制器管理过渡动画。导航控制器还管理着导航栏,它显示了一个返回按钮,用于关闭最上面的视图控制器。

图5-1显示了一个导航控制器及其视图的结构。大部分的内容区域被最上面的子视图控制器填满,只有一小部分被导航条占据。

image: ../Art/VCPG_structure-of-navigation-interface_5-1_2x.png

在紧凑和常规的环境中,一个导航控制器一次只能显示一个子视图控制器。导航控制器会调整其子视图的大小以适应可用空间。

实现一个自定义的容器视图控制器

要实现一个容器视图控制器,你必须在你的视图控制器和其子视图控制器之间建立关系。在你试图管理任何子视图控制器的视图之前,需要建立这些父子关系。这样做可以让UIKit知道你的视图控制器正在管理子视图的大小和位置。你可以在Interface Builder中创建这些关系,或者以编程方式创建它们。当以编程方式创建父子关系时,你明确地添加和删除子视图控制器作为视图控制器设置的一部分。

向你的内容添加子视图控制器

要以编程方式将子视图控制器加入到你的内容中,通过以下方式在相关的视图控制器之间创建一个父子关系:

  1. Call the addChildViewController: method of your container view controller.

    This method tells UIKit that your container view controller is now managing the view of the child view controller.

  2. Add the child’s root view to your container’s view hierarchy.

    Always remember to set the size and position of the child’s frame as part of this process.

  3. Add any constraints for managing the size and position of the child’s root view.

  4. Call the didMoveToParentViewController: method of the child view controller.

清单5-1显示了一个容器如何将一个子视图控制器嵌入它的容器中。在建立了父子关系后,容器设置了它的子视图的框架,并将子视图添加到它自己的视图层次中。设置子视图的框架大小很重要,它可以确保视图在你的容器中正确显示出来。在添加完视图后,容器会调用子视图控制器的didMoveToParentViewController:方法,让子视图控制器有机会响应视图所有权的变化。

(void) displayContentController: (UIViewController*) content {
[self addChildViewController:content];
content.view.frame = [self frameForContentController];
[self.view addSubview:self.currentClientView]。
[content didMoveToParentViewController:self]。
}

在前面的例子中,注意到你只调用了子代的 didMoveToParentViewController:方法。这是因为addChildViewController:方法为你调用了孩子的willMoveToParentViewController:方法。你必须自己调用didMoveToParentViewController:方法的原因是,在你将子视图嵌入到容器的视图层次结构中之后,该方法才会被调用。

当使用自动布局时,在将子视图添加到容器的视图层次结构后,在容器和子视图之间设置约束。你的约束应该只影响子代根视图的大小和位置。不要改变根视图的内容或子视图层次结构中的任何其他视图。

移除子视图控制器

要从你的内容中移除一个子视图控制器,通过以下方式移除视图控制器之间的父子关系。

  1. 调用子视图控制器的 willMoveToParentViewController: 方法,其值为nil。
  2. 移除你为孩子的根视图配置的任何约束。
  3. 从你的容器的视图层次结构中移除子代的根视图。
  4. 调用子视图控制器的removeFromParentViewController方法来完成父子关系的结束。

删除一个子视图控制器会永久地切断父子之间的关系。只有当你不再需要引用一个子视图控制器时,才可以删除它。例如,当一个新的子视图控制器被推送到导航堆栈时,导航控制器不会删除其当前的子视图控制器。只有当它们被从堆栈中弹出时,它才会删除它们。

从容器中移除一个子视图控制器:

(void) hideContentController: (UIViewController*) content {
  [content willMoveToParentViewController:nil]
  [content.view removeFromSuperview]。
  [content removeFromParentViewController]
}

子视图控制器之间的过渡

当你想把一个子视图控制器替换成另一个子视图控制器的动画时,将子视图控制器的添加和移除纳入过渡动画过程。在制作动画之前,确保两个子视图控制器都是你内容的一部分,但要让当前的子视图控制器知道它即将离开。在你的动画过程中,将新的孩子的视图移到位置上,并移除旧的孩子的视图。在动画完成后,完成对子视图控制器的移除。

下列代码显示了一个如何使用过渡动画将一个子视图控制器换成另一个的例子。在这个例子中,新的视图控制器被动画化到现有的子视图控制器目前所占据的矩形,而子视图控制器则被移到了屏幕之外。动画结束后,完成块将子视图控制器从容器中移除。在这个例子中,transitionFromViewController:toViewController:duration:options:animations:completion:方法会自动更新容器的视图层次结构,所以你不需要自己添加和删除视图。

清单5-3 在两个子视图控制器之间进行转换

(void)cycleFromViewController: (UIViewController*) oldVC toViewController: (UIViewController*) newVC {
// 准备好这两个视图控制器的变化。
[oldVC willMoveToParentViewController:nil]。
[self addChildViewController:newVC]。

// 获取新视图控制器的起始帧和旧视图控制器的结束帧。
// 获取新视图控制器的起始帧和旧视图控制器的结束帧。两个矩形都在屏幕外。
newVC.view.frame = [self newViewStartFrame];
CGRect endFrame = [self oldViewEndFrame];

// 排队等待过渡动画。
[self transitionFromViewController: oldVC toViewController: newVC
     持续时间。0.25 options:0
     animations:^{
         // 给视图制作动画到它们的最终位置。
         newVC.view.frame = oldVC.view.frame。
         oldVC.view.frame = endFrame;
     }
     completion:^(BOOL finished) {
        // 删除旧的视图控制器,并将最终的
        // 通知给新的视图控制器。
        [oldVC removeFromParentViewController]。
        [newVC didMoveToParentViewController:self];
     }];
}

构建容器视图控制器的建议

设计、开发和测试一个新的容器视图控制器需要时间。尽管单个行为是直接的,但控制器作为一个整体可能是相当复杂的。在实现你自己的容器类时,请考虑以下的提示。

  • 只访问子视图控制器的根视图。容器应该只访问每个子视图的根视图,也就是由子视图属性返回的视图。它不应该访问子视图的任何其他视图。

  • 子视图控制器应该对其容器有最小的了解。一个子视图控制器应该专注于它自己的内容。如果容器允许其行为受到子视图的影响,它应该使用委托设计模式来管理这些交互。

  • 首先使用常规视图来设计你的容器。使用常规的视图(而不是来自子视图控制器的视图)使你有机会在一个简化的环境中测试布局约束和动画转换。当常规视图如期工作时,把它们换成你的子视图控制器的视图。

将控制权委托给子视图控制器

容器视图控制器可以将其自身外观的某些方面委托给一个或多个子视图控制器。你可以通过以下方式委托控制。

  • 让一个子视图控制器决定状态栏的样式。要把状态栏的外观委托给一个子代,在你的容器视图控制器中覆盖一个或两个childViewControllerForStatusBarStyle和childViewControllerForStatusBarHidden方法。

  • 让孩子指定它自己喜欢的尺寸。一个具有灵活布局的容器可以使用子节点自己的preferredContentSize属性来帮助确定子节点的大小。

保存和恢复状态

视图控制器在状态保存和恢复过程中起着重要作用。状态保存记录了你的应用在暂停之前的配置,以便在随后的应用启动时恢复配置。将应用程序恢复到以前的配置可以为用户节省时间并提供更好的用户体验。

保存和恢复过程大多是自动的,但你需要告诉iOS你的应用程序的哪些部分需要保存。保存你的应用程序的视图控制器的步骤如下。

  • (必需的)为你想保留的配置的视图控制器分配恢复标识符;见为保留而标记视图控制器。
  • (必填)告诉iOS如何在启动时创建或定位新的视图控制器对象;见在启动时恢复视图控制器。
  • (可选)对于每个视图控制器,存储任何特定的配置数据,以使该视图控制器返回到其原始配置;请参阅编码和解码您的视图控制器的状态。

image: ../Art/state_vc_caveats.jpg

image: ../Art/state_vc_caveats_2.jpg

为保存的视图控制器打上标签

UIKit只保存你告诉它要保存的视图控制器。每个视图控制器都有一个restorationIdentifier属性,其值默认为nil。将该属性设置为一个有效的字符串告诉UIKit该视图控制器及其视图应该被保存。你可以通过编程或在故事板文件中指定恢复标识符。

在分配恢复标识符时,请记住,视图控制器层次结构中的所有父视图控制器也必须有恢复标识符。在保存过程中,UIKit从窗口的根视图控制器开始,并在视图控制器的层次结构中行走。如果该层次中的一个视图控制器没有修复标识符,那么该视图控制器及其所有的子视图控制器和呈现的视图控制器都会被忽略。

选择有效的恢复标识符

UIKit使用你的恢复标识符字符串来重新创建视图控制器,所以选择对你的代码来说容易识别的字符串。如果UIKit不能自动创建你的一个视图控制器,它会要求你创建它,为你提供视图控制器及其所有父视图控制器的恢复标识符。这个标识符链代表了视图控制器的恢复路径,也是你确定哪个视图控制器被请求的方法。恢复路径从根视图控制器开始,包括每一个视图控制器,直到并包括被请求的那个。

恢复标识符通常只是视图控制器的类名。如果你在很多地方使用同一个类,你可能想指定更有意义的值。例如,你可以根据视图控制器所管理的数据来指定一个字符串。

每个视图控制器的恢复路径必须是唯一的。如果一个容器视图控制器有两个孩子,容器必须给每个孩子分配一个唯一的恢复标识符。UIKit中的一些容器视图控制器会自动消除其子视图控制器的歧义,允许你对每个孩子使用相同的恢复标识符。

排除视图控制器组

要从恢复过程中排除整组视图控制器,请将父视图控制器的恢复标识符设置为nil。

下图显示了将恢复标识符设置为nil对视图控制器层次结构的影响。由于缺乏保存数据,该视图控制器以后无法被恢复。

保存一个视图控制器的视图

一些视图有额外的状态信息,这些信息与视图有关,但与父级视图控制器无关。例如,一个滚动视图有一个滚动位置,你可能想保留它。虽然视图控制器负责提供滚动视图的内容,但滚动视图本身负责保存其视觉状态。

要保存一个视图的状态,请执行以下操作。

  1. 给视图的restorationIdentifier属性指定一个有效的字符串。
  2. 使用来自视图控制器的视图,该控制器也有一个有效的恢复标识符。
  3. 对于表视图和集合视图,指定一个采用UIDataSourceModelAssociation协议的数据源。

为视图指定一个恢复标识符会告诉UIKit,它应该将该视图的状态写入保存档案中。当视图控制器后来被恢复时,UIKit也会恢复任何有恢复标识符的视图的状态。

在启动时恢复视图控制器

在启动时,UIKit会尝试将您的应用程序恢复到以前的状态。那时,UIKit会要求您的应用程序创建(或定位)构成您保留的用户界面的视图控制器对象。在试图定位视图控制器时,UIKit按以下顺序搜索。

如果视图控制器有一个恢复类,UIKit要求该类提供视图控制器。UIKit调用相关修复类的viewControllerWithRestorationIdentifierPath:coder:方法来检索视图控制器。如果该方法返回nil,则认为该应用程序不想重新创建视图控制器,UIKit停止寻找它。 如果视图控制器没有一个恢复类,UIKit会要求应用程序委托人提供视图控制器。UIKit调用app委托的app:viewControllerWithRestorationIdentifierPath:coder:方法来寻找没有恢复类的视图控制器。如果该方法返回nil,UIKit会尝试以隐式方式找到视图控制器。 如果一个具有正确修复路径的视图控制器已经存在,UIKit会使用该对象。如果你的应用程序在启动时创建了视图控制器(以编程方式或通过从故事板加载它们),并且这些视图控制器有恢复标识符,UIKit会根据它们的恢复路径隐式地找到它们。 如果视图控制器最初是从故事板文件加载的,UIKit会使用保存的故事板信息来定位和创建它。UIKit将视图控制器的故事板信息保存在还原档案里面。在还原时,UIKit使用这些信息来定位相同的故事板文件,如果通过其他方式没有找到视图控制器,就实例化相应的视图控制器。 给视图控制器指定一个恢复类可以防止UIKit隐含地搜索该视图控制器。使用恢复类可以让你更多地控制是否真的要创建一个视图控制器。例如,你的viewControllerWithRestorationIdentifierPath:coder:方法可以返回nil,如果你的类确定视图控制器不应该被重新创建。当没有恢复类时,UIKit会尽其所能找到或创建视图控制器并恢复它。

当使用一个恢复类时,你的viewControllerWithRestorationIdentifierPath:coder:方法应该创建一个新的类的实例,执行最小的初始化,并返回产生的对象。清单7-1显示了一个如何使用这个方法从故事板上加载一个视图控制器的例子。因为视图控制器最初是从故事板加载的,这个方法使用UIStateRestorationViewControllerStoryboardKey键来从存档中获取故事板。请注意,这个方法并没有尝试配置视图控制器的数据字段。这一步会在以后对视图控制器的状态进行解码时发生。

(UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
    coder:(NSCoder *)coder {
    MyViewController* vc;
    UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey] 。
    if (sb){
       vc = (PushViewController*)[sb instantiateViewControllerWithIdentifier:@"MyViewController"] 。
       vc.restorationIdentifier = [identifierComponents lastObject];
       vc.restorationClass = [MyViewControllerClass]
    }
    return vc
}

在手动重新创建视图控制器时,重新分配修复标识符和修复类是一个好习惯。最简单的恢复标识符的方法是抓取标识符Components数组中的最后一项,并将其分配给你的视图控制器。

对于那些在启动时从你的应用程序的主故事板文件中创建的对象,不要为每个对象创建新的实例。让UIKit隐含地找到这些对象,或者使用app委托的app:viewControllerWithRestorationIdentifierPath:coder:方法来找到现有的对象。

呈现一个视图控制器

在屏幕上显示一个视图控制器有两种方法:将其嵌入容器视图控制器或展示它。容器视图控制器提供了一个应用程序的主要导航,但展示视图控制器也是一个重要的导航工具。你可以使用直接呈现来在当前视图控制器的基础上显示一个新的视图控制器。通常情况下,当你想实现模态界面时,你会展示视图控制器,但你也可以把它们用于其他目的。

UIViewController类内置了对呈现视图控制器的支持,所有视图控制器对象都可以使用。你可以从任何其他视图控制器中展示任何视图控制器,尽管UIKit可能会将请求重定向到一个不同的视图控制器。呈现一个视图控制器在原始视图控制器(被称为呈现的视图控制器)和要显示的新视图控制器(被称为呈现的视图控制器)之间建立了一种关系。这种关系构成了视图控制器层次结构的一部分,并一直保持到呈现的视图控制器被驳回。

呈现和过渡过程

呈现视图控制器是一种快速而简单的方法,可以将新的内容动画化到屏幕上。UIKit内置的演示机制可以让你使用内置或自定义的动画来显示一个新的视图控制器。内置的演示和动画需要很少的代码,因为UIKit处理所有的工作。你也可以创建自定义的演示和动画,只需一点额外的努力,并将它们用于你的任何视图控制器。

演示风格

视图控制器的表现风格决定了它在屏幕上的外观。UIKit定义了许多标准的展示风格,每一种都有特定的外观和意图。你也可以定义你自己的演示风格。在设计你的应用程序时,选择对你要做的事情最有意义的展示风格,并将适当的常数分配给你要展示的视图控制器的modalPresentationStyle属性。

全屏演示样式

全屏演示样式覆盖整个屏幕,防止与底层内容的交互。在一个水平规则的环境中,只有一种全屏样式能完全覆盖底层内容。其余的结合了调光视图或透明度,允许底层视图控制器的部分内容显示出来。在一个水平紧凑的环境中,全屏演示会自动适应UIModalPresentationFullScreen样式,并覆盖所有的底层内容。

图8-1说明了在水平规则环境下,使用UIModalPresentationFullScreen、UIModalPresentationPageSheet和UIModalPresentationFormSheet样式的演示的外观。在图中,左上角的绿色视图控制器呈现出右上角的蓝色视图控制器,每种呈现风格的结果如下所示。对于某些演示风格,UIKit在两个视图控制器的内容之间插入了一个调光视图。