UIViewController(三):呈现和转场动画

呈现一个视图控制器

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

UIViewController类支持呈现一个视图控制器,并可用于所有视图控制器对象。我们可以使用任何视图控制器来呈现其他的任何视图控制器,但UIKit可能会将呈现请求重新分配给不同的视图控制器。正在呈现的视图控制器会在它和呈现它的起始视图控制器之间创建一种关系,正在呈现的视图控制器是起始视图控制器的presentedViewController,起始视图控制器是正在呈现的视图控制器的presentingViewController。这种关系构成了视图控制器层次结构的一部分,并保持原样直到视图控制器被从屏幕上移除。

呈现和转场动画过程

呈现一个视图控制器是将新内容动画化到屏幕上的快捷方式。内置于UIKit中的呈现机制允许我们使用内置或者自定义的动画显示一个新的视图控制器。实现内置的呈现和动画只需要很少的代码,UIKit会处理所有的工作。我们也可以创建自定义的呈现和动画,只需要少许额外处理,就能将其用于任何视图控制器。

呈现样式

视图控制器的呈现样式控制其在屏幕上的外观。UIKit定义了许多标准的呈现样式,每种风格都有特定的外观和意图。我们也可以定义我们自己的自定义呈现样式。在设计应用程序时,请选择最适合我们正在尝试呈现的视图控制器界面的样式,并为视图控制器的modalPresentationStyle属性分配合适的值。

全屏样式

全屏样式覆盖整个屏幕,防止与底层内容的交互。在水平常规环境中,只有一种全屏样式完全覆盖了底层内容。其他的全屏样式都包含遮罩视图或透明视图,以允许显示一部分底层视图控制器。在水平紧凑环境中,全屏呈现会自动适应UIModalPresentationFullScreen样式并覆盖所有底层内容。

下图显示了在水平常规环境中使用UIModalPresentationFullScreenUIModalPresentationPageSheetUIModalPresentationFormSheet样式被呈现的视图控制器的外观。在图中,左上方的绿色视图控制器呈现右上角的蓝色视图控制器,每种呈现样式的结果如下所示。对于某些呈现样式,UIKit会在两个视图控制器的内容之间插入遮罩视图。

图8-1

注意:使用UIModalPresentationFullScreen样式来呈现视图控制器时,UIKit通常会在转场动画完成后删除底层视图控制器的视图。可以通过指定UIModalPresentationOverFullScreen样式来防止删除底层视图。当被呈现的视图控制器具有让底层视图内容显示的透明区域时,可以使用此样式。

当使用一种全屏呈现样式时,起始视图控制器必须覆盖整个屏幕。如果起始视图控制器没有覆盖整个屏幕,则UIKit逐步向上查看视图控制器层次结构,直到找到一个有效的视图控制器来全屏呈现另一个视图控制器。 如果找不到填充屏幕的中间视图控制器,则UIKit将使用窗口的根视图控制器。

Popover样式

UIModalPresentationPopover样式在Popover中显示视图控制器。Popover对于显示附加信息或者与焦点、选定对象相关的项目列表非常有用。在水平常规环境中,Popover仅覆盖部分屏幕,如下图所示。在水平紧凑的环境中,默认情况下,Popover会适应UIModalPresentationOverFullScreen呈现样式。点击弹出视图之外的屏幕会自动移除Popover。

图8-2

因为Popover会自动适配在水平紧凑环境中的全屏呈现,所以通常需要修改Popover代码来处理适配。在全屏模式下,需要一种方法来移除被呈现的Popover。可以通过添加一个按钮,将Popover嵌入到一个可用的容器视图控制器中,或者改变适配行为本身。

当前上下文样式

UIModalPresentationCurrentContext样式覆盖了界面中的特定视图控制器。使用上下文样式时,通过将视图控制器的definesPresentationContext属性值设为YES来指定要覆盖此视图控制器。下图显示了一个当前上下文样式的呈现,它只覆盖了分割视图控制器的一个子视图控制器。

图8-3

注意:当使用UIModalPresentationFullScreen样式来呈现一个视图控制器时,UIKit通常会在转场动画执行完成后删除处于被呈现的视图控制器之下的视图控制器的视图。可以通过指定UIModalPresentationOverCurrentContext样式来防止删除这些视图。当被呈现的视图控制器具有让底层内容显示的透明区域时,可以使用该样式。

定义了呈现上下文的视图控制器也可以定义在呈现过程中使用的转场动画。通常情况下,UIKit使用起始视图控制器的modalTransitionStyle属性值来在屏幕上动画视图控制器。如果呈现上下文视图控制器的providesPresentationContextTransitionStyle属性值为YES,则UIKit将使用该视图控制器的modalTransitionStyle属性值。

当切换到水平紧凑环境时,当前上下文样式会适应UIModalPresentationFullScreen样式。要更改此行为,请使用自适应呈现委托来指定不同的呈现样式或视图控制器。

自定义呈现样式

UIModalPresentationCustom样式允许我们使用自己定义的自定义样式来呈现一个视图控制器。创建自定义样式需要子类化UIPresentationController,并使用其方法来将任何自定义视图动画到屏幕上,同时设置需要被呈现的视图控制器的尺寸和位置。起始控制器还处理由于其所呈现的视图控制器的特性的变化而发生的任何适应。

转场动画样式

转场动画样式确定了用于呈现一个视图控制器的动画类型。对于官方提供的转场动画样式,可以将其中一种标准样式分配给需要呈现的视图控制器的modalTransitionStyle属性。呈现一个视图控制器时,UIKit会创建与该样式相对应的动画。例如,下图说明了标准的UIModalTransitionStyleCoverVertical转场动画样式如何为屏幕上的视图控制器生成动画。视图控制器B开始进入屏幕时,动画滑动到视图控制器A的顶部上方。当视图控制器B被移除时,动画反转,以便B向下滑动以显示A。

图8-4

可以使用动画对象和转场动画委托对象来创建自定义转场。动画对象创建用于将视图控制器显示到屏幕上的过渡动画。转场动画委托在适当的时候将动画对象提供给UIKit。

呈现一个视图控制器的方式

UIViewController类提供了两种方式来显示视图控制器:

  • showViewController:sender:showDetailViewController:sender:方法提供了最适应和最灵活的方式来显示视图控制器。这些方法让起始控制器决定如何最好地处理呈现。例如,容器视图控制器可以让视图控制器作为一个子视图控制器来呈现,而不是以模态方式呈现。默认行为以模态方式呈现一个视图控制器。
  • presentViewController:animated:completion:方式总是模态显示视图控制器。调用此方法的视图控制器可能最终不会处理此次呈现,但呈现始终是模态的。这种方法会适应在水平紧凑环境中的呈现样式。

showViewController:sender:showDetailViewController:sender:方法是发起呈现的首选方式。视图控制器可以调用它们而不知道视图控制器层次结构的其余部分或当前视图控制器在该层次结构中的位置。这些方法可以在不用编写附带条件的代码的情况下,在应用程序的不同部分重新使用视图控制器。

呈现一个视图控制器

有以下几种方式来发起视图控制器的呈现:

  • 使用segue自动呈现视图控制器。segue使用在Interface Builder中指定的信息来实例化并呈现视图控制器。
  • 使用showViewController:sender:showDetailViewController:sender:方法来显示视图控制器。在自定义视图控制器中,可以将这些方法的行为更改为更适合我们的视图控制器的行为。
  • 调用presentViewController:animated:completion:方法以模态方式呈现视图控制器。

显示视图控制器

当使用showViewController:sender:showDetailViewController:sender:方法时,将新视图控制器添加到屏幕上的过程很简单:

  • 创建需要呈现的视图控制器对象。在创建视图控制器时,需要使用任何需要执行其任务的数据对其进行初始化。
  • 将视图控制器的modalPresentationStyle属性设置为需要的样式。这种样式可能不会在最终呈现中使用。
  • 将视图控制器的modalTransitionStyle属性设置为需要的转场动画样式。这种样式可能不会在最终呈现中使用。
  • 调用当前视图控制器的showViewController:sender:showDetailViewController:sender:方法。

UIKit会将对showViewController:sender:showDetailViewController:sender:方法的调用转发给合适的视图控制器。该视图控制器可以决定如何最好地执行呈现,并可以根据需要更改呈现样式和转场动画样式。例如,导航控制器可能会将视图控制器推到其导航堆栈上。

模态呈现视图控制器

当直接呈现视图控制器时,需要告诉UIKit如何显示新的视图控制器以及如何动画显示到屏幕上:

  • 创建需要呈现的视图控制器对象。在创建视图控制器时,需要使用任何需要执行其任务的数据对其进行初始化。
  • 将视图控制器的modalPresentationStyle属性设置为需要的样式。
  • 将视图控制器的modalTransitionStyle属性设置为需要的转场动画样式。
  • 调用当前视图控制器的presentViewController:animated:completion:方法。

调用presentViewController:animated:completion:方法的视图控制器可能不是实际发起模态呈现的视图控制器。呈现样式决定来如何呈现视图控制器,包括发起呈现的视图控制器所需的特性。例如,全屏呈现必须由覆盖全屏的视图控制器发起。如果当前视图控制器不合适,UIKit将遍历视图控制器层次结构,直到找到一个为止。UIKit在完成模态呈现后,会更新受影响的视图控制器的presentingViewControllerpresentedViewController属性。

在Popover中呈现一个视图控制器

在呈现Popover之前,需要一些额外配置。在设置模态呈现样式为UIModalPresentationPopover后,配置以下与Popover相关的属性:

  • 将视图控制器的preferredContentSize属性设置为所需的尺寸。
  • 访问视图控制器的popoverPresentationController属性获得与其相关联的UIPopoverPresentationController对象,并使用该对象来设置Popover的锚点。只需要设置下列之一:
    • barButtonItem属性值设为一个UIBarButtonItem对象。
    • sourceView属性值设置为当前视图层中的某个视图,sourceRect属性值设置为sourceView中的某个特定区域。

可以使用UIPopoverPresentationController对象根据需要对Popover的外观进行其他调整。Popover控制器对象还支持设置委托对象来响应在呈现过程中的更改。例如,当Popover出现、消失或在屏幕上重新定位时,可以使用委托对象来响应。有关此对象的更多信息,可以参阅UIPopoverPresentationController Class Reference

移除呈现的视图控制器

要移除当前呈现的视图控制器,需要调用该视图控制器的dismissViewControllerAnimated:completion:方法。也可以调用呈现该视图控制器的起始视图控制器的该方法来移除当前被呈现的视图控制器。当调用起始视图控制器的该方法时,UIKit会自动将移除请求转发给被呈现的视图控制器。

在移除视图控制器之前,请总是保持视图控制器中的重要信息。移除视图控制器会将其从视图控制器层次结构中删除,并将其视图从屏幕上移除。如果没有强引用该视图控制器,移除该视图控制器将释放与之关联的内存。

如果呈现的视图控制器必须传输数据给发起呈现的视图控制器,则使用委托设计模式来促进传输。委托可以使在应用程序的不同部分来重用视图控制器变得简单。使用委托,被呈现的视图控制器会存储对实现来协议方法的委托对象的引用。当被呈现的视图控制器需要接收数据时,其会调用委托对象的协议方法。

在不同storyboard文件中的视图控制器之间发起呈现

可以在同一个storyboard文件中的视图控制器之间创建segue,但不能在不同storyboard文件中的视图控制器之间创建segue。当需要呈现一个存储在不同storyboard文件中的视图控制器时,必须在呈现其之前明确地实例化这个视图控制器,如下所示。该示例以模态方式呈现视图控制器,但我们也可以将其推到导航堆栈上或以其他方式显示。

UIStoryboard* sb = [UIStoryboard storyboardWithName:@"SecondStoryboard" bundle:nil];
MyViewController* myVC = [sb instantiateViewControllerWithIdentifier:@"MyViewController"];

// Configure the view controller.

// Display the view controller
[self presentViewController:myVC animated:YES completion:nil];

使用Segue

使用segue来定义应用程序界面的跳转流程。segue定义了storyboard文件中的两个视图控制器之间的转换。segue的起始点是启动segue的按钮、表格行或者手势识别器。segue的结束点是想要显示的视图控制器。segue总是呈现一个新的视图控制器,但是也可以使用unwind segue来移除视图控制器。

图9-1

不需要以编程方式触发segue。在运行时,UIKit加载与视图控制器相关联的segue,并将它们连接到相应的元素。当用户与元素产生交互时,UIKit会加载相应的视图控制器,通知应用程序即将触发segue,并执行转换。可以使用UIKit发送的通知将数据传递到新的视图控制器或者防止此种情况发生。

在视图控制器之间创建一个segue

要在同一个storyboard文件中的视图控制器之间创建一个segue,请按住Control键并单击第一个视图控制器中的相应元素,然后拖动到目标视图控制器。segue的起始点必须是具有已经定义了action的视图或者对象,例如control、bar button item或者gesture recognizer。也可以从基于单元格的视图(如table view和collection view)创建segue。下图显示了创建一个当单元格被点击时显示一个新视图控制器的segue。

图9-2

注意:某些元素支持关联多个segue。例如,可以为cell上的button点击配置一个segue,同时也可以为cell点击配置另一个segue。

当松开鼠标按钮时,Interface Builder会提示我们选择要在两个视图控制器之间创建的关系类型,如下图所示。选择符合我们需要的转换的segue。

图9-3

当为segue选择关系类型时,尽可能选择一个自适应segue。自适应segue会根据当前屏幕环境调整其行为。例如,Show segue的行为基于需要呈现的视图控制器而改变。非自适应segue适用于必须在iOS 7系统运行的应用程序。以下列出了自适应segue类型以及它们在应用程序中的行为:

  • Show (Push) : 该segue使用目标视图控制器的showViewController:sender:方法来显示新的内容。对于大多数视图控制器,该segue在源视图控制器上以模态方式呈现新内容。一些视图控制器专门覆盖该方法并使用它来实现不同的行为。例如,导航控制器将新的视图控制器推到其导航堆栈上。UIKit使用targetViewControllerForAction:sender:方法来定位源视图控制器。
  • Show Detail (Replace) : 该segue使用目标视图控制器的showDetailViewController:sender:方法来显示新的内容。其仅与嵌入在UISplitViewController对象内的视图控制器有关。通过该segue,分割视图控制器用新的内容替换它的第二个子视图控制器(细节控制器)。大多数其他控制器以模态方式呈现新内容。UIKit使用targetViewControllerForAction:sender:方法来定位源视图控制器。
  • Present Modally : 该segue使用指定的呈现样式和转场动画样式以模态方式显示视图控制器。定义了相应的呈现上下文的视图控制器会处理实际的呈现。
  • Present as Popover : 在水平常规屏幕环境中,视图控制器显示在Popover中。在水平紧凑屏幕环境中,视图控制器使用全屏呈现样式来被显示。

创建一个segue之后,选中segue对象并使用属性检查器为其分配一个标识符。在执行segue时,可以使用标识符来确定哪个segue被触发。如果视图控制器支持多个segue,那么这样做是特别有用的。标识符包含在执行segue时传递给视图控制器的UIStoryboardSegue对象中。

在运行时修改segue的行为

下图显示了当一个segue被触发时发生了什么。大多数工作发生在发起呈现的视图控制器中,其管理着到新视图控制器的转场。新视图控制器的配置与以编码方式创建视图控制器并呈现它的过程基本相同。由于是在storyboard文件中配置segue,所以与segue关联的两个视图控制器必须在同一个storyboard文件中。

图9-4

在执行segue期间,UIKit调用当前视图控制器的方法来提供机会让我们影响segue的结果。

  • shouldPerformSegueWithIdentifier:sender:方法提供了阻止segue执行的机会。该方法返回NO会导致segue执行失败,但不会阻止其他行为的发生。例如,点击tableview的cell仍然会导致tableview调用任何相关的委托方法。
  • 源视图控制器的prepareForSegue:sender:方法允许我们将数据从源视图控制器传递到目标视图控制器。传递给该方法的UIStoryboardSegue对象包含对目标视图控制器的引用以及其他与segue相关的信息。

创建一个unwind segue

unwind segue能够移除已经被呈现的视图控制器。可以在Interface Builder中通过关联一个按钮或者其他合适的对象到当前视图控制器的Exit对象来创建unwind segue。当用户点击按钮或者与适当的对象交互时,UIKit会搜索视图控制器层次结构来找到一个能够处理unwind segue的对象。然后移除当前视图控制器和任何中间视图控制器来展示与unwind segue关联的目标视图控制器。

创建一个unwind segue遵循以下步骤:

  1. 选择unwind segue执行结束后应该显示在屏幕上的视图控制器。
  2. 在选择的视图控制器中定义一个unwind 操作方法,这个操作方法的Objective-C语法为- (IBAction)myUnwindAction:(UIStoryboardSegue*)unwindSegue
  3. 导航到发起unwind segue的视图控制器。
  4. 按住Control键点击执行unwind segue的按钮(或其他对象)。该按钮(或其他对象)应该存在于需要被移除的视图控制器中。
  5. 拖动到视图控制器顶部的Exit对象。
  6. 在relationship panel中选择unwind操作方法。
图9-5

在Interface Builder中创建相应的unwind segue之前,必须在发起unwind segue的视图控制器中定义一个unwind操作方法。该方法的存在是必需的,其告知Interface Builder有一个有效的unwind segue目标。

使用unwind操作方法的实现来执行应用程序中特定的任何任务。UIKit会自动移除视图控制器,而不需要我们手动移除发起segue的任何视图控制器。可以使用segue对象获取正在被移除的视图控制器,以便从其中回收数据。也可以使用unwind操作方法在unwind segue结束之前更新当前视图控制器。

以编程方式发起segue

segue通常是由在storyboard文件中创建的连接触发的。但是,有时可能无法在storyboard文件中创建segue,可能是因为无法确定目标视图控制器。例如,游戏应用程序可能会根据游戏的结果转场到不同的界面。在这些情况下,可以使用当前视图控制器的performSegueWithIdentifier:sender:方法编程方式出发segue。

以下代码演示了从纵向到横向旋转时呈现特定视图控制器的segue。因为这种情况下的通知对象没有提供执行segue命令的有用信息,视图控制器就将自己指定为segue的发起者。

- (void)orientationChanged:(NSNotification *)notification
{
    UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
    
    if (UIDeviceOrientationIsLandscape(deviceOrientation) && !isShowingLandscapeView)
    {
        [self performSegueWithIdentifier:@"DisplayAlternateView" sender:self];
        
        isShowingLandscapeView = YES;
    }
    // Remainder of example omitted.
}

创建自定义的segue

Interface Builder提供了从一个视图控制器转换到另一个视图控制器的所有标准方式。如果这些方式不能满足需求,也可以创建一个自定义segue。

segue的生命周期

要理解自定义segue如何工作,就需要了解segue对象的生命周期。segue对象是UIStoryboardSegue类或其子类的实例,应用程序永远不会直接创建segue对象。当一个segue被触发时,UIKit会自动创建segue对象。以下是segue被触发时所发生的事情:

  1. 创建并初始化需要呈现的视图控制器。
  2. 创建segue对象并调用其initWithIdentifier:source:destination:方法来初始化。identifier是在Interface Builder中为segue提供的唯一字符串,另外两个参数为参与转换的两个视图控制器对象。
  3. 调用发起呈现的视图控制器的prepareForSegue:sender:方法。
  4. 调用segue对象的perform方法。该方法执行转换以将新的视图控制器显示在屏幕上。
  5. 释放被引用的segue对象。

实现一个自定义segue

为了实现一个自定义segue,需要子类化UIStoryboardSegue并实现以下方法:

  • 覆写initWithIdentifier:source:destination:方法,并使用该方法来初始化自定义的segue对象。需要首先调用super
  • 实现perform方法并使用该方法来配置转场动画。

注意:如果自定义segue类中添加了配置segue的属性,是无法在Interface Builder中配置这些属性的。但可以在触发segue的源视图控制器的prepareForSegue:sender:方法中配置自定义segue的附加属性。

以下代码展示了一个简单的自定义segue,其只是简单的呈现了目标视图控制器,没有使用任何动画,但可以根据需要来扩展该它。

- (void)perform
{
    // Add your own animation code here.

    [[self sourceViewController] presentViewController:[self destinationViewController] animated:NO completion:nil];
}

Demo

Demo地址:https://github.com/Jen668/UIViewControllerDemo

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

推荐阅读更多精彩内容