Atalhos da Siri no Xamarin.iOS

No iOS 10, a Apple introduziu o SiriKit, tornando possível criar aplicativos de mensagens, chamadas VoIP, pagamentos, treinos, reserva de viagens e pesquisa de fotos que interagem com a Siri.

No iOS 11, o SiriKit ganhou suporte para mais tipos de aplicativos e maior flexibilidade para personalização da interface do usuário.

O iOS 12 adiciona os Atalhos da Siri, permitindo que todos os tipos de aplicativos exponham suas funcionalidades à Siri. A Siri aprende quando determinadas tarefas baseadas em aplicativos são mais relevantes para o usuário e usa esse conhecimento para sugerir possíveis ações por meio de atalhos. Tocar em um atalho ou invocá-lo com um comando de voz abrirá um aplicativo ou executará uma tarefa em segundo plano.

Os atalhos devem ser usados para acelerar a capacidade de um usuário de realizar uma tarefa comum – em muitos casos, sem sequer abrir o aplicativo em questão.

Aplicativo de exemplo: Soup Chef

Para entender melhor os Atalhos da Siri, dê uma olhada no aplicativo de exemplo Soup Chef . O Soup Chef permite que os usuários façam pedidos de um restaurante de sopa imaginário, visualizem seu histórico de pedidos e definam frases para usar ao pedir sopa interagindo com a Siri.

Dica

Antes de testar o Soup Chef em um simulador ou dispositivo iOS 12, ative as duas configurações a seguir, que são úteis ao depurar atalhos:

  • No aplicativo Configurações, habilite Atalhos recentes de exibição do desenvolvedor>.
  • No aplicativo Configurações, habilite Doações de exibição do desenvolvedor > na tela de bloqueio.

Essas configurações de depuração facilitam a localização de atalhos recém-criados (em vez de previstos) na tela de bloqueio e na tela de pesquisa do iOS.

Para usar o aplicativo de exemplo:

  • Instale e execute o aplicativo de exemplo Soup Chef em um simulador ou dispositivo iOS 12.
  • + Clique no botão no canto superior direito para criar uma nova ordem.
  • Selecione um tipo de sopa, especifique uma quantidade e opções e toque em Fazer pedido.
  • Na tela Histórico de pedidos, toque no pedido recém-criado para exibir seus detalhes.
  • Na parte inferior da tela de detalhes do pedido, toque em Adicionar à Siri.
  • Grave uma frase de voz para associar à ordem e toque em Concluído.
  • Minimize o Soup Chef, invoque a Siri e faça o pedido novamente usando a frase de voz que você gravou.
  • Depois que a Siri concluir o pedido, reabra o Soup Chef e observe que o novo pedido está listado na tela Histórico de pedidos.

O aplicativo de exemplo demonstra como:

Info.plist e Entitlements.plist

Antes de se aprofundar no código do Soup Chef, dê uma olhada em seus arquivos Info.plist e Entitlements.plist.

Info.plist

O arquivo Info.plist no projeto SoupChef define o Identificador de pacote como com.xamarin.SoupChef. Esse identificador de pacote será usado como um prefixo para os identificadores de pacote das extensões de interface do usuário Intents e Intents discutidas posteriormente neste documento.

O arquivo Info.plist também contém a seguinte entrada:

<key>NSUserActivityTypes</key>
<array>
    <string>OrderSoupIntent</string>
    <string>com.xamarin.SoupChef.viewMenu</string>
</array>

Esse NSUserActivityTypes par chave/valor indica que o Soup Chef sabe como lidar com um , e um ter um ActivityTypeOrderSoupIntentNSUserActivity de "com.xamarin.SoupChef.viewMenu".

Atividades e intenções personalizadas passadas para o próprio aplicativo, em oposição às suas extensões, são manipuladas no AppDelegate (a UIApplicationDelegate pelo ContinueUserActivity método.

Entitlements.plist

O arquivo Entitlements.plist no projeto SoupChef contém as seguintes entradas:

<key>com.apple.security.application-groups</key>
<array>
    <string>group.com.xamarin.SoupChef</string>
</array>
<key>com.apple.developer.siri</key>
<true/>

Essa configuração indica que o aplicativo usa o grupo de aplicativos "group.com.xamarin.SoupChef". A extensão do aplicativo SoupChefIntents usa esse mesmo grupo de aplicativos, o que permite que os dois projetos compartilhem Dados NSUserDefaults.

A com.apple.developer.siri chave indica que o aplicativo interage com a Siri.

Observação

A configuração de compilação do projeto SoupChef define Direitos Personalizados para Entitlements.plist.

Usando um atalho NSUserActivity para abrir um aplicativo

Para criar um atalho que abra um aplicativo para exibir conteúdo específico, crie um NSUserActivity e anexe-o ao controle de exibição da tela que você deseja que o atalho abra.

Configurando uma NSUserActivity

Na tela de menu, SoupMenuViewController cria um NSUserActivity e atribui-o à propriedade do controlador de UserActivity exibição:

public override void ViewDidLoad()
{
    base.ViewDidLoad();
    UserActivity = NSUserActivityHelper.ViewMenuActivity;
}

A configuração da UserActivity propriedade doa a atividade para a Siri. A partir dessa doação, a Siri ganha informações sobre quando e onde essa atividade é relevante para o usuário e aprende a sugeri-la melhor no futuro.

NSUserActivityHelperé uma classe de utilitário incluída na solução SoupChef, na biblioteca de classes SoupKit. Ele cria um NSUserActivity e define várias propriedades relacionadas à Siri e pesquisa:

public static string ViewMenuActivityType = "com.xamarin.SoupChef.viewMenu";

public static NSUserActivity ViewMenuActivity {
    get
    {
        var userActivity = new NSUserActivity(ViewMenuActivityType)
        {
            Title = NSBundleHelper.SoupKitBundle.GetLocalizedString("ORDER_LUNCH_TITLE", "View menu activity title"),
            EligibleForSearch = true,
            EligibleForPrediction = true
        };

        var attributes = new CSSearchableItemAttributeSet(NSUserActivityHelper.SearchableItemContentType)
        {
            ThumbnailData = UIImage.FromBundle("tomato").AsPNG(),
            Keywords = ViewMenuSearchableKeywords,
            DisplayName = NSBundleHelper.SoupKitBundle.GetLocalizedString("ORDER_LUNCH_TITLE", "View menu activity title"),
            ContentDescription = NSBundleHelper.SoupKitBundle.GetLocalizedString("VIEW_MENU_CONTENT_DESCRIPTION", "View menu content description")
        };
        userActivity.ContentAttributeSet = attributes;

        var phrase = NSBundleHelper.SoupKitBundle.GetLocalizedString("ORDER_LUNCH_SUGGESTED_PHRASE", "Voice shortcut suggested phrase");
        userActivity.SuggestedInvocationPhrase = phrase;
        return userActivity;
    }
}

Observe os seguintes recursos em particular:

  • A configuração EligibleForPrediction indica que a true Siri pode prever essa atividade e apresentá-la como um atalho.
  • A ContentAttributeSet matriz é um padrão CSSearchableItemAttributeSet usado para incluir um nos resultados de NSUserActivity pesquisa do iOS.
  • SuggestedInvocationPhrase é uma frase que a Siri sugerirá ao usuário como uma escolha potencial ao atribuir uma frase a um atalho.

Manipulando um atalho NSUserActivity

Para manipular um atalho invocado por um usuário, um NSUserActivity aplicativo iOS deve substituir o AppDelegateContinueUserActivity método da classe, respondendo com base no ActivityType campo do objeto transmitidoNSUserActivity:

public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
    // ...
    else if (userActivity.ActivityType == NSUserActivityHelper.ViewMenuActivityType)
    {
        HandleUserActivity();
        return true;
    }
    // ...
}

Esse método chama HandleUserActivity, que localiza o segue para a tela de menu e o invoca:

void HandleUserActivity()
{
    var rootViewController = Window?.RootViewController as UINavigationController;
    var orderHistoryViewController = rootViewController?.ViewControllers?.FirstOrDefault() as OrderHistoryTableViewController;
    if (orderHistoryViewController is null)
    {
        Console.WriteLine("Failed to access OrderHistoryTableViewController.");
        return;
    }
    var segue = OrderHistoryTableViewController.SegueIdentifiers.SoupMenu;
    orderHistoryViewController.PerformSegue(segue, null);
}

Atribuindo uma frase a um NSUserActivity

Para atribuir uma frase a um NSUserActivity, abra o aplicativo Configurações do iOS e escolha Siri & Pesquisar > Meus Atalhos. Em seguida, selecione o atalho (neste caso, "Pedir almoço") e grave uma frase.

Invocar a Siri e usar esta frase abrirá o Soup Chef na tela do menu.

Usando um atalho de intenção personalizado para executar uma tarefa

Definindo uma intenção personalizada

Para fornecer um atalho que permita que um usuário conclua rapidamente uma tarefa específica relacionada ao seu aplicativo, crie uma intenção personalizada. Uma intenção personalizada representa uma tarefa que um usuário pode querer concluir, parâmetros relevantes para essa tarefa e possíveis respostas resultantes da execução da tarefa. Dependendo de como uma intenção personalizada é definida, invocá-la pode abrir seu aplicativo ou executar uma tarefa em segundo plano.

Use o Xcode 10 para criar intenções personalizadas. No repositório SoupChef, a intenção personalizada é definida em OrderSoupIntentCodeGen, um Objective-C projeto. Abra este projeto e selecione o arquivo Intents.intentdefinition no Project Navigator para exibir a intenção do OrderSoup .

Perceba os seguintes recursos:

  • A intenção tem uma Categoria de Ordem. Existem várias categorias predefinidas que podem ser usadas para intenções personalizadas; Selecione a que mais se aproxima da tarefa que sua intenção personalizada habilitará. Como esta solução é um aplicativo de pedidos de sopa, o OrderSoupIntent usa o Order.
  • A caixa de seleção Confirmação indica se a Siri deve ou não solicitar confirmação antes de executar a tarefa. Para a intenção de Pedir Sopa no Soup Chef, esta opção está ativada desde que o usuário esteja fazendo uma compra.
  • A seção Parâmetros do arquivo .intentdefinition define os parâmetros relevantes para um atalho. Para fazer um pedido de sopa, o Soup Chef deve saber o tipo de sopa, sua quantidade e quaisquer opções associadas. Cada parâmetro tem um tipo; parâmetro que não pode ser representado por um tipo predefinido são definidos como Personalizado.
  • A interface Tipos de atalho descreve as várias combinações de parâmetros que a Siri pode usar ao sugerir seu atalho. As seções Título e Legenda associadas permitem que você defina as mensagens que a Siri usará ao apresentar um atalho sugerido ao usuário.
  • A caixa de seleção Suporta execução em segundo plano deve ser marcada para qualquer atalho que possa ser executado sem abrir o aplicativo para interação adicional do usuário.

Definindo respostas de intenção personalizadas

O item Resposta aninhado abaixo da intenção OrderSoup representa as respostas potenciais resultantes de uma ordem de sopa.

Na definição de resposta da intenção do OrderSoup , observe os seguintes recursos:

  • As Propriedades de uma resposta podem ser usadas para personalizar a mensagem comunicada de volta ao usuário. A resposta de intenção do OrderSoup tem propriedades soup e waitTime.
  • Os Modelos de Resposta especificam as várias mensagens de êxito e falha que podem ser usadas para indicar o status após a conclusão da tarefa de uma intenção.
  • A caixa de seleção Êxito deve ser marcada para respostas que indicam sucesso.
  • A resposta de sucesso OrderSoupIntent usa as propriedades soup e waitTime para fornecer uma mensagem amigável e útil descrevendo quando o pedido de sopa estará pronto.

Gerando código para a intenção personalizada

A criação do projeto Xcode que contém essa definição de intenção personalizada faz com que o Xcode gere código que pode ser usado para interagir programaticamente com a intenção personalizada e suas respostas.

Para exibir esse código gerado:

  • Abra AppDelegate.m.
  • Adicione uma importação ao arquivo de cabeçalho da intenção personalizada: #import "OrderSoupIntent.h"
  • Em qualquer método da classe, adicione uma referência a OrderSoupIntent.
  • Clique com o botão direito do mouse e OrderSoupIntent escolha Saltar para definição.
  • Clique com o botão direito do mouse no arquivo recém-aberto, OrderSoupIntent.h, e selecione Mostrar no Finder.
  • Essa ação abrirá uma janela do Finder que contém um arquivo .h e .m contendo o código gerado.

Esse código gerado inclui:

  • OrderSoupIntent – Uma classe que representa a intenção personalizada.
  • OrderSoupIntentHandling – Um protocolo que define os métodos que serão usados para confirmar que a intenção deve ser executada e o método que realmente a executa.
  • OrderSoupIntentResponseCode – Um enum que define vários status de resposta.
  • OrderSoupIntentResponse – uma classe que representa a resposta à execução de uma intenção.

Criando uma associação à intenção personalizada

Para usar o código gerado pelo Xcode em um aplicativo Xamarin.iOS, crie uma associação C# para ele.

Criando uma biblioteca estática e definições de vinculação C#

No repositório SoupChef, dê uma olhada na pasta OrderSoupIntentStaticLib e abra o projeto OrderSoupIntentStaticLib.xcodeproj Xcode.

Este projeto Cocoa Touch Static Library contém os arquivos OrderSoupIntent.h e OrderSoupIntent.m gerados pelo Xcode.

Definindo as configurações de compilação do projeto de biblioteca estática

No Xcode Project Navigator, selecione o projeto de nível superior, OrderSoupIntentStaticLib, e navegue até Build Phases > Compile Sources. Observe que OrderSoupIntent.m (que importa OrderSoupIntent.h) está listado aqui. Em Link Binary With Libraries, observe que Intents.framework e Foundation.framework estão incluídos. Com essas configurações em vigor, a estrutura será compilada corretamente.

Criando a biblioteca estática e gerando definições de ligações C#

Para criar a biblioteca estática e gerar definições de ligações C# para ela, execute estas etapas:

  • Instale o Objective Sharpie, a ferramenta usada para gerar definições de ligações a partir dos arquivos .h e .m criados pelo Xcode.

  • Configure seu sistema para usar as ferramentas de linha de comando do Xcode 10:

    Aviso

    A atualização das Ferramentas de Linha de Comando selecionadas afeta todas as versões instaladas do Xcode em seu sistema. Quando terminar de usar o aplicativo de exemplo Soup Chef, certifique-se de reverter essa configuração para sua configuração original.

    • No Xcode, escolha Locais das Preferências > do Xcode e defina Ferramentas de Linha de Comando para a instalação mais atual do Xcode > 10 disponível no seu sistema.
  • No terminal, cd para o diretório OrderSoupIntentStaticLib .

  • Tipo make, que constrói:

    • A biblioteca estática, libOrderSoupIntentStaticLib.a
    • No diretório de saída bo, as definições de vinculação do C#:
      • ApiDefinitions.cs
      • StructsAndEnums.cs

O projeto OrderSoupIntentBindings, que depende dessa biblioteca estática e suas definições de ligações associadas, cria esses itens automaticamente. No entanto, a execução manual do processo acima garantirá que ele seja compilado conforme o esperado.

Para obter mais informações sobre como criar uma biblioteca estática e usar o Objective Sharpie para criar definições de vinculações em C#, consulte o passo a passo Vinculando uma biblioteca do iOSObjective-C.

Criando uma biblioteca de associações

Com a biblioteca estática e as definições de ligações C# criadas, a parte restante necessária para consumir o código relacionado à intenção gerado pelo Xcode em um projeto Xamarin.iOS é uma biblioteca de associações.

No repositório do Soup Chef, abra o arquivo SoupChef.sln Entre outras coisas, essa solução contém OrderSoupIntentBinding, uma biblioteca de ligações para a biblioteca estática gerada anteriormente.

Observe em particular que este projeto inclui:

  • ApiDefinitions.cs – Um arquivo gerado anteriormente pelo Objective Sharpie e adicionado a este projeto. A Ação de compilação deste arquivo é definida como ObjcBindingApiDefinition.

  • StructsAndEnums.cs – Outro arquivo gerado anteriormente pelo Objective Sharpie e adicionado a este projeto. A ação de compilação deste arquivo é definida como ObjcBindingCoreSource.

  • Uma referência nativa a libOrderSoupIntentStaticLib.a, a biblioteca estática criada anteriormente. Atualize as propriedades de referência nativas e especifique os seguintes valores:

    1. Estruturas = Foundation Intents
    2. Link inteligente = On
    3. Força de Carga = On
    4. Tipo = Static

Observação

Ambos ApiDefinitions.cs e StructsAndEnums.cs contêm atributos como [Watch (5,0), iOS (12,0)]. Esses atributos, gerados pelo Objective Sharpie, foram comentados, pois não são necessários para este projeto.

Para obter mais informações sobre como criar uma biblioteca de associações C#, consulte o passo a passo Vinculando uma biblioteca do iOSObjective-C.

Observe que o projeto SoupChef contém uma referência a OrderSoupIntentBinding, o que significa que agora ele pode acessar, em C#, as classes, interfaces e enums que ele contém:

  • OrderSoupIntent
  • OrderSoupIntentHandling
  • OrderSoupIntentResponse
  • OrderSoupIntenseResponseCode

Criando uma estrutura Swift

O código nativo de definição de intenção é gerado pelo Xcode por padrão usando a linguagem do seu projeto nativo. Se você definir o arquivo Intents.intentdefinition em um projeto Swift, o Xcode gerará um único arquivo Swift com todas as classes necessárias, que você pode usar para criar uma estrutura Swift.

Dica

Você pode selecionar um idioma desejado para o código de intenção gerado nas configurações de compilação do Xcode. Vá para Intent target > Build Settings > Intent Definition Compiler - Code Generation e selecione Swift ou Objective-C. Você também pode mantê-lo automático para corresponder ao seu idioma de destino.

O processo de criação de uma estrutura Swift é semelhante ao descrito anteriormente:

  1. Crie um novo projeto de estrutura Swift.
  2. Copie o arquivo Swift gerado automaticamente com código de intenção para este projeto, você pode encontrar esse arquivo conforme descrito aqui.
  3. Habilite o cabeçalho de ponte, para que a estrutura seja gerada automaticamente com o arquivo de cabeçalho sharpie Objective-C necessárioObjective-C.

Depois que a estrutura for criada, siga as mesmas etapas descritas anteriormente para criar uma associação Xamarin. Você pode ler mais sobre como criar uma vinculação para uma estrutura Swift aqui.

Adicionando o arquivo de definição de intenção à sua solução

Na solução C# SoupChef, o projeto SoupKit contém código compartilhado entre o aplicativo e suas extensões. O arquivo Intents.intentdefinition foi colocado no diretório Base.lproj do SoupKit e tem uma ação de compilação de conteúdo. O processo de compilação copia esse arquivo para o pacote de aplicativos Soup Chef, onde é necessário para que o aplicativo funcione corretamente.

Doar uma intenção

Para que a Siri sugira um atalho, ela deve primeiro entender quando o atalho é relevante.

Para dar à Siri esse entendimento, o Soup Chef doa uma intenção para a Siri cada vez que o usuário faz um pedido de sopa. Com base nessa doação – quando foi doada, onde foi doada, os parâmetros que contém – a Siri aprende quando sugerir o atalho no futuro.

O SoupChef usa a SoupOrderDataManager aula para fazer doações. Quando chamado para fazer um pedido de sopa para um usuário, o PlaceOrder método, por sua vez, chama DonateInteraction:

void DonateInteraction(Order order)
{
    var interaction = new INInteraction(order.Intent, null);
    interaction.Identifier = order.Identifier.ToString();
    interaction.DonateInteraction((error) =>
    {
        // ...
    });
}

Depois de buscar uma intenção, ela é embrulhada em um INInteractionarquivo . O INInteraction é dado um Identifier que corresponda ao ID exclusivo do pedido (será útil mais tarde ao excluir doações de intenção que não são mais válidas). Em seguida, a interação é doada para a Siri.

A chamada para o getter busca um OrderSoupIntent que representa a ordem definindo seu Quantity, , e imagem, SoupOptionse uma frase de invocação para usar como sugestão quando o order.Intent usuário grava uma frase para a Siri associar à intenção:

public OrderSoupIntent Intent
{
    get
    {
        var orderSoupIntent = new OrderSoupIntent();
        orderSoupIntent.Quantity = new NSNumber(Quantity);
        orderSoupIntent.Soup = new INObject(MenuItem.ItemNameKey, MenuItem.LocalizedString);

        var image = UIImage.FromBundle(MenuItem.IconImageName);
        if (!(image is null))
        {
            var data = image.AsPNG();
            orderSoupIntent.SetImage(INImage.FromData(data), "soup");
        }

        orderSoupIntent.Options = MenuItemOptions
            .ToArray<MenuItemOption>()
            .Select<MenuItemOption, INObject>(arg => new INObject(arg.Value, arg.LocalizedString))
            .ToArray<INObject>();

        var comment = "Suggested phrase for ordering a specific soup";
        var phrase = NSBundleHelper.SoupKitBundle.GetLocalizedString("ORDER_SOUP_SUGGESTED_PHRASE", comment);
        orderSoupIntent.SuggestedInvocationPhrase = String.Format(phrase, MenuItem.LocalizedString);

        return orderSoupIntent;
    }
}

Removendo doações inválidas

É importante remover as doações que não são mais válidas para que a Siri não faça sugestões de atalhos inúteis ou confusas.

No Soup Chef, a tela Configurar menu pode ser usada para marcar um item de menu como indisponível. A Siri não deve mais sugerir um atalho para ordenar o item de menu indisponível, portanto, o RemoveDonation método de excluir doações para itens de SoupMenuManager menu que não estão mais disponíveis. O aplicativo implementa essa funcionalidade ao:

  • Localizando pedidos associados ao item de menu agora indisponível.
  • Pegando seus identificadores.
  • Exclusão de interações que tenham os mesmos identificadores.
void RemoveDonation(MenuItem menuItem)
{
    if (!menuItem.IsAvailable)
    {
        Order[] orderHistory = OrderManager?.OrderHistory.ToArray<Order>();
        if (orderHistory is null)
        {
            return;
        }

        string[] orderIdentifiersToRemove = orderHistory
            .Where<Order>((order) => order.MenuItem.ItemNameKey == menuItem.ItemNameKey)
            .Select<Order, string>((order) => order.Identifier.ToString())
            .ToArray<string>();

        INInteraction.DeleteInteractions(orderIdentifiersToRemove, (error) =>
        {
            if (!(error is null))
            {
                Console.WriteLine($"Failed to delete interactions with error {error.ToString()}");
            }
            else
            {
                Console.WriteLine("Successfully deleted interactions");
            }
        });
    }
}

Validando doações bem-sucedidas

A solução inclui vários projetos e configuração específica. Em alguns casos, o aplicativo pode falhar devido à configuração incompleta, em outros casos, ele pode silenciosamente deixar de doar uma interação. É importante validar doações bem-sucedidas e as configurações do desenvolvedor do iOS ajudam nisso. Navegue até Configurações > do desenvolvedor e habilite as seguintes opções de desenvolvedor para ver doações e atalhos recentes:

  • Exibir atalhos recentes
  • Exibir doações na tela de bloqueio

Uma vez habilitada, toda doação bem-sucedida aparecerá na tela de bloqueio e abaixo das opções de sugestões da Siri. Se depois de executar seu aplicativo você não vir as doações lá, analise os seguintes casos de solução de problemas:

  1. Um aplicativo falha ao criar o com o OrderSoupIntent seguinte erro:

    Não foi possível criar uma instância nativa do tipo 'NativeLibrary.OrderSoupIntent': a classe nativa não foi carregada.

    Esse erro significa que o Xamarin não consegue carregar a classe nativa por meio da associação Xamarin. Para corrigir isso, verifique se a biblioteca nativa inclui o código necessário, referenciado pelo projeto de vinculação, e se os sinalizadores apropriados estão definidos, conforme descrito aqui, defina o Force Load sinalizador como On.

  2. Um aplicativo falha ao inicializar a instância nativa carregada da classe de intenção com o seguinte erro:

    Não foi possível inicializar uma instância do tipo 'NativeLibrary.OrderSoupIntent': o método nativo 'init' retornou zero.

    O problema está relacionado ao arquivo de definição de intenção ausente. O aplicativo Xamarin deve incluir o arquivo de definição de intenção original com o Content tipo, conforme descrito aqui.

  3. Um aplicativo cria a intenção e chama o método de doação sem uma falha, mas a saída do console mostra um aviso sobre o tipo de intenção desconhecido e nenhuma doação é feita:

    Não é possível doar interação com OrderSoupIntent que não tem tipos de atalho válidos

    Para corrigir o problema, a intenção deve ser definida corretamente no plist, o direito Siri deve ser habilitado e selecionado para a configuração de compilação atual por meio das configurações do projeto.

    O info.plist do aplicativo:

    <key>NSUserActivityTypes</key>
    <array>
        <string>ScheduleMeetingIntent</string>
    </array>
    

    O Entitlements.plist do aplicativo com o recurso Siri:

    <key>com.apple.developer.siri</key>
    <true/>
    

    Os direitos personalizados devem ser selecionados para a configuração de compilação de destino. Vá para Configurações do projeto, Criar > assinatura de pacote do > iOS e defina Direitos personalizados para o arquivo Entitlements.plist que contém os direitos necessários.

Criando uma extensão Intents

O código executado quando a Siri invoca uma intenção é colocado em uma extensão Intents, que pode ser adicionada como um novo projeto à mesma solução que um aplicativo Xamarin.iOS existente, como o Soup Chef. Na solução SoupChef, a extensão é chamada de SoupChefIntents.

SoupChefIntents – Info.plist e Entitlements.plist

SoupChefIntents – Info.plist

O Info.plist no projeto SoupChefIntents define o Identificador de Pacote como com.xamarin.SoupChef.SoupChefIntents.

O arquivo Info.plist também contém a seguinte entrada:

<key>NSExtension</key>
<dict>
    <key>NSExtensionAttributes</key>
    <dict>
        <key>IntentsRestrictedWhileLocked</key>
        <array/>
        <key>IntentsSupported</key>
        <array>
            <string>OrderSoupIntent</string>
        </array>
        <key>IntentsRestrictedWhileProtectedDataUnavailable</key>
        <array/>
    </dict>
    <key>NSExtensionPointIdentifier</key>
    <string>com.apple.intents-service</string>
    <key>NSExtensionPrincipalClass</key>
    <string>IntentHandler</string>
</dict>

No Info.plist acima:

  • IntentsRestrictedWhileLocked Lista as intenções a serem manipuladas quando o dispositivo é desbloqueado.
  • IntentsSupported lista as intenções manipuladas por essa extensão.
  • NSExtensionPointIdentifier Especifica o tipo de extensão de aplicativo. Para obter mais informações, consulte a documentação da Apple.
  • NSExtensionPrincipalClass Especifica a classe que deve ser usada para manipular intenções suportadas por essa extensão.
SoupChefIntents – Entitlements.plist

O Entitlements.plist no projeto SoupChefIntents tem o recurso Grupos de Aplicativos. Esse recurso é configurado para usar o mesmo grupo de aplicativos que o projeto SoupChef :

<key>com.apple.security.application-groups</key>
<array>
    <string>group.com.xamarin.SoupChef</string>
</array>

Soup Chef persiste dados com NSUserDefaults. Para compartilhar dados entre o aplicativo e a extensão do aplicativo, eles fazem referência ao mesmo grupo de aplicativos em seus arquivos Entitlements.plist .

Observação

A configuração de compilação do projeto SoupChefIntents define Direitos Personalizados para Entitlements.plist.

Manipulando uma tarefa em segundo plano OrderSoupIntent

Uma extensão Intents executa as tarefas em segundo plano necessárias para um atalho com base em uma intenção personalizada.

A Siri chama o GetHandlerIntentHandler método da classe (definido em Info.plist como o NSExtensionPrincipalClass) para obter uma instância de uma classe que se estende OrderSoupIntentHandling, que pode ser usada para manipular um OrderSoupIntent:

[Register("IntentHandler")]
public class IntentHandler : INExtension
{
    public override NSObject GetHandler(INIntent intent)
    {
        if (intent is OrderSoupIntent)
        {
            return new OrderSoupIntentHandler();
        }
        throw new Exception("Unhandled intent type: ${intent}");
    }

    protected IntentHandler(IntPtr handle) : base(handle) { }
}

OrderSoupIntentHandler, definido no projeto SoupKit de código compartilhado, implementa dois métodos importantes:

  • ConfirmOrderSoup – Confirma se a tarefa associada à intenção deve ou não ser realmente executada
  • HandleOrderSoup – Faz o pedido de sopa e responde ao usuário chamando o manipulador de conclusão passado

Manipulando um OrderSoupIntent que abre o aplicativo

Um aplicativo deve lidar corretamente com intenções que não são executadas em segundo plano. Essas intenções são tratadas da mesma forma que NSUserActivity os ContinueUserActivity atalhos, no método de AppDelegate:

public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
    var intent = userActivity.GetInteraction()?.Intent as OrderSoupIntent;
    if (!(intent is null))
    {
        HandleIntent(intent);
        return true;
    }
    // ...
}  

Fornecendo uma interface de usuário para uma intenção personalizada

Uma extensão de interface do usuário de Intenções fornece uma interface de usuário personalizada para uma extensão de Intenções. Na solução SoupChef, SoupChefIntentsUI é uma extensão de interface do usuário Intents que fornece uma interface para SoupChefIntents.

SoupChefIntentsUI – Info.plist e Entitlements.plist

SoupChefIntentsUI – Info.plist

O Info.plist no projeto SoupChefIntentsUI define o Identificador de Pacote como com.xamarin.SoupChef.SoupChefIntentsui.

O arquivo Info.plist também contém a seguinte entrada:

<key>NSExtension</key>
<dict>
    <key>NSExtensionAttributes</key>
    <dict>
        <key>IntentsSupported</key>
        <array>
            <string>OrderSoupIntent</string>
        </array>
        <!-- ... -->
    </dict>
    <key>NSExtensionPointIdentifier</key>
    <string>com.apple.intents-ui-service</string>
    <key>NSExtensionMainStoryboard</key>
    <string>MainInterface</string>
</dict>

No Info.plist acima:

  • IntentsSupported indica que o é manipulado por essa extensão de interface do OrderSoupIntent usuário Intents.
  • NSExtensionPointIdentifier Especifica o tipo de extensão de aplicativo. Para obter mais informações, consulte a documentação da Apple.
  • NSExtensionMainStoryboard Especifica o storyboard que define a interface principal dessa extensão

SoupChefIntentsUI – Entitlements.plist

O projeto SoupChefIntentsUI não precisa de um arquivo Entitlements.plist .

Criar a interface do usuário

Como o Info.plist para SoupChefIntentsUI define a chave como MainInterface, o arquivo MainInterace.storyboard define a interface para a NSExtensionMainStoryboard extensão da interface do usuário Intents.

Neste storyboard, há um único controlador de exibição, do tipo IntentViewController. Ele faz referência a dois pontos de vista:

  • invoiceView, do tipo InvoiceView
  • confirmationView, do tipo ConfirmOrderView

Observação

As interfaces para invoiceView e confirmationView são definidas em Main.storyboard como exibições secundárias. O Visual Studio para Mac e o Visual Studio 2017 não oferecem suporte para exibir ou editar modos de exibição secundários; para fazer isso, abra Main.storyboard no Construtor de Interfaces do Xcode.

IntentViewController implementa o IINUIHostedViewControlling interface, usada para fornecer uma interface personalizada ao trabalhar com o Siri Intents. O ConfigureViewmétodo é chamado para personalizar a interface, exibindo a confirmação ou a fatura, dependendo se a interação está sendo confirmada () ou foi executada com êxito (INIntentHandlingStatus.ReadyINIntentHandlingStatus.Success):

[Export("configureViewForParameters:ofInteraction:interactiveBehavior:context:completion:")]
public void ConfigureView(
    NSSet<INParameter> parameters,
    INInteraction interaction,
    INUIInteractiveBehavior interactiveBehavior,
    INUIHostedViewContext context,
    INUIHostedViewControllingConfigureViewHandler completion)
{
    // ...
    if (interaction.IntentHandlingStatus == INIntentHandlingStatus.Ready)
    {
        desiredSize = DisplayInvoice(order, intent);
    }
    else if(interaction.IntentHandlingStatus == INIntentHandlingStatus.Success)
    {
        var response = interaction.IntentResponse as OrderSoupIntentResponse;
        if (!(response is null))
        {
            desiredSize = DisplayOrderConfirmation(order, intent, response);
        }
    }
    completion(true, parameters, desiredSize);
}

Dica

Para obter mais informações sobre o método, assista à ConfigureView apresentação da WWDC 2017 da Apple, What's New in SiriKit.

Criando um atalho de voz

O Soup Chef fornece uma interface para atribuir um atalho de voz a cada pedido, tornando possível pedir sopa com Siri. Na verdade, a interface usada para gravar e atribuir atalhos de voz é fornecida pelo iOS e requer pouco código personalizado.

No OrderDetailViewController, quando um usuário toca na linha Adicionar à Siri da tabela, o RowSelected método apresenta uma tela para adicionar ou editar um atalho de voz:

public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
    // ...
    else if (TableConfiguration.Sections[indexPath.Section].Type == OrderDetailTableConfiguration.SectionType.VoiceShortcut)
    {
        INVoiceShortcut existingShortcut = VoiceShortcutDataManager?.VoiceShortcutForOrder(Order);
        if (!(existingShortcut is null))
        {
            var editVoiceShortcutViewController = new INUIEditVoiceShortcutViewController(existingShortcut);
            editVoiceShortcutViewController.Delegate = this;
            PresentViewController(editVoiceShortcutViewController, true, null);
        }
        else
        {
            // Since the app isn't yet managing a voice shortcut for
            // this order, present the add view controller
            INShortcut newShortcut = new INShortcut(Order.Intent);
            if (!(newShortcut is null))
            {
                var addVoiceShortcutVC = new INUIAddVoiceShortcutViewController(newShortcut);
                addVoiceShortcutVC.Delegate = this;
                PresentViewController(addVoiceShortcutVC, true, null);
            }
        }
    }
}

Com base na existência ou não de um atalho de voz existente para a ordem exibida no momento, RowSelected apresenta um controlador de exibição do tipo INUIEditVoiceShortcutViewController ou INUIAddVoiceShortcutViewController. Em cada caso, OrderDetailViewController configura-se como o controlador de visualização , Delegatee é por isso que ele também implementa IINUIAddVoiceShortcutViewControllerDelegate e IINUIEditVoiceShortcutViewControllerDelegate.

Teste no dispositivo

Para executar o Soup Chef em um dispositivo, siga as instruções nesta seção. Leia também a nota sobre o provisionamento automático.

Grupo de aplicativos, IDs de aplicativos, perfis de provisionamento

Na seção Certificados, IDs e Perfis do Apple Developer Portal, execute as seguintes etapas:

  • Crie um grupo de aplicativos para compartilhar dados entre o aplicativo Soup Chef e suas extensões. Por exemplo: group.com.yourcompanyname.SoupChef

  • Crie três IDs de aplicativo: uma para o próprio aplicativo, uma para a extensão Intents e uma para a extensão Intents UI. Por exemplo:

    • Aplicativo: com.yourcompanyname.SoupChef

      • A essa ID de aplicativo, atribua os recursos SiriKit e Grupos de aplicativos .
    • Extensão de intenções: com.yourcompanyname.SoupChef.Intents

      • A esta ID de Aplicativo, atribua o recurso Grupos de Aplicativos .
    • Extensão da interface do usuário de intenções: com.yourcompanyname.SoupChef.Intentsui

      • Este ID de aplicativo não precisa de recursos especiais.
  • Depois de criar as IDs de aplicativo acima, edite o recurso Grupos de aplicativos atribuído ao aplicativo e a extensão Intenções, especificando o grupo de aplicativos específico criado anteriormente.

  • Crie três novos perfis de provisionamento de desenvolvimento, um para cada uma das novas IDs de aplicativo.

  • Baixe esses perfis de provisionamento e clique duas vezes em cada um deles para instalá-lo. Se o Visual Studio para Mac ou o Visual Studio 2017 já estiver em execução, reinicie-o para garantir que ele registre os novos perfis de provisionamento.

Editando Info.plist, Entitlements.plist e código-fonte

No Visual Studio para Mac ou Visual Studio 2017, execute as seguintes etapas:

  • Atualize os vários arquivos Info.plist na solução. Defina o aplicativo, a extensão Intents e o Identificador de pacote da extensão da interface do usuário de intenções para as IDs de aplicativo definidas anteriormente:

    • Aplicativo: com.yourcompanyname.SoupChef
    • Extensão de intenções: com.yourcompanyname.SoupChef.Intents
    • Extensão da interface do usuário de intenções: com.yourcompanyname.SoupChef.Intentsui
  • Atualize o arquivo Entitlements.plist para o projeto SoupChef:

    • Para o recurso Grupos de Aplicativos, defina o grupo para o novo grupo de aplicativos criado anteriormente (no exemplo acima, era group.com.yourcompanyname.SoupChef).
    • Certifique-se de que o SiriKit está ativado.
  • Atualize o arquivo Entitlements.plist para o projeto SoupChefIntents:

    • Para o recurso Grupos de Aplicativos, defina o grupo para o novo grupo de aplicativos criado anteriormente (no exemplo acima, era group.com.yourcompanyname.SoupChef).
  • Finalmente, abra NSUserDefaultsHelper.cs. Defina a variável como o valor do seu novo grupo de aplicativos (por exemplo, defina-a AppGroup como group.com.yourcompanyname.SoupChef).

Definindo as configurações de compilação

No Visual Studio para Mac ou Visual Studio 2017:

  • Abra as opções/propriedades do projeto SoupChef . Na guia Assinatura de Pacote do iOS, defina Identidade de Assinatura como automática e Perfil de Provisionamento como o novo perfil de provisionamento específico do aplicativo criado anteriormente.

  • Abra as opções/propriedades do projeto SoupChefIntents . Na guia Assinatura de Pacote do iOS, defina Identidade de Assinatura como automática e Perfil de Provisionamento como o novo perfil de provisionamento específico da extensão de Intenções criado anteriormente.

  • Abra as opções/propriedades do projeto SoupChefIntentsUI . Na guia Assinatura de Pacote do iOS, defina Identidade de Assinatura como automática e Perfil de Provisionamento como o novo perfil de provisionamento específico da extensão da interface do usuário de Intenções que você criou anteriormente.

Com essas mudanças, o aplicativo será executado em um dispositivo iOS.

Provisionamento automático

Você pode usar o provisionamento automático para realizar muitas dessas tarefas de provisionamento diretamente no IDE. No entanto, o provisionamento automático não configura grupos de aplicativos. Você precisará configurar manualmente os arquivos Entitlements.plist com o nome do grupo de aplicativos que deseja usar, visitar o Apple Developer Portal para criar o grupo de aplicativos, atribuir esse grupo de aplicativos a cada ID de aplicativo criado pelo provisionamento automático, gerar novamente os perfis de provisionamento (aplicativo, extensão Intents, extensão Intents UI) para incluir o grupo de aplicativos recém-criado, e baixá-los e instalá-los.