Xamarin.iOS 中的消息应用扩展基础知识

本文介绍如何在 Xamarin.iOS 解决方案中包含消息应用扩展,该扩展与消息应用集成并为用户提供新的功能

iOS 10 新增的“消息应用扩展”功能与“消息”应用集成,向用户提供新功能。 该扩展可以发送文本、贴纸、媒体文件和交互式消息。

关于消息应用扩展

如上所述,消息应用扩展与消息用集成,向用户提供新功能。 该扩展可以发送文本、贴纸、媒体文件和交互式消息。 有两种类型的消息应用扩展可用:

  • 贴纸包 - 包含用户可以添加到邮件的贴纸集合。 无需编写任何代码即可创建贴纸包。
  • iMessage 应用 - 可以在“消息”应用中呈现自定义用户界面,用于选择贴纸、输入文本、包含媒体文件(包括可选的类型转换),以及创建、编辑和发送交互消息。

消息应用扩展提供三种主要内容类型:

  • 交互式消息 - 应用生成的自定义消息内容,当用户点击消息时,应用将在前台启动。
  • 贴纸 - 应用生成的图像可以包含在用户之间发送的消息中。
  • 其他支持的内容 - 应用可以提供照片、视频、文本,或消息应用一直支持的任何其他内容类型的链接等内容

在 iOS 10 中,消息应用现在包括其自己的专用内置 App Store。 包含消息应用扩展的任何应用都将在此应用商店中显示和推广。 新的消息应用抽屉将显示已从消息应用商店下载的任何应用,以便快速访问用户。

在 iOS 10 中,Apple 还添加了内联应用归属,允许用户轻松发现应用。 例如,如果一个用户从未安装第二个用户的应用(例如贴纸)将内容发送到另一个应用,则发送应用的名称将列在消息历史记录中的内容下。 如果用户点击应用的名称,则会打开消息应用商店,并在应用商店中选择该应用。

消息应用扩展类似于开发人员熟悉的现有 iOS 应用,它们可以访问标准 iOS 应用的所有标准框架和功能。 例如:

  • 它们可以访问应用内购买。
  • 它们可以访问 Apple Pay。
  • 它们可以访问设备硬件,例如相机。

消息应用扩展仅在 iOS 10 上受支持,但这些扩展发送的内容可在 watchOS 和 macOS 设备上查看。 新增到 watchOS 3 的最近访问的页面将显示从手机发送的最新贴纸,包括来自消息应用扩展的贴纸,并允许用户从手表发送这些贴纸。

关于消息框架

消息框架是 iOS 10 的新增功能,它提供消息应用扩展和用户 iOS 设备上的消息应用之间的接口。 当用户从消息应用内部启动应用时,该框架允许发现应用并提供布局其 UI 所需的数据和上下文。

应用启动后,用户与其交互以创建新内容并通过消息共享。 然后,应用使用消息框架将新建的内容传输到消息应用进行处理。

消息框架和消息应用扩展建立在现有的 iOS 应用扩展技术基础之上。 有关应用扩展的详细信息,请参阅 Apple 的应用扩展编程指南

与 Apple 在整个系统中提供的其他扩展点不同,开发人员不需要为其消息应用扩展提供宿主应用,因为消息应用本身充当容器。 但是,开发人员可以选择将消息应用扩展包含在新的或现有 iOS 应用中,并将其与捆绑包一起交付。

如果将消息应用扩展包含在 iOS 应用的捆绑包中,则该应用的图标将显示在设备的主屏幕上,以及消息应用内的消息应用抽屉中。 如果不将它包含在应用捆绑包中,则消息应用扩展将仅显示在消息应用抽屉中。

即使不将消息应用扩展包含在宿主应用捆绑包中,开发人员也需要在消息应用扩展捆绑包中提供应用图标,因为该图标将显示在系统的其他部分(例如消息应用)中。

关于贴纸

Apple 将贴纸设计为 iMessage 用户沟通的新方式,它允许贴纸与任何其他消息内容一起内嵌发送,或者可以将其附加到对话中之前的消息气泡中。

什么是贴纸?

  • 它们是消息应用扩展提供的图像。
  • 它们可以是动画或静态图像。
  • 它们提供了一种从应用内部共享图像内容的新方法。

可通过两种方式创建贴纸:

  1. 可以从 Xcode 内部创建贴纸包消息应用扩展,而无需包含任何代码。 只需提供贴纸和应用图标的资产即可。
  2. 通过创建一个标准的消息应用扩展,通过消息框架从代码中提供贴纸。

创建贴纸包

贴纸包是基于 Xcode 内部的特殊模板创建的,仅提供一组可用作贴纸的静态图像资产。 如上所述,它们不需要任何代码,开发人员只需将图像文件拖放到贴纸资产目录内的贴纸包文件夹中即可。

要在贴纸包中包含某个图像,它必须满足以下要求:

  • 图像必须采用 PNG、APNG、GIF 或 JPEG 格式。 Apple 建议在提供贴纸资产时仅使用 PNG 和 APNG 格式。
  • 动画贴纸仅支持 APNG 和 GIF 格式。
  • 贴纸图像应提供透明背景,因为用户可能会将其放置在对话中的消息气泡上。
  • 单个图像文件必须小于 500kb。
  • 图像不能小于 100x100 点或大于 206 x 206 点。

重要

贴纸图像应始终以 300 x 300 至 618 x 618 像素范围内的 @3x 分辨率提供。 系统会根据需要在运行时自动生成 @2x@1x 版本。

Apple 建议在各种不同颜色的背景(例如白色、黑色、红色、黄色和多色)和照片上测试贴纸图像资产,以确保它们在所有可能的情况下都呈现最佳外观。

贴纸包可以提供三种可用大小之一的贴纸:

  • 小 - 100 x 100 点
  • 中 - 136 x 136 点。 这是默认大小。
  • 大 - 206 x 206 点

使用 Xcode 的属性检查器设置整个贴纸包的大小,并仅提供与请求大小匹配的图像资产,以便在消息应用内的贴纸浏览器中获得最佳效果。

有关详细信息,请参阅 Apple 的消息参考

创建自定义贴纸体验

如果应用需要比贴纸包提供更好的控制度或灵活性,它可以包含消息应用扩展,并通过消息框架提供贴纸以实现自定义贴纸体验。

创建自定义贴纸体验有哪些好处?

  1. 允许应用自定义如何向应用用户显示贴纸。 例如,以标准网格布局以外的格式或在不同颜色的背景上呈现贴纸。
  2. 允许从代码动态创建贴纸,而不是将贴纸包含为静态图像资产。
  3. 允许从开发人员的 Web 服务器动态下载贴纸图像资产,而无需向 App Store 发布新版本。
  4. 允许访问设备的相机以即时创建贴纸。
  5. 允许应用内购买,以便用户可以从应用内部购买更多贴纸。

若要创建自定义贴纸体验,请执行以下操作:

  1. 启动 Visual Studio for Mac。

  2. 打开要添加消息应用扩展的解决方案。

  3. 选择“iOS”>“扩展”>“iMessage 扩展”,然后单击“下一步”按钮

    选择 iMessage 扩展

  4. 输入一个扩展名称,然后单击“下一步”按钮

    输入扩展名称

  5. 单击“创建”按钮生成扩展

    单击“创建”按钮

默认情况下,MessagesViewController.cs 文件将添加到解决方案。 这是扩展的主要入口点,它继承自 MSMessageAppViewController 类。

消息框架提供了用于向用户呈现可用贴纸的类:

  • MSStickerBrowserViewController - 控制呈现贴纸的视图。 它还符合 IMSStickerBrowserViewDataSource 接口,可以返回给定浏览器索引的贴纸计数和贴纸。
  • MSStickerBrowserView - 这是显示可用贴纸的视图。
  • MSStickerSize - 确定浏览器视图中呈现的贴纸网格的各个单元格大小。

创建自定义贴纸浏览器

开发人员可以通过在消息应用扩展中提供自定义贴纸浏览器 (MSMessageAppBrowserViewController) 来进一步为用户自定义贴纸体验。 当用户选择要包含在消息流中的贴纸时,自定义贴纸浏览器会更改向用户呈现贴纸的方式。

请执行以下操作:

  1. 在“Solution Pad”中,右键单击扩展的项目名称,然后选择“添加”>“新文件...”>“iOS | Apple Watch”>“接口控制器”

  2. 为“名称”输入 StickerBrowserViewController,然后单击“新建”按钮

    为 Name 输入 StickerBrowserViewController

  3. 打开 StickerBrowserViewController.cs 文件进行编辑。

使 StickerBrowserViewController.cs 文件如下所示:

using System;
using System.Collections.Generic;
using UIKit;
using Messages;
using Foundation;

namespace MonkeyStickers
{
    public partial class StickerBrowserViewController : MSStickerBrowserViewController
    {
        #region Computed Properties
        public List<MSSticker> Stickers { get; set; } = new List<MSSticker> ();
        #endregion

        #region Constructors
        public StickerBrowserViewController (MSStickerSize stickerSize) : base (stickerSize)
        {
        }
        #endregion

        #region Private Methods
        private void CreateSticker (string assetName, string localizedDescription)
        {

            // Get path to asset
            var path = NSBundle.MainBundle.PathForResource (assetName, "png");
            if (path == null) {
                Console.WriteLine ("Couldn't create sticker {0}.", assetName);
                return;
            }

            // Build new sticker
            var stickerURL = new NSUrl (path);
            NSError error = null;
            var sticker = new MSSticker (stickerURL, localizedDescription, out error);
            if (error == null) {
                // Add to collection
                Stickers.Add (sticker);
            } else {
                // Report error
                Console.WriteLine ("Error, couldn't create sticker {0}: {1}", assetName, error);
            }
        }

        private void LoadStickers ()
        {

            // Load sticker assets from disk
            CreateSticker ("canada", "Canada Sticker");
            CreateSticker ("clouds", "Clouds Sticker");
            ...
            CreateSticker ("tree", "Tree Sticker");
        }
        #endregion

        #region Public Methods
        public void ChangeBackgroundColor (UIColor color)
        {
            StickerBrowserView.BackgroundColor = color;

        }
        #endregion

        #region Override Methods
        public override void ViewDidLoad ()
        {
            base.ViewDidLoad ();

            // Initialize
            LoadStickers ();
        }

        public override nint GetNumberOfStickers (MSStickerBrowserView stickerBrowserView)
        {
            return Stickers.Count;
        }

        public override MSSticker GetSticker (MSStickerBrowserView stickerBrowserView, nint index)
        {
            return Stickers[(int)index];
        }
        #endregion
    }
}

详细分析上述代码。 它为扩展提供的贴纸创建存储:

public List<MSSticker> Stickers { get; set; } = new List<MSSticker> ();

并重写 MSStickerBrowserViewController 类的两个方法,以便从此数据存储中为浏览器提供数据:

public override nint GetNumberOfStickers (MSStickerBrowserView stickerBrowserView)
{
    return Stickers.Count;
}

public override MSSticker GetSticker (MSStickerBrowserView stickerBrowserView, nint index)
{
    return Stickers[(int)index];
}

CreateSticker 方法从扩展捆绑包中获取图像资产的路径,使用它从该资产创建 MSSticker 的新实例,并将其添加到集合中:

private void CreateSticker (string assetName, string localizedDescription)
{

    // Get path to asset
    var path = NSBundle.MainBundle.PathForResource (assetName, "png");
    if (path == null) {
        Console.WriteLine ("Couldn't create sticker {0}.", assetName);
        return;
    }

    // Build new sticker
    var stickerURL = new NSUrl (path);
    NSError error = null;
    var sticker = new MSSticker (stickerURL, localizedDescription, out error);
    if (error == null) {
        // Add to collection
        Stickers.Add (sticker);
    } else {
        // Report error
        Console.WriteLine ("Error, couldn't create sticker {0}: {1}", assetName, error);
    }
}

ViewDidLoad 调用 LoadSticker 方法,以从指定的图像资产(包含在应用的捆绑包中)创建贴纸并将其添加到贴纸集合中。

若要实现自定义贴纸浏览器,请编辑 MessagesViewController.cs 文件并使其如下所示:

using System;
using UIKit;
using Messages;

namespace MonkeyStickers
{
    public partial class MessagesViewController : MSMessagesAppViewController
    {
        #region Computed Properties
        public StickerBrowserViewController BrowserViewController { get; set;}
        #endregion

        #region Constructors
        protected ViewController (IntPtr handle) : base (handle)
        {
            // Note: this .ctor should not contain any initialization logic.
        }
        #endregion

        #region Override Methods
        public override void ViewDidLoad ()
        {
            base.ViewDidLoad ();

            // Create new browser and configure it
            BrowserViewController = new StickerBrowserViewController (MSStickerSize.Regular);
            BrowserViewController.View.Frame = View.Frame;
            BrowserViewController.ChangeBackgroundColor (UIColor.Gray);

            // Add to view
            AddChildViewController (BrowserViewController);
            BrowserViewController.DidMoveToParentViewController (this);
            View.AddSubview (BrowserViewController.View);
        }
        #endregion
    }
}

详细分析此代码:它为自定义浏览器创建存储:

public StickerBrowserViewController BrowserViewController { get; set;}

ViewDidLoad 方法中,它实例化并配置一个新浏览器:

// Create new browser and configure it
BrowserViewController = new StickerBrowserViewController (MSStickerSize.Regular);
BrowserViewController.View.Frame = View.Frame;
BrowserViewController.ChangeBackgroundColor (UIColor.Gray);

然后它将浏览器添加到视图中以显示该浏览器:

// Add to view
AddChildViewController (BrowserViewController);
BrowserViewController.DidMoveToParentViewController (this);
View.AddSubview (BrowserViewController.View);

进一步的贴纸自定义

只需在消息应用扩展中包含两个类即可进一步自定义贴纸:

  • MSStickerView
  • MSSticker

使用上述方法,扩展可以支持不依赖于标准贴纸浏览器方法的贴纸选择。 此外,贴纸显示内容可以在两种不同的视图模式之间切换:

  • 紧凑 - 这是默认模式,贴纸视图占据消息视图底部的 25%
  • 展开 - 贴纸视图填充整个消息视图

此贴纸视图可以由用户以编程方式或手动方式在这些模式之间切换。

查看以下示例,了解如何处理两种不同视图模式之间的切换。 每个状态都需要两个不同的视图控制器。 StickerBrowserViewController 处理“紧凑”视图,如下所示

using System;
using System.Collections.Generic;
using UIKit;
using Messages;
using Foundation;

namespace MessageExtension
{
    public partial class StickerBrowserViewController : MSStickerBrowserViewController
    {
        #region Computed Properties
        public MessagesViewController MessagesAppViewController { get; set; }
        public List<MSSticker> Stickers { get; set; } = new List<MSSticker> ();
        #endregion

        #region Constructors
        public StickerBrowserViewController (MessagesViewController messagesAppViewController, MSStickerSize stickerSize) : base (stickerSize)
        {
            // Initialize
            this.MessagesAppViewController = messagesAppViewController;
        }
        #endregion

        #region Private Methods
        private void CreateSticker (string assetName, string localizedDescription)
        {

            // Get path to asset
            var path = NSBundle.MainBundle.PathForResource (assetName, "png");
            if (path == null) {
                Console.WriteLine ("Couldn't create sticker {0}.", assetName);
                return;
            }

            // Build new sticker
            var stickerURL = new NSUrl (path);
            NSError error = null;
            var sticker = new MSSticker (stickerURL, localizedDescription, out error);
            if (error == null) {
                // Add to collection
                Stickers.Add (sticker);
            } else {
                // Report error
                Console.WriteLine ("Error, couldn't create sticker {0}: {1}", assetName, error);
            }
        }

        private void LoadStickers ()
        {

            // Load sticker assets from disk
            CreateSticker ("add", "Add New Sticker");
            CreateSticker ("canada", "Canada Sticker");
            CreateSticker ("clouds", "Clouds Sticker");
            CreateSticker ("tree", "Tree Sticker");
        }
        #endregion

        #region Public Methods
        public void ChangeBackgroundColor (UIColor color)
        {
            StickerBrowserView.BackgroundColor = color;

        }
        #endregion

        #region Override Methods
        public override void ViewDidLoad ()
        {
            base.ViewDidLoad ();

            // Initialize
            LoadStickers ();

        }

        public override nint GetNumberOfStickers (MSStickerBrowserView stickerBrowserView)
        {
            return Stickers.Count;
        }

        public override MSSticker GetSticker (MSStickerBrowserView stickerBrowserView, nint index)
        {
            // Wanting to add a new sticker?
            if (index == 0) {
                // Yes, ask controller to present add sticker interface
                MessagesAppViewController.AddNewSticker ();
                return null;
            } else {
                // No, return existing sticker
                return Stickers [(int)index];
            }
        }
        #endregion
    }
}

AddStickerViewController 处理“展开”贴纸视图,如下所示

using System;
using Foundation;
using UIKit;
using Messages;

namespace MessageExtension
{
    public class AddStickerViewController : UIViewController
    {
        #region Computed Properties
        public MessagesViewController MessagesAppViewController { get; set;}
        public MSSticker NewSticker { get; set;}
        #endregion

        #region Constructors
        public AddStickerViewController (MessagesViewController messagesAppViewController)
        {
            // Initialize
            this.MessagesAppViewController = messagesAppViewController;
        }
        #endregion

        #region Override Method
        public override void ViewDidLoad ()
        {
            base.ViewDidLoad ();

            // Build interface to create new sticker
            var cancelButton = new UIButton (UIButtonType.RoundedRect);
            cancelButton.TouchDown += (sender, e) => {
                // Cancel add new sticker
                MessagesAppViewController.CancelAddNewSticker ();
            };
            View.AddSubview (cancelButton);

            var doneButton = new UIButton (UIButtonType.RoundedRect);
            doneButton.TouchDown += (sender, e) => {
                // Add new sticker to collection
                MessagesAppViewController.AddStickerToCollection (NewSticker);
            };
            View.AddSubview (doneButton);

            ...
        }
        #endregion
    }
}

MessageViewController 实现这些视图控制器以驱动请求的状态:

using System;
using UIKit;
using Messages;

namespace MessageExtension
{
    public partial class MessagesViewController : MSMessagesAppViewController
    {
        #region Computed Properties
        public bool IsAddingSticker { get; set;}
        public StickerBrowserViewController BrowserViewController { get; set; }
        public AddStickerViewController AddStickerController { get; set;}
        #endregion

        #region Constructors
        protected MessagesViewController (IntPtr handle) : base (handle)
        {
            // Note: this .ctor should not contain any initialization logic.
        }
        #endregion

        #region Public Methods
        public void PresentStickerBrowser ()
        {
            // Is the Add sticker view being displayed?
            if (IsAddingSticker) {
                // Yes, remove it from view
                AddStickerController.RemoveFromParentViewController ();
                AddStickerController.View.RemoveFromSuperview ();
            }

            // Add to view
            AddChildViewController (BrowserViewController);
            BrowserViewController.DidMoveToParentViewController (this);
            View.AddSubview (BrowserViewController.View);

            // Save mode
            IsAddingSticker = false;
        }

        public void PresentAddSticker ()
        {
            // Is the sticker browser being displayed?
            if (!IsAddingSticker) {
                // Yes, remove it from view
                BrowserViewController.RemoveFromParentViewController ();
                BrowserViewController.View.RemoveFromSuperview ();
            }

            // Add to view
            AddChildViewController (AddStickerController);
            AddStickerController.DidMoveToParentViewController (this);
            View.AddSubview (AddStickerController.View);

            // Save mode
            IsAddingSticker = true;
        }

        public void AddNewSticker ()
        {
            // Switch to expanded view mode
            Request (MSMessagesAppPresentationStyle.Expanded);
        }

        public void CancelAddNewSticker ()
        {
            // Switch to compact view mode
            Request (MSMessagesAppPresentationStyle.Compact);
        }

        public void AddStickerToCollection (MSSticker sticker)
        {
            // Add sticker to collection
            BrowserViewController.Stickers.Add (sticker);

            // Switch to compact view mode
            Request (MSMessagesAppPresentationStyle.Compact);
        }
        #endregion

        #region Override Methods
        public override void ViewDidLoad ()
        {
            base.ViewDidLoad ();

            // Create new browser and configure it
            BrowserViewController = new StickerBrowserViewController (this, MSStickerSize.Regular);
            BrowserViewController.View.Frame = View.Frame;
            BrowserViewController.ChangeBackgroundColor (UIColor.Gray);

            // Create new Add controller and configure it as well
            AddStickerController = new AddStickerViewController (this);
            AddStickerController.View.Frame = View.Frame;

            // Initially present the sticker browser
            PresentStickerBrowser ();
        }

        public override void DidTransition (MSMessagesAppPresentationStyle presentationStyle)
        {
            base.DidTransition (presentationStyle);

            // Take action based on style
            switch (presentationStyle) {
            case MSMessagesAppPresentationStyle.Compact:
                PresentStickerBrowser ();
                break;
            case MSMessagesAppPresentationStyle.Expanded:
                PresentAddSticker ();
                break;
            }
        }
        #endregion
    }
}

当用户请求将新贴纸添加到其可用集合中时,会将一个新的 AddStickerViewController 设为可见控制器,贴纸视图将进入“扩展”视图模式

// Switch to expanded view mode
Request (MSMessagesAppPresentationStyle.Expanded);

当用户选择要添加的贴纸时,该贴纸将添加到用户的可用集合中,并请求“紧凑”视图

public void AddStickerToCollection (MSSticker sticker)
{
    // Add sticker to collection
    BrowserViewController.Stickers.Add (sticker);

    // Switch to compact view mode
    Request (MSMessagesAppPresentationStyle.Compact);
}

重写 DidTransition 方法以处理两种模式之间的切换:

public override void DidTransition (MSMessagesAppPresentationStyle presentationStyle)
{
    base.DidTransition (presentationStyle);

    // Take action based on style
    switch (presentationStyle) {
    case MSMessagesAppPresentationStyle.Compact:
        PresentStickerBrowser ();
        break;
    case MSMessagesAppPresentationStyle.Expanded:
        PresentAddSticker ();
        break;
    }
}

总结

本文介绍了如何在 Xamarin.iOS 解决方案中包含消息应用扩展,该扩展与消息应用集成并为用户提供新的功能。 其中介绍了如何使用扩展来发送文本、贴纸、媒体文件和交互式消息。