在 Xamarin.Mac 中使用情节提要

情节提要定义给定应用的所有 UI,这些 UI 细分为其视图控制器的功能概述。 在 Xcode 的 Interface Builder 中,每个控制器都位于其自己的场景中。

A storyboard in Xcode's Interface Builder

情节提要是一个资源文件(扩展名 .storyboard为),在编译和交付时,该文件包含在 Xamarin.Mac 应用的捆绑包中。 若要为应用定义起始情节提要,请编辑其 Info.plist 文件,然后从下拉列表框中选择“主界面”

The Info.plist editor

从代码加载

有时可能需要从代码加载特定的情节提要,并手动创建视图控制器。 可以使用以下代码执行此操作:

// Get new window
var storyboard = NSStoryboard.FromName ("Main", null);
var controller = storyboard.InstantiateControllerWithIdentifier ("MainWindow") as NSWindowController;

// Display
controller.ShowWindow(this);

FromName 加载具有应用捆绑包中包含的给定名称的 Storyboard 文件。 InstantiateControllerWithIdentifier 使用给定标识创建视图控制器的实例。 在设计 UI 时,在 Xcode 的 Interface Builder 中设置标识:

Setting the Storyboard ID in Interface Builder.

(可选)可以使用 InstantiateInitialController 方法加载 Interface Builder 中已分配初始控制器的视图控制器:

Setting the initial controller

它由情节提要入口点和上面的开口箭头标记。

视图控制器

视图控制器定义了 Mac 应用内给定信息视图与提供该信息的数据模型之间的关系。 情节提要中的每个顶级场景代表 Xamarin.Mac 应用代码中的一个视图控制器。

视图控制器生命周期

NSViewController 类添加了几个新方法,以支持 macOS 中的情节提要。 最重要的是,以下方法用于响应由给定视图控制器控制的视图的生命周期:

  • ViewDidLoad - 从情节提要文件加载视图时调用此方法。
  • ViewWillAppear - 此方法是在屏幕上显示视图之前调用的。
  • ViewDidAppear - 此方法直接在屏幕上显示视图后调用。
  • ViewWillDisappear - 在从屏幕中删除视图之前,将调用此方法。
  • ViewDidDisappear - 在从屏幕中删除视图后,直接调用此方法。
  • UpdateViewConstraints - 定义视图自动布局位置和大小的约束需要更新时,将调用此方法。
  • ViewWillLayout - 此方法在屏幕布局此视图的子视图之前调用。
  • ViewDidLayout - 在屏幕布局视图子视图后,直接调用此方法。

响应方链

此外,NSViewControllers 现在是窗口响应者链的一部分:

The Responder Chain

因此,它们通过有线方式接收和响应剪切、复制和粘贴菜单项等事件。 这种自动视图控制器连接仅在 macOS Sierra (10.12) 及更高级别的应用上运行。

遏制

在情节提要中,视图控制器(如拆分视图控制器和选项卡视图控制器)现在可以实现包含,以便它们可以“包含”其他子视图控制器:

An example of View Controller Containment

子视图控制器包含的方法和属性,用于将它们关联回父视图控制器,并处理从屏幕中显示和删除视图。

macOS 中内置的所有容器视图控制器都有一个特定的布局,Apple 建议在创建自己的自定义容器视图控制器时遵循该布局:

The View Controller layout

集合视图控制器包含集合视图项的数组,每个数组都包含一个或多个包含其自己的视图的视图控制器。

Segues

Segues 提供定义应用 UI 的所有场景之间的关系。 如果你熟悉 iOS 中情节提要的使用方式,则就会知道,iOS 的 Segue 通常定义全屏视图之间的转换。 这不同于 macOS,Segues 通常定义“包含”,其中一个场景是父场景的子级。

在 macOS 中,大多数应用倾向于使用拆分视图和选项卡等 UI 元素将其视图分组在同一窗口中。 与 iOS 不同的是,由于物理显示空间有限,视图需要在屏幕内外转换。

演示文稿 Segues

鉴于 macOS 的包容性倾向,在某些情况下会使用呈现 Segue,例如模态窗口、工作表视图和弹出窗口。 macOS 提供以下内置 segue 类型:

  • 显示 - 将 Segue 的目标显示为非模式窗口。 例如,使用此类型的 Segue 在应用中显示文档窗口的另一个实例。
  • 模式 - 将 Segue 的目标呈现为模式窗口。 例如,使用此类型的 Segue 来显示应用的“首选项”窗口。
  • 工作表 - 将 Segue 的目标显示为附加到父窗口的工作表。 例如,使用此类型的 segue 来显示“查找”和“替换工作表”。
  • 弹出窗口 - 将 Segue 的目标呈现为弹出窗口。 例如,当用户单击 UI 元素时,使用此 Segue 类型来显示选项。
  • 自定义 - 使用开发人员定义的自定义 Segue 类型呈现 Segue 的目标。 有关详细信息,请参阅下面的创建自定义 Segues 部分。

使用呈现 Segue 时,可以重写用于呈现的父视图控制器的 PrepareForSegue 方法,以初始化变量并向正在呈现的视图控制器提供任何数据。

触发的 Segues

触发的 Segues 允许你指定命名的 Segues(通过 Interface Builder 中的 Identifier 属性),并让用户通过单击按钮或调用代码中的 PerformSegue 方法等事件触发:

// Display the Scene defined by the given Segue ID
PerformSegue("MyNamedSegue", this);

在布局应用的 UI 时,Segue ID 在 Xcode 的 Interface Builder 中定义:

Entering a Segue Name

在充当 Segue 源的视图控制器中,应重写 PrepareForSegue 方法,并在执行 Segue 之前执行所需的任何初始化,并显示指定的视图控制器:

public override void PrepareForSegue (NSStoryboardSegue segue, NSObject sender)
{
    base.PrepareForSegue (segue, sender);

    // Take action based on Segue ID
    switch (segue.Identifier) {
    case "MyNamedSegue":
        // Prepare for the segue to happen
        ...
        break;
    }
}

(可选)可以重写 ShouldPerformSegue 方法,并控制是否通过 C# 代码实际执行 Segue。 对于手动显示的视图控制器,请调用其 DismissController 方法,以便在不再需要视图控制器时将其从显示中删除。

创建自定义 Segues

有时,你的应用可能需要 macOS 中定义的内置 Segues 所不提供的 Segue 类型。 如果是这种情况,可以在布局应用的 UI 时创建可在 Xcode 的 Interface Builder 中分配的自定义 Segue。

例如,若要创建一个新的 Segue 类型,该类型替换窗口内的当前视图控制器(而不是在新窗口中打开目标场景),可以使用以下代码:

using System;
using AppKit;
using Foundation;

namespace OnCardMac
{
    [Register("ReplaceViewSeque")]
    public class ReplaceViewSeque : NSStoryboardSegue
    {
        #region Constructors
        public ReplaceViewSeque() {

        }

        public ReplaceViewSeque (string identifier, NSObject sourceController, NSObject destinationController) : base(identifier,sourceController,destinationController) {

        }

        public ReplaceViewSeque (IntPtr handle) : base(handle) {
        }

        public ReplaceViewSeque (NSObjectFlag x) : base(x) {
        }
        #endregion

        #region Override Methods
        public override void Perform ()
        {
            // Cast the source and destination controllers
            var source = SourceController as NSViewController;
            var destination = DestinationController as NSViewController;

            // Swap the controllers
            source.View.Window.ContentViewController = destination;

            // Release memory
            source.RemoveFromParentViewController ();
        }
        #endregion

    }
        
}

此处要注意的几个事项:

  • 我们使用 Register 属性向 Objective-C/macOS 公开此类。
  • 我们正在重写 Perform 方法,以实际执行自定义 Segue 的操作。
  • 我们将窗口的 ContentViewController 控制器替换为 Segue 的目标(目标)定义的控制器。
  • 我们将删除原始视图控制器,以使用 RemoveFromParentViewController 方法释放内存。

若要在 Xcode 的 Interface Builder 中使用这一新的 Segue 类型,首先需要编译应用,然后切换到 Xcode 并在两个场景之间添加新 Segue。 将“样式”设置为“自定义”,将“Segue 类”设置为 ReplaceViewSegue(自定义 Segue 类的名称):

Setting the Segue class

窗口控制器

窗口控制器包含和控制 macOS 应用可以创建的不同窗口类型。 对于情节提要,他们具有以下功能:

  1. 它们必须提供内容视图控制器。 这与子窗口具有的内容视图控制器相同。
  2. Storyboard 属性将包含窗口控制器从中加载的情节提要,否则 null(如果未从情节提要加载)。
  3. 可以调用 DismissController 方法关闭给定的窗口并将其从视图中删除。

与视图控制器一样,窗口控制器实现 PerformSeguePrepareForSegueShouldPerformSegue 方法,并可用作 Segue 操作的源。

窗口控制器负责 macOS 应用的以下功能:

  • 管理特定窗口。
  • 它们管理窗口的标题栏和工具栏(如果可用)。
  • 它们管理内容视图控制器以显示窗口的内容。

手势识别器

macOS 的手势识别器与 iOS 中的对应项几乎完全相同,并允许开发人员轻松地将手势(例如单击鼠标按钮)添加到应用 UI 中的元素。

但是,iOS 中的手势由应用的设计(如用两根手指点击屏幕)决定,macOS 中的大多数手势由硬件决定。

通过使用手势识别器,可以大大减少向 UI 中的项添加自定义交互所需的代码量。 因为它们可以自动判断双击和单击、单击和拖动事件等。

使用 Storyboard 时,应使用手势识别器来处理用户输入事件,而不是在视图控制器中重写 MouseDown 事件。

macOS 中提供了以下手势识别器:

  • NSClickGestureRecognizer - 注册鼠标向下和向上事件。
  • NSPanGestureRecognizer - 向下注册鼠标按钮、拖动和释放事件。
  • NSPressGestureRecognizer - 注册将鼠标按钮按住给定的时间量事件。
  • NSMagnificationGestureRecognizer - 从触控板硬件注册放大事件。
  • NSRotationGestureRecognizer - 从触控板硬件注册旋转事件。

使用情节提要引用

情节提要参考使你能够采用大型复杂情节提要设计,并将其分解为从原始内容中引用的较小情节提要,从而消除复杂性并使生成的单个情节提要更易于设计和维护。

此外,情节提要参考可以为同一情节提要中的另一个场景或不同场景上的特定场景提供定位点

引用外部情节提要

若要添加对外部情节提要的引用,请执行以下操作:

  1. 在“解决方案资源管理器”中,右键单击项目名称并选择“添加”>“新建文件..>“Mac”>“情节提要”。 输入新情节提要的“名称”,然后单击“新建”按钮:

    Adding a new Storyboard

  2. 在“解决方案资源管理器”中,双击新的情节提要名称以在 Xcode 的 Interface Builder 中打开编辑。

  3. 像平时一样设计新情节提要场景的布局,并保存更改:

    Designing the interface

  4. 切换到要在 Interface Builder 中添加引用的情节提要。

  5. 将“情节提要参考”从“对象库”拖到设计图面上:

    Selecting a Storyboard Reference in the Library

  6. 在“属性检查器”中,选择上面创建的“情节提要”的名称:

    Configuring the reference

  7. 在现有场景中单击 UI 小组件(如按钮),并创建一个新的 Segue 到刚刚创建的情节提要参考。 从弹出菜单中选择“显示”,完成 Segue:

    Setting the Segue type

  8. 将更改保存到情节提要。

  9. 返回到 Visual Studio for Mac 以同步更改。

当应用运行时,用户单击从中创建 Segue 的 UI 元素时,将显示情节提要引用中指定的外部情节提要的初始窗口控制器。

在外部情节提要中引用特定场景

若要添加对特定场景的引用,外部情节提要(而不是初始窗口控制器),请执行以下操作:

  1. 在“解决方案资源管理器”中,双击外部情节提要将其打开,以便在 Xcode 的 Interface Builder 中编辑。

  2. 像平时一样添加新的场景并设计其布局:

    Designing the layout in Xcode

  3. 在“标识检查器”中,为新场景的窗口控制器输入“情节提要 ID”

    Setting the Storyboard I D to AltScene under Identity.

  4. 打开要在 Interface Builder 中添加引用的情节提要。

  5. 将“情节提要参考”从“对象库”拖到设计图面上:

    Selecting a Storyboard Reference from the Library

  6. 在“标识检查器”中,选择上面创建的场景的“情节提要”的名称和参考 ID(情节提要 ID):

    Setting the Reference I D to AltScene under Storyboard Reference.

  7. 在现有场景中单击 UI 小组件(如按钮),并创建一个新的 Segue 到刚刚创建的情节提要参考。 从弹出菜单中选择“显示”,完成 Segue:

    Setting the Segue Type

  8. 将更改保存到情节提要。

  9. 返回到 Visual Studio for Mac 以同步更改。

当应用运行时,用户单击你创建的 Segue 所源自的 UI 元素,将显示在情节提要参考中指定的外部情节提要中具有给定情节提要 ID 的场景。

引用同一情节提要中的特定场景

若要添加对同一情节提要的特定场景的引用,请执行以下操作:

  1. 在“解决方案资源管理器”中,双击情节提要将其打开进行编辑。

  2. 像平时一样添加新的场景并设计其布局:

    Editing the storyboard in Xcode

  3. 在“标识检查器”中,为新场景的窗口控制器输入“情节提要 ID”

    Setting the Storyboard I D to IntScene under Identity.

  4. 将“情节提要参考”工具箱拖到设计图面上:

    Selecting a Storyboard Reference from the Library

  5. 在“属性检查器”中,选择上面创建的场景的“引用 ID”(情节提要 ID):

    Setting the Reference I D to IntScene under Storyboard Reference.

  6. 在现有场景中单击 UI 小组件(如按钮),并创建一个新的 Segue 到刚刚创建的情节提要参考。 从弹出菜单中选择“显示”,完成 Segue:

    Selecting the Segue Type

  7. 将更改保存到情节提要。

  8. 返回到 Visual Studio for Mac 以同步更改。

当应用运行并且用户单击从中创建 Segue 的 UI 元素时,将显示在情节提要引用中指定的同一情节提要中具有给定情节提要 ID 的场景。

复杂情节提要示例

有关在 Xamarin.Mac 应用中使用情节提要的复杂示例,请参阅 SourceWriter 示例应用。 SourceWriter 是一个非常简单的源代码编辑器,提供代码补全和简单语法突出显示支持。

SourceWriter 代码已经完全注释,且在可用时,提供了相关链接,链接涵盖了从关键技术或方法到 Xamarin.Mac 指南文档中的相关信息。