Xamarin.Mac 中的图像

本文介绍如何在 Xamarin.Mac 应用程序中使用图像和图标。 其中介绍了创建和维护创建应用程序图标和使用 C# 代码和 Xcode 接口生成器中的映像所需的映像。

概述

在 Xamarin.Mac 应用程序中使用 C# 和 .NET 时,可以访问开发人员处理Objective-C和 Xcode 时使用的同一图像和图标工具。

在 macOS(以前称为 Mac OS X)应用程序中使用映像资产有多种方法。 从仅将图像显示为应用程序的 UI 的一部分,将其分配给 UI 控件(如工具栏或源列表项),到提供图标,Xamarin.Mac 可通过以下方式轻松地向 macOS 应用程序添加出色的插图:

  • UI 元素 - 图像可以显示为背景,也可以在图像视图(NSImageView)中作为应用程序的一部分显示。
  • 按钮 - 图像可以显示在按钮 (NSButton) 中。
  • 图像单元格 - 作为基于表的控件的一部分(NSTableViewNSOutlineView),图像可用于图像单元格(NSImageCell)。
  • 工具栏项 - 可将图像作为图像工具栏项添加到工具栏(NSToolbarNSToolbarItem) 中。
  • 源列表图标 - 作为源列表的一部分(特殊格式 NSOutlineView)。
  • 应用图标 - 可将一系列图像组合到一 .icns 组,并用作应用程序的图标。 有关详细信息,请参阅我们的 应用程序图标 文档。

此外,macOS 还提供一组可在应用程序中使用的预定义图像。

An example run of the app

在本文中,我们将介绍在 Xamarin.Mac 应用程序中使用图像和图标的基础知识。 强烈建议先浏览 Hello、Mac 文章,特别是 Xcode 和 Interface Builder 和输出口和 操作 简介部分,因为它介绍本文中将要使用的关键概念和技术。

将图像添加到 Xamarin.Mac 项目

添加图像以便在 Xamarin.Mac 应用程序中使用时,开发人员可以通过多种方式将图像文件包含在项目的源中:

  • 主项目树 [已弃用] - 可以直接将图像添加到项目树。 从代码调用存储在主项目树中的图像时,未指定文件夹位置。 例如:NSImage image = NSImage.ImageNamed("tags.png");
  • 资源文件夹 [已弃用] - 特殊 资源 文件夹适用于将成为应用程序捆绑包的一部分的任何文件,例如图标、启动屏幕或常规图像(或者开发人员希望添加的任何其他图像或文件)。 从代码调用存储在 Resources 文件夹中的图像时,就像存储在主项目树中的图像一样,未指定文件夹位置。 例如:NSImage.ImageNamed("tags.png")
  • 自定义文件夹或子文件夹 [已弃用] - 开发人员可以将自定义文件夹添加到项目源树,并将图像存储在其中。 添加文件的位置可以嵌套在子文件夹中,以进一步帮助组织项目。 例如,如果开发人员向项目添加了一个Card文件夹,并将该文件夹的Hearts子文件夹添加到该文件夹,则在运行时将图像存储在文件夹中 NSImage.ImageNamed("Card/Hearts/Jack.png") Jack.pngHearts
  • 资产目录映像集 [首选] - 在 OS X El Capitan 中添加, 资产目录映像集 包含支持各种设备和应用程序缩放因子所需的映像的所有版本或表示形式。 而不是依赖于图像资产文件名(@1x@2x)。

将图像添加到资产目录映像集

如上所述, 资产目录映像集 包含支持应用程序的各种设备和缩放因素所需的映像的所有版本或表示形式。 图像集使用资产编辑器指定哪些图像属于哪个设备和/或分辨率,而不是依赖图像资产文件名(请参阅上面的“分辨率无关的图像”和“图像名词”)。

  1. 在 Solution Pad,双击 Assets.xcassets 文件将其打开以供编辑:

    Selecting the Assets.xcassets

  2. 右键单击 “资产列表 ”,然后选择“ 新建映像集

    Adding a new image set

  3. 选择新图像集,并显示编辑器:

    Selecting the new image set

  4. 在这里,我们可以拖动每个不同设备和分辨率所需的图像。

  5. 双击资产列表中的新映像集的名称对其进行编辑:

    Editing the image set name

添加到图像集的特殊 Vector 类,允许我们在大小写集中包括 PDF 格式矢量图像,而不是在不同的分辨率中包含单个位图文件。 使用此方法,将为@1x分辨率(格式化为矢量 PDF 文件)提供单个向量文件,并且将在编译时生成文件@2x@3x版本,并将其包含在应用程序的捆绑包中。

The image set editor interface

例如,如果将文件作为资产目录的矢量包含 MonkeyIcon.pdf ,分辨率为 150px x 150px,则编译后,最终应用捆绑包中将包含以下位图资产:

  1. MonkeyIcon@1x.png - 150px x 150px 分辨率。
  2. MonkeyIcon@2x.png - 300px x 300px 分辨率。
  3. MonkeyIcon@3x.png - 450px x 450px 分辨率。

在资产目录中使用 PDF 矢量图像时,应考虑以下事项:

  • 这不是完整的矢量支持,因为 PDF 将在编译时将光栅化为位图,并且位图在最终应用程序中提供。
  • 在资产目录中设置图像后,无法调整图像的大小。 如果尝试调整图像的大小(在代码中或使用自动布局和大小类),图像将像任何其他位图一样扭曲。

在 Xcode 的 Interface Builder 中使用映像集时,只需从属性检查器中的下拉列表中选择集的名称:

Selecting an image set in Xcode's Interface Builder

添加新的资产集合

在资产目录中处理图像时,有时可能需要创建新集合,而不是将所有映像添加到 Assets.xcassets 集合。 例如,在设计按需资源时。

若要向项目添加新的资产目录,请执行以下操作:

  1. 右键单击 Solution Pad 中的项目,然后选择“添加新>文件...”

  2. 选择 Mac>资产目录,输入集合的名称,然后单击“新建”按钮:

    Adding a new Asset Catalog

在这里,可以使用与项目中自动包含的默认 Assets.xcassets 集合相同的方式处理集合。

将图像添加到资源

重要

Apple 已弃用在 macOS 应用中处理图像的方法。 应改用 资产目录映像集 来管理应用的图像。

在 Xamarin.Mac 应用程序中(在 C# 代码或 Interface Builder 中)中使用图像文件之前,需要将其作为捆绑资源包含在项目的 Resources 文件夹中。 若要向项目添加文件,请执行以下操作:

  1. 右键单击 Solution Pad 中项目中的“资源”文件夹,然后选择“添加>文件...”:

    Adding a file

  2. 从“添加文件”对话框中,选择要添加到项目的图像文件,为“替代生成”操作选择BundleResource,然后单击“打开”按钮:

    Selecting the files to add

  3. 如果文件尚未位于 “资源” 文件夹中,系统会询问是否要 复制移动链接 文件。 选择适合你的需求的每个选项,通常是 复制

    Selecting the add action

  4. 新文件将包含在项目中并读取以供使用:

    The new image files added to the Solution Pad

  5. 对所需的任何图像文件重复此过程。

可以将任何 png、jpg 或 pdf 文件用作 Xamarin.Mac 应用程序中的源图像。 在下一部分中,我们将介绍如何添加图像和图标的高分辨率版本以支持基于 Retina 的 Mac。

重要

如果要将“映像”添加到 “资源” 文件夹,可以将 “替代生成”操作 设置为 “默认值”。 此文件夹的默认生成操作为 BundleResource

提供所有应用图形资源的高分辨率版本

添加到 Xamarin.Mac 应用程序的任何图形资产(图标、自定义控件、自定义光标、自定义插图等)除了其标准分辨率版本之外,还需要具有高分辨率版本。 这是必需的,以便在配备 Retina Display 的 Mac 计算机上运行时,应用程序将看起来最佳。

采用@2x命名约定

重要

Apple 已弃用在 macOS 应用中处理图像的方法。 应改用 资产目录映像集 来管理应用的图像。

创建映像的标准和高分辨率版本时,请在 Xamarin.Mac 项目中包括映像对时遵循映像对的命名约定:

  • Standard-Resolution - ImageName.filename-extension (示例: tags.png
  • 高分辨率 - ImageName@2x.filename扩展 (示例: ) tags@2x.png

添加到项目后,它们将如下所示:

The image files in the Solution Pad

将图像分配给 Interface Builder 中的 UI 元素时,只需在 ImageName选取该文件即可。filename-extension 格式(示例:tags.png)。 在 C# 代码中使用图像时,可以在 ImageName 中选择该文件。filename-extension 格式。

在 Mac 上运行 Xamarin.Mac 应用程序时,ImageName。filename-extension format image will be used on Standard Resolution Display, ImageName@2x.filenamethe -extension image will automatically be picked on Retina Display bases Macs.

在 Interface Builder 中使用映像

已添加到 Xamarin.Mac 项目中的 Resources 文件夹的任何图像资源,并将生成操作 设置为 BundleResource 将自动显示在 Interface Builder 中,并且可以选择作为 UI 元素的一部分(如果处理图像)。

若要在接口生成器中使用映像,请执行以下操作:

  1. 使用生成操作BundleResource将映像添加到 Resources 文件夹

    An image resource in the Solution Pad

  2. 双击 Main.storyboard 文件将其打开,以便在 Interface Builder 中编辑:

    Editing the main storyboard

  3. 将图像拖动到设计图面上的 UI 元素(例如 图像工具栏项):

    Editing a toolbar item

  4. 在“映像名称”下拉列表中选择已添加到“资源”文件夹的映像:

    Selecting an image for a toolbar item

  5. 所选图像将显示在设计图面中:

    The image being displayed in the Toolbar editor

  6. 保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。

上述步骤适用于允许在属性检查器设置其图像属性的任何 UI 元素。 同样,如果已包含 图像文件的@2x 版本,它将自动在基于 Retina Display 的 Mac 上使用。

重要

如果图像在“图像名称”下拉列表中不可用,请关闭 Xcode 中的 .storyboard 项目,然后从 Visual Studio for Mac 重新打开它。 如果映像仍然不可用,请确保映像 的生成操作BundleResource 添加到 “资源” 文件夹。

在 C# 代码中使用图像

在 Xamarin.Mac 应用程序中使用 C# 代码将图像加载到内存中时,该映像将存储在对象 NSImage 中。 如果映像文件已包含在 Xamarin.Mac 应用程序捆绑包(包含在资源中),请使用以下代码加载映像:

NSImage image = NSImage.ImageNamed("tags.png");

上面的代码使用类的NSImage静态ImageNamed("...")方法将给定图像加载到 Resources 文件夹中的内存中(如果找不到映像)null将返回。 与 Interface Builder 中分配的图像一样,如果已包含 图像文件的@2x 版本,它将自动在基于 Retina Display 的 Mac 上使用。

若要从应用程序捆绑包之外加载图像(从 Mac 文件系统),请使用以下代码:

NSImage image = new NSImage("/Users/KMullins/Documents/photo.jpg")

使用模板映像

根据 macOS 应用的设计,有时可能需要自定义用户界面中的图标或图像以匹配配色方案的更改(例如,基于用户首选项)。

若要实现此效果,请将 图像资产的呈现模式 切换到 模板图像

Setting a template image

从 Xcode 的 Interface Builder 中,将图像资产分配给 UI 控件:

Selecting an image in Xcode's Interface Builder

或者(可选)在代码中设置映像源代码:

MyIcon.Image = NSImage.ImageNamed ("MessageIcon");

将以下公共函数添加到视图控制器:

public NSImage ImageTintedWithColor(NSImage sourceImage, NSColor tintColor)
    => NSImage.ImageWithSize(sourceImage.Size, false, rect => {
        // Draw the original source image
        sourceImage.DrawInRect(rect, CGRect.Empty, NSCompositingOperation.SourceOver, 1f);

        // Apply tint
        tintColor.Set();
        NSGraphics.RectFill(rect, NSCompositingOperation.SourceAtop);

        return true;
    });

重要

特别是在 macOS Mojave 中出现深色模式时,请务必在重新呈现NSImage自定义对象时避免 LockFocus API。 此类图像变为静态图像,不会自动更新以考虑外观或显示密度更改。

通过使用上述基于处理程序的机制,在托管动态条件时 NSImage ,自动重新呈现动态条件,例如在一个 NSImageView中。

最后,若要淡化模板图像,请针对图像调用此函数以着色:

MyIcon.Image = ImageTintedWithColor (MyIcon.Image, NSColor.Red);

将图像与表视图配合使用

若要在单元格中包含 NSTableView图像,需要更改表视图 NSTableViewDelegate'sGetViewForItem 方法返回数据的方式,以使用 NSTableCellView 而不是典型 NSTextField数据。 例如:

public override NSView GetViewForItem (NSTableView tableView, NSTableColumn tableColumn, nint row)
{

    // This pattern allows you reuse existing views when they are no-longer in use.
    // If the returned view is null, you instance up a new view
    // If a non-null view is returned, you modify it enough to reflect the new data
    NSTableCellView view = (NSTableCellView)tableView.MakeView (tableColumn.Title, this);
    if (view == null) {
        view = new NSTableCellView ();
        if (tableColumn.Title == "Product") {
            view.ImageView = new NSImageView (new CGRect (0, 0, 16, 16));
            view.AddSubview (view.ImageView);
            view.TextField = new NSTextField (new CGRect (20, 0, 400, 16));
        } else {
            view.TextField = new NSTextField (new CGRect (0, 0, 400, 16));
        }
        view.TextField.AutoresizingMask = NSViewResizingMask.WidthSizable;
        view.AddSubview (view.TextField);
        view.Identifier = tableColumn.Title;
        view.TextField.BackgroundColor = NSColor.Clear;
        view.TextField.Bordered = false;
        view.TextField.Selectable = false;
        view.TextField.Editable = true;

        view.TextField.EditingEnded += (sender, e) => {

            // Take action based on type
            switch(view.Identifier) {
            case "Product":
                DataSource.Products [(int)view.TextField.Tag].Title = view.TextField.StringValue;
                break;
            case "Details":
                DataSource.Products [(int)view.TextField.Tag].Description = view.TextField.StringValue;
                break; 
            }
        };
    }

    // Tag view
    view.TextField.Tag = row;

    // Setup view based on the column selected
    switch (tableColumn.Title) {
    case "Product":
        view.ImageView.Image = NSImage.ImageNamed ("tags.png");
        view.TextField.StringValue = DataSource.Products [(int)row].Title;
        break;
    case "Details":
        view.TextField.StringValue = DataSource.Products [(int)row].Description;
        break;
    }

    return view;
}

这里有几行兴趣。 首先,对于要包含图像的列,我们将创建一个新的 NSImageView 所需大小和位置,我们还创建一个新的 NSTextField 位置,并根据我们是否使用图像来放置其默认位置:

if (tableColumn.Title == "Product") {
    view.ImageView = new NSImageView (new CGRect (0, 0, 16, 16));
    view.AddSubview (view.ImageView);
    view.TextField = new NSTextField (new CGRect (20, 0, 400, 16));
} else {
    view.TextField = new NSTextField (new CGRect (0, 0, 400, 16));
}

其次,我们需要在父 NSTableCellView级中包含新的图像视图和文本字段:

view.AddSubview (view.ImageView);
...

view.AddSubview (view.TextField);
...

最后,我们需要告诉文本字段,它可以使用表视图单元格收缩和增长:

view.TextField.AutoresizingMask = NSViewResizingMask.WidthSizable;

示例输出:

An example of displaying an image in an app

有关使用表视图的详细信息,请参阅表 视图 文档。

将图像与大纲视图配合使用

若要在单元格中包含 NSOutlineView图像,需要更改大纲视图 NSTableViewDelegate'sGetView 方法返回数据的方式,以使用 NSTableCellView 常规 NSTextField数据。 例如:

public override NSView GetView (NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item) {
    // Cast item
    var product = item as Product;

    // This pattern allows you reuse existing views when they are no-longer in use.
    // If the returned view is null, you instance up a new view
    // If a non-null view is returned, you modify it enough to reflect the new data
    NSTableCellView view = (NSTableCellView)outlineView.MakeView (tableColumn.Title, this);
    if (view == null) {
        view = new NSTableCellView ();
        if (tableColumn.Title == "Product") {
            view.ImageView = new NSImageView (new CGRect (0, 0, 16, 16));
            view.AddSubview (view.ImageView);
            view.TextField = new NSTextField (new CGRect (20, 0, 400, 16));
        } else {
            view.TextField = new NSTextField (new CGRect (0, 0, 400, 16));
        }
        view.TextField.AutoresizingMask = NSViewResizingMask.WidthSizable;
        view.AddSubview (view.TextField);
        view.Identifier = tableColumn.Title;
        view.TextField.BackgroundColor = NSColor.Clear;
        view.TextField.Bordered = false;
        view.TextField.Selectable = false;
        view.TextField.Editable = !product.IsProductGroup;
    }

    // Tag view
    view.TextField.Tag = outlineView.RowForItem (item);

    // Allow for edit
    view.TextField.EditingEnded += (sender, e) => {

        // Grab product
        var prod = outlineView.ItemAtRow(view.Tag) as Product;

        // Take action based on type
        switch(view.Identifier) {
        case "Product":
            prod.Title = view.TextField.StringValue;
            break;
        case "Details":
            prod.Description = view.TextField.StringValue;
            break; 
        }
    };

    // Setup view based on the column selected
    switch (tableColumn.Title) {
    case "Product":
        view.ImageView.Image = NSImage.ImageNamed (product.IsProductGroup ? "tags.png" : "tag.png");
        view.TextField.StringValue = product.Title;
        break;
    case "Details":
        view.TextField.StringValue = product.Description;
        break;
    }

    return view;
}

这里有几行兴趣。 首先,对于要包含图像的列,我们将创建一个新的 NSImageView 所需大小和位置,我们还创建一个新的 NSTextField 位置,并根据我们是否使用图像来放置其默认位置:

if (tableColumn.Title == "Product") {
    view.ImageView = new NSImageView (new CGRect (0, 0, 16, 16));
    view.AddSubview (view.ImageView);
    view.TextField = new NSTextField (new CGRect (20, 0, 400, 16));
} else {
    view.TextField = new NSTextField (new CGRect (0, 0, 400, 16));
}

其次,我们需要在父 NSTableCellView级中包含新的图像视图和文本字段:

view.AddSubview (view.ImageView);
...

view.AddSubview (view.TextField);
...

最后,我们需要告诉文本字段,它可以使用表视图单元格收缩和增长:

view.TextField.AutoresizingMask = NSViewResizingMask.WidthSizable;

示例输出:

An example of an image being displayed in an Outline View

有关使用大纲视图的详细信息,请参阅大纲 视图 文档。

总结

本文详细介绍了如何使用 Xamarin.Mac 应用程序中的图像和图标。 我们了解了图像的不同类型和用法,如何在 Xcode 的 Interface Builder 中使用图像和图标,以及如何在 C# 代码中使用图像和图标。