¿Se adaptarán las intenciones de SiriKit a su aplicación? Si es así, aquí está cómo usarlos

Publicado: 2022-03-10
Resumen rápido ↬ Desde el año pasado, es posible agregar compatibilidad con Siri a una aplicación si encaja en uno de los casos de uso predefinidos de Apple. Averigüe si SiriKit funcionará para usted y cómo usarlo.

Desde iOS 5, Siri ha ayudado a los usuarios de iPhone a enviar mensajes, establecer recordatorios y buscar restaurantes con las aplicaciones de Apple. A partir de iOS 10, también hemos podido usar Siri en algunas de nuestras propias aplicaciones.

Para usar esta funcionalidad, su aplicación debe encajar dentro de los "dominios e intenciones" predefinidos de Siri de Apple. En este artículo, aprenderemos cuáles son y veremos si nuestras aplicaciones pueden usarlos. Tomaremos una aplicación simple que es un administrador de listas de tareas y aprenderemos cómo agregar soporte para Siri. También repasaremos las pautas del sitio web para desarrolladores de Apple sobre la configuración y el código Swift para un nuevo tipo de extensión que se introdujo con SiriKit: la extensión Intents .

Cuando llegue a la parte de codificación de este artículo, necesitará Xcode (al menos la versión 9.x), y sería bueno si está familiarizado con el desarrollo de iOS en Swift porque vamos a agregar Siri a un pequeño grupo de trabajo. aplicación Seguiremos los pasos para configurar una extensión en el sitio web para desarrolladores de Apple y para agregar el código de extensión de Siri a la aplicación.

“Oye Siri, ¿por qué te necesito?”

A veces uso mi teléfono mientras estoy en mi sofá, con ambas manos libres, y puedo prestarle toda mi atención a la pantalla. Tal vez le envíe un mensaje de texto a mi hermana para planificar el cumpleaños de nuestra madre o responda una pregunta en Trello. Puedo ver la aplicación. Puedo tocar la pantalla. Puedo teclear.

Pero podría estar caminando por mi ciudad, escuchando un podcast, cuando aparece un mensaje de texto en mi reloj. Mi teléfono está en mi bolsillo y no puedo responder fácilmente mientras camino.

¡Más después del salto! Continúe leyendo a continuación ↓

Con Siri, puedo mantener presionado el botón de control de mis auriculares y decir: "Envíale un mensaje de texto a mi hermana para decirle que estaré allí a las dos en punto". Siri es genial cuando estás en movimiento y no puedes prestar toda tu atención a tu teléfono o cuando la interacción es menor, pero requiere varios toques y mucho tipeo.

Esto está bien si quiero usar aplicaciones de Apple para estas interacciones. Pero algunas categorías de aplicaciones, como la mensajería, tienen alternativas muy populares. Otras actividades, como reservar un viaje o reservar una mesa en un restaurante, ni siquiera son posibles con las aplicaciones integradas de Apple, pero son perfectas para Siri.

El enfoque de Apple para los asistentes de voz

Para habilitar Siri en aplicaciones de terceros, Apple tuvo que decidir un mecanismo para tomar el sonido de la voz del usuario y de alguna manera llevarlo a la aplicación de manera que pudiera cumplir con la solicitud. Para que esto sea posible, Apple requiere que el usuario mencione el nombre de la aplicación en la solicitud, pero tenían varias opciones sobre qué hacer con el resto de la solicitud.

  • Podría haber enviado un archivo de sonido a la aplicación.
    El beneficio de este enfoque es que la aplicación podría tratar de manejar literalmente cualquier solicitud que el usuario pueda tener. A Amazon o Google les podría haber gustado este enfoque porque ya cuentan con sofisticados servicios de reconocimiento de voz. Pero la mayoría de las aplicaciones no podrían manejar esto fácilmente.
  • Podría haber convertido el discurso en texto y enviarlo.
    Debido a que muchas aplicaciones no tienen implementaciones sofisticadas de lenguaje natural, el usuario generalmente tendría que ceñirse a frases muy particulares, y el desarrollador de la aplicación tendría que implementar el soporte que no está en inglés.
  • Podría haberle pedido que proporcione una lista de frases que comprenda.
    Este mecanismo está más cerca de lo que Amazon hace con Alexa (en su marco de "habilidades") y permite muchos más usos de Alexa de los que SiriKit puede manejar actualmente. En una habilidad de Alexa, proporciona frases con variables de marcador de posición que Alexa completará por usted. Por ejemplo, “Alexa, recuérdame a las $TIME$ a $REMINDER$ ”: Alexa comparará esta frase con lo que el usuario ha dicho y te dirá los valores de TIME y REMINDER . Al igual que con el mecanismo anterior, el desarrollador debe hacer toda la traducción y no hay mucha flexibilidad si el usuario dice algo ligeramente diferente.
  • Podría definir una lista de solicitudes con parámetros y enviar a la aplicación una solicitud estructurada.
    Esto es realmente lo que hace Apple, y el beneficio es que puede admitir una variedad de idiomas y hace todo el trabajo para tratar de comprender todas las formas en que un usuario puede formular una solicitud. El gran inconveniente es que solo puede implementar controladores para las solicitudes que define Apple. Esto es excelente si tiene, por ejemplo, una aplicación de mensajería, pero si tiene un servicio de transmisión de música o un reproductor de podcasts, no tiene forma de usar SiriKit en este momento.

Del mismo modo, hay tres formas en que las aplicaciones responden al usuario: con sonido, con texto que se convierte o expresando el tipo de cosa que quieres decir y dejando que el sistema descubra la forma exacta de expresarlo. La última solución (que es lo que hace Apple) pone la carga de la traducción en Apple, pero le brinda formas limitadas de usar sus propias palabras para describir las cosas.

Los tipos de solicitudes que puede manejar se definen en los dominios e intenciones de SiriKit. Una intención es un tipo de solicitud que un usuario puede hacer, como enviar un mensaje de texto a un contacto o encontrar una foto. Cada intención tiene una lista de parámetros; por ejemplo, los mensajes de texto requieren un contacto y un mensaje.

Un dominio es solo un grupo de intenciones relacionadas. Leer un texto y enviar un texto están en el dominio de mensajería. Reservar un viaje y obtener una ubicación están en el dominio de reserva de viajes. Hay dominios para hacer llamadas VoIP, iniciar entrenamientos, buscar fotos y algunas cosas más. La documentación de SiriKit contiene una lista completa de dominios y sus intenciones.

Una crítica común a Siri es que parece incapaz de manejar las solicitudes tan bien como Google y Alexa, y que el ecosistema de voz de terceros habilitado por los competidores de Apple es más rico.

Estoy de acuerdo con esas críticas. Si su aplicación no se ajusta a las intenciones actuales, entonces no puede usar SiriKit y no hay nada que pueda hacer. Incluso si su aplicación se ajusta, no puede controlar todas las palabras que dice o entiende Siri; por lo tanto, si tiene una forma particular de hablar sobre las cosas en su aplicación, no siempre puede enseñársela a Siri.

La esperanza de los desarrolladores de iOS es que Apple amplíe en gran medida su lista de intenciones y que su procesamiento de lenguaje natural sea mucho mejor. Si lo hace, entonces tendremos un asistente de voz que funciona sin que los desarrolladores tengan que traducir o entender todas las formas de decir lo mismo. E implementar soporte para solicitudes estructuradas es bastante simple de hacer, mucho más fácil que construir un analizador de lenguaje natural.

Otro gran beneficio del marco de intenciones es que no se limita a Siri y las solicitudes de voz. Incluso ahora, la aplicación Maps puede generar una solicitud basada en intenciones de su aplicación (por ejemplo, una reserva de restaurante). Lo hace mediante programación (no desde la voz o el lenguaje natural). Si Apple permitiera que las aplicaciones descubrieran las intenciones expuestas de las demás, tendríamos una forma mucho mejor de que las aplicaciones trabajaran juntas (a diferencia de las URL de estilo x-callback).

Finalmente, debido a que una intención es una solicitud estructurada con parámetros, existe una forma simple para que una aplicación exprese que faltan parámetros o que necesita ayuda para distinguir entre algunas opciones. Luego, Siri puede hacer preguntas de seguimiento para resolver los parámetros sin que la aplicación necesite llevar a cabo la conversación.

El dominio de reserva de viajes

Para comprender los dominios y las intenciones, veamos el dominio de reserva de viajes. Este es el dominio que usarías para pedirle a Siri que te consiga un auto Lyft.

Apple define cómo solicitar un viaje y cómo obtener información al respecto, pero en realidad no hay una aplicación de Apple integrada que pueda manejar esta solicitud. Este es uno de los pocos dominios donde se requiere una aplicación habilitada para SiriKit.

Puede invocar uno de los intentos a través de la voz o directamente desde Maps. Algunas de las intenciones para este dominio son:

  • Solicitar un viaje
    Use este para reservar un viaje. Deberá proporcionar un lugar de recogida y entrega, y es posible que la aplicación también necesite saber el tamaño de su grupo y qué tipo de viaje desea. Una frase de muestra podría ser: "Resérvame un viaje con <appname>".
  • Obtener el estado del viaje
    Utilice esta intención para averiguar si se recibió su solicitud y para obtener información sobre el vehículo y el conductor, incluida su ubicación. La aplicación Mapas utiliza esta intención para mostrar una imagen actualizada del automóvil a medida que se acerca a usted.
  • Cancelar un viaje
    Use esto para cancelar un viaje que ha reservado.

Para cualquiera de estos intentos, Siri podría necesitar más información. Como verá cuando implementemos un controlador de intenciones, su extensión de Intents puede decirle a Siri que falta un parámetro obligatorio, y Siri se lo solicitará al usuario.

El hecho de que Maps pueda invocar las intenciones mediante programación muestra cómo las intenciones podrían permitir la comunicación entre aplicaciones en el futuro.

Nota : puede obtener una lista completa de dominios y sus intenciones en el sitio web para desarrolladores de Apple. También hay una aplicación de Apple de muestra con muchos dominios e intenciones implementados, incluida la reserva de viajes.

Agregar soporte de dominio de listas y notas a su aplicación

Bien, ahora que entendemos los conceptos básicos de SiriKit, veamos cómo agregaría soporte para Siri en una aplicación que implica mucha configuración y una clase para cada intención que desea manejar.

El resto de este artículo consta de los pasos detallados para agregar compatibilidad con Siri a una aplicación. Hay cinco cosas de alto nivel que debe hacer:

  1. Prepárese para agregar una nueva extensión a la aplicación creando perfiles de aprovisionamiento con nuevos derechos en el sitio web para desarrolladores de Apple.
  2. Configure su aplicación (a través de su plist ) para usar los derechos.
  3. Use la plantilla de Xcode para comenzar con un código de muestra.
  4. Agregue el código para admitir su intención de Siri.
  5. Configura el vocabulario de Siri a través de plist s.

No se preocupe: revisaremos cada uno de estos, explicando las extensiones y los derechos a lo largo del camino.

Para centrarme solo en las partes de Siri, he preparado un sencillo administrador de listas de tareas pendientes, List-o-Mat.

Un GIF animado que muestra una demostración de List-o-Mat
Hacer listas en List-o-Mat (Vista previa grande)

Puede encontrar la fuente completa de la muestra, List-o-Mat, en GitHub.

Para crearlo, todo lo que hice fue comenzar con la plantilla de la aplicación Xcode Master-Detail y convertir ambas pantallas en UITableView . Agregué una forma de agregar y eliminar listas y elementos, y una forma de marcar elementos como hechos. Toda la navegación es generada por la plantilla.

Para almacenar los datos, utilicé el protocolo Codable (presentado en WWDC 2017), que convierte las estructuras en JSON y las guarda en un archivo de texto en la carpeta de documents .

Deliberadamente he mantenido el código muy simple. Si tiene alguna experiencia con Swift y con la creación de controladores de vista, entonces no debería tener ningún problema.

Ahora podemos seguir los pasos para agregar compatibilidad con SiriKit. Los pasos de alto nivel serían los mismos para cualquier aplicación y cualquiera que sea el dominio y las intenciones que planee implementar. En su mayoría, nos ocuparemos del sitio web para desarrolladores de Apple, plist y escribiremos un poco de Swift.

Para List-o-Mat, nos centraremos en el dominio de listas y notas, que se aplica ampliamente a cosas como aplicaciones para tomar notas y listas de tareas pendientes.

En el dominio de listas y notas, tenemos las siguientes intenciones que tendrían sentido para nuestra aplicación.

  • Obtener una lista de tareas.
  • Agregar una nueva tarea a una lista.

Debido a que las interacciones con Siri en realidad ocurren fuera de su aplicación (tal vez incluso cuando su aplicación no se está ejecutando), iOS usa una extensión para implementar esto.

La extensión de intenciones

Si no has trabajado con extensiones, necesitarás saber tres cosas principales:

  1. Una extensión es un proceso separado. Se entrega dentro del paquete de su aplicación, pero se ejecuta completamente solo, con su propia zona de pruebas.
  2. Su aplicación y extensión pueden comunicarse entre sí al estar en el mismo grupo de aplicaciones. La forma más fácil es a través de las carpetas de espacio aislado compartidas del grupo (para que puedan leer y escribir en los mismos archivos si los coloca allí).
  3. Las extensiones requieren sus propios ID, perfiles y derechos de aplicación.

Para agregar una extensión a su aplicación, inicie sesión en su cuenta de desarrollador y vaya a la sección "Certificados, identificadores y perfiles".

Actualización de los datos de su cuenta de la aplicación de desarrollador de Apple

En nuestra cuenta de desarrollador de Apple, lo primero que debemos hacer es crear un grupo de aplicaciones. Vaya a la sección "Grupos de aplicaciones" en "Identificadores" y agregue uno.

Captura de pantalla del cuadro de diálogo del sitio web para desarrolladores de Apple para registrar un grupo de aplicaciones
Registro de un grupo de aplicaciones (vista previa grande)

Debe comenzar con group , seguido de su identificador habitual basado en dominio inverso. Debido a que tiene un prefijo, puede usar el identificador de su aplicación para el resto.

Luego, necesitamos actualizar la ID de nuestra aplicación para usar este grupo y habilitar Siri:

  1. Vaya a la sección "ID de la aplicación" y haga clic en la ID de su aplicación;
  2. Haga clic en el botón "Editar";
  3. Habilite los grupos de aplicaciones (si no están habilitados para otra extensión).
    Una captura de pantalla del sitio web para desarrolladores de Apple que habilita grupos de aplicaciones para una ID de aplicación
    Habilitar grupos de aplicaciones (vista previa grande)
  4. Luego configure el grupo de aplicaciones haciendo clic en el botón "Editar". Elija el grupo de aplicaciones de antes.
    Una captura de pantalla del cuadro de diálogo del sitio web para desarrolladores de Apple para establecer el nombre del grupo de aplicaciones
    Establecer el nombre del grupo de aplicaciones (vista previa grande)
  5. Activa SiriKit.
    Una captura de pantalla de SiriKit habilitado
    Habilitar SiriKit (vista previa grande)
  6. Haga clic en "Listo" para guardarlo.

Ahora, necesitamos crear una nueva ID de aplicación para nuestra extensión:

  1. En la misma sección "ID de la aplicación", agregue una nueva ID de la aplicación. Este será el identificador de su aplicación, con un sufijo. No use solo Intents como sufijo porque este nombre se convertirá en el nombre de su módulo en Swift y luego entraría en conflicto con los Intents reales.
    Una captura de pantalla de la pantalla del desarrollador de Apple para crear una ID de aplicación
    Cree una ID de aplicación para la extensión Intents (vista previa grande)
  2. Habilite esta ID de aplicación también para grupos de aplicaciones (y configure el grupo como lo hicimos antes).

Ahora, cree un perfil de aprovisionamiento de desarrollo para la extensión Intents y vuelva a generar el perfil de aprovisionamiento de su aplicación. Descárguelos e instálelos como lo haría normalmente.

Ahora que nuestros perfiles están instalados, debemos ir a Xcode y actualizar los derechos de la aplicación.

Actualización de los derechos de su aplicación en Xcode

De vuelta en Xcode, elige el nombre de tu proyecto en el navegador de proyectos. Luego, elija el objetivo principal de su aplicación y vaya a la pestaña "Capacidades". Allí, verá un interruptor para activar el soporte de Siri.

Una captura de pantalla de la pantalla de derechos de Xcode que muestra que SiriKit está habilitado
Habilite SiriKit en los derechos de su aplicación. (Vista previa grande)

Más abajo en la lista, puede activar grupos de aplicaciones y configurarlo.

Una captura de pantalla de la pantalla de derechos de Xcode que muestra que el grupo de aplicaciones está habilitado y configurado
Configurar el grupo de aplicaciones de la aplicación (vista previa grande)

Si lo configuró correctamente, verá esto en el archivo .entitlements de su aplicación:

Una captura de pantalla del plist de la aplicación que muestra que los derechos están establecidos
El plist muestra los derechos que establece (vista previa grande)

Ahora, finalmente estamos listos para agregar el objetivo de la extensión Intents a nuestro proyecto.

Agregar la extensión de intenciones

Finalmente estamos listos para agregar la extensión. En Xcode, elija "Archivo" → "Nuevo objetivo". Aparecerá esta hoja:

Una captura de pantalla que muestra la extensión Intents en el cuadro de diálogo New Target en Xcode
Agregue la extensión Intents a su proyecto (vista previa grande)

Elija "Extensión de intenciones" y haga clic en el botón "Siguiente". Rellene la siguiente pantalla:

Captura de pantalla de Xcode que muestra cómo configurar la extensión Intents
Configurar la extensión Intents (vista previa grande)

El nombre del producto debe coincidir con el sufijo que haya creado en el ID de la aplicación de intents en el sitio web para desarrolladores de Apple.

Elegimos no agregar una extensión de interfaz de usuario de intentos. Esto no se trata en este artículo, pero puede agregarlo más adelante si lo necesita. Básicamente, es una forma de poner su propia marca y estilo de visualización en los resultados visuales de Siri.

Cuando haya terminado, Xcode creará una clase de controlador de intenciones que podemos usar como parte inicial para nuestra implementación de Siri.

El controlador de intenciones: resolver, confirmar y manejar

Xcode generó un nuevo objetivo que tiene un punto de partida para nosotros.

Lo primero que debe hacer es configurar este nuevo objetivo para que esté en el mismo grupo de aplicaciones que la aplicación. Como antes, vaya a la pestaña "Capacidades" del objetivo, active los grupos de aplicaciones y configúrelo con el nombre de su grupo. Recuerde, las aplicaciones del mismo grupo tienen un espacio aislado que pueden usar para compartir archivos entre sí. Necesitamos esto para que las solicitudes de Siri lleguen a nuestra aplicación.

List-o-Mat tiene una función que devuelve la carpeta de documentos del grupo. Deberíamos usarlo siempre que queramos leer o escribir en un archivo compartido.

 func documentsFolder() -> URL? { return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.app-o-mat.ListOMat") }

Por ejemplo, cuando guardamos las listas, usamos esto:

 func save(lists: Lists) { guard let docsDir = documentsFolder() else { fatalError("no docs dir") } let url = docsDir.appendingPathComponent(fileName, isDirectory: false) // Encode lists as JSON and save to url }

La plantilla de la extensión Intents creó un archivo llamado IntentHandler.swift , con una clase llamada IntentHandler . También lo configuró para que fuera el punto de entrada de las intenciones en el plist de la extensión.

Una captura de pantalla de Xcode que muestra cómo se configura IntentHandler como punto de entrada
El plist de extensión de intención configura IntentHandler como el punto de entrada

En este mismo plist , verás una sección para declarar las intenciones que admitimos. Vamos a empezar con el que permite buscar listas, que se llama INSearchForNotebookItemsIntent . Agréguelo a la matriz en IntentsSupported .

Una captura de pantalla en Xcode que muestra que la extensión plist debe enumerar las intenciones que maneja
Agregue el nombre de la intención a la lista de intenciones (vista previa grande)

Ahora, ve a IntentHandler.swift y reemplaza su contenido con este código:

 import Intents class IntentHandler: INExtension { override func handler(for intent: INIntent) -> Any? { switch intent { case is INSearchForNotebookItemsIntent: return SearchItemsIntentHandler() default: return nil } } }

Se llama a la función del handler para que un objeto maneje una intención específica. Puede simplemente implementar todos los protocolos en esta clase y regresar self , pero colocaremos cada intent en su propia clase para mantenerlo mejor organizado.

Debido a que tenemos la intención de tener algunas clases diferentes, démosles una clase base común para el código que necesitamos compartir entre ellos:

 class ListOMatIntentsHandler: NSObject { }

El marco de intenciones requiere que heredemos de NSObject . Más adelante completaremos algunos métodos.

Comenzamos nuestra implementación de búsqueda con esto:

 class SearchItemsIntentHandler: ListOMatIntentsHandler, INSearchForNotebookItemsIntentHandling { }

Para configurar un controlador de intenciones, debemos implementar tres pasos básicos

  1. Resuelva los parámetros.
    Asegúrese de que se proporcionen los parámetros requeridos y elimine la ambigüedad de cualquiera que no entienda por completo.
  2. Confirme que la solicitud es factible.
    A menudo, esto es opcional, pero incluso si sabe que cada parámetro es bueno, es posible que aún necesite acceso a un recurso externo o tenga otros requisitos.
  3. Manejar la solicitud.
    Haz lo que se te pide.

INSearchForNotebookItemsIntent , la primera intención que implementaremos, se puede usar como una búsqueda de tareas. Los tipos de solicitudes que podemos manejar con esto son, "En List-o-Mat, mostrar la lista de la tienda de comestibles" o "En List-o-Mat, mostrar la lista de la tienda".

Aparte: "List-o-Mat" es en realidad un mal nombre para una aplicación SiriKit porque Siri tiene dificultades con los guiones en las aplicaciones. Afortunadamente, SiriKit nos permite tener nombres alternativos y proporcionar pronunciación. En el Info.plist de la aplicación, agregue esta sección:

Una captura de pantalla de Xcode que muestra que la lista de aplicaciones puede agregar pronunciaciones y nombres de aplicaciones alternativos
Agregue guías de pronunciación y nombres de aplicaciones alternativos a la lista de aplicaciones

Esto permite que el usuario diga “list oh mat” y que se entienda como una sola palabra (sin guiones). No se ve ideal en la pantalla, pero sin ella, Siri a veces piensa que "Lista" y "Mat" son palabras separadas y se confunde mucho.

Resolver: Averiguar los parámetros

Para una búsqueda de elementos del cuaderno, hay varios parámetros:

  1. el tipo de elemento (una tarea, una lista de tareas o una nota),
  2. el título del artículo,
  3. el contenido del artículo,
  4. el estado de finalización (si la tarea está marcada como completada o no),
  5. la ubicación con la que está asociado,
  6. la fecha a la que está asociado.

Solo requerimos los dos primeros, por lo que necesitaremos escribir funciones de resolución para ellos. INSearchForNotebookItemsIntent tiene métodos para que los implementemos.

Debido a que solo nos preocupamos por mostrar listas de tareas, codificaremos eso en la resolución para el tipo de elemento. En SearchItemsIntentHandler , agrega esto:

 func resolveItemType(for intent: INSearchForNotebookItemsIntent, with completion: @escaping (INNotebookItemTypeResolutionResult) -> Void) { completion(.success(with: .taskList)) }

Entonces, no importa lo que diga el usuario, buscaremos listas de tareas. Si quisiéramos expandir nuestro soporte de búsqueda, dejaríamos que Siri intente resolver esto a partir de la frase original y luego simplemente use la completion(.needsValue()) si falta el tipo de elemento. Alternativamente, podríamos intentar adivinar a partir del título viendo qué coincide con él. En este caso, completaríamos con éxito cuando Siri sepa lo que es, y completion(.notRequired()) cuando vayamos a probar múltiples posibilidades.

La resolución del título es un poco más complicada. Lo que queremos es que Siri use una lista si encuentra una que coincida exactamente con lo que dijiste. Si no está seguro o si hay más de una posibilidad, queremos que Siri nos pida ayuda para resolverlo. Para hacer esto, SiriKit proporciona un conjunto de enumeraciones de resolución que nos permiten expresar lo que queremos que suceda a continuación.

Entonces, si dices "Tienda de comestibles", entonces Siri tendría una coincidencia exacta. Pero si dices "Tienda", entonces Siri presentará un menú de listas coincidentes.

Comenzaremos con esta función para dar la estructura básica:

 func resolveTitle(for intent: INSearchForNotebookItemsIntent, with completion: @escaping (INSpeakableStringResolutionResult) -> Void) { guard let title = intent.title else { completion(.needsValue()) return } let possibleLists = getPossibleLists(for: title) completeResolveListName(with: possibleLists, for: title, with: completion) }

Implementaremos getPossibleLists(for:) y completeResolveListName(with:for:with:) en la clase base ListOMatIntentsHandler .

getPossibleLists(for:) necesita intentar hacer una coincidencia aproximada del título que Siri nos pasa con los nombres reales de la lista.

 public func getPossibleLists(for listName: INSpeakableString) -> [INSpeakableString] { var possibleLists = [INSpeakableString]() for l in loadLists() { if l.name.lowercased() == listName.spokenPhrase.lowercased() { return [INSpeakableString(spokenPhrase: l.name)] } if l.name.lowercased().contains(listName.spokenPhrase.lowercased()) || listName.spokenPhrase.lowercased() == "all" { possibleLists.append(INSpeakableString(spokenPhrase: l.name)) } } return possibleLists }

Recorremos todas nuestras listas. Si obtenemos una coincidencia exacta, la devolveremos y, si no, devolveremos una serie de posibilidades. En esta función, simplemente verificamos si la palabra que dijo el usuario está contenida en un nombre de lista (por lo tanto, una coincidencia bastante simple). Esto permite que "Comestibles" coincida con "Tienda de comestibles". Un algoritmo más avanzado podría intentar hacer coincidir las palabras que suenan igual (por ejemplo, con el algoritmo Soundex),

completeResolveListName(with:for:with:) es responsable de decidir qué hacer con esta lista de posibilidades.

 public func completeResolveListName(with possibleLists: [INSpeakableString], for listName: INSpeakableString, with completion: @escaping (INSpeakableStringResolutionResult) -> Void) { switch possibleLists.count { case 0: completion(.unsupported()) case 1: if possibleLists[0].spokenPhrase.lowercased() == listName.spokenPhrase.lowercased() { completion(.success(with: possibleLists[0])) } else { completion(.confirmationRequired(with: possibleLists[0])) } default: completion(.disambiguation(with: possibleLists)) } }

Si obtenemos una coincidencia exacta, le decimos a Siri que lo logramos. Si obtenemos una coincidencia inexacta, le decimos a Siri que le pregunte al usuario si acertó.

Si tenemos varias coincidencias, usamos completion(.disambiguation(with: possibleLists)) para decirle a Siri que muestre una lista y dejar que el usuario elija una.

Ahora que sabemos cuál es la solicitud, debemos analizar todo el asunto y asegurarnos de que podemos manejarlo.

Confirmar: verifique todas sus dependencias

En este caso, si hemos resuelto todos los parámetros, siempre podemos manejar la solicitud. Las implementaciones típicas de confirm() pueden verificar la disponibilidad de servicios externos o verificar los niveles de autorización.

Debido a que confirm() es opcional, no podríamos hacer nada y Siri asumiría que podemos manejar cualquier solicitud con parámetros resueltos. Para ser explícitos, podríamos usar esto:

 func confirm(intent: INSearchForNotebookItemsIntent, completion: @escaping (INSearchForNotebookItemsIntentResponse) -> Void) { completion(INSearchForNotebookItemsIntentResponse(code: .success, userActivity: nil)) }

Esto significa que podemos manejar cualquier cosa.

Manejar: Hazlo

El paso final es manejar la solicitud.

 func handle(intent: INSearchForNotebookItemsIntent, completion: @escaping (INSearchForNotebookItemsIntentResponse) -> Void) { guard let title = intent.title, let list = loadLists().filter({ $0.name.lowercased() == title.spokenPhrase.lowercased()}).first else { completion(INSearchForNotebookItemsIntentResponse(code: .failure, userActivity: nil)) return } let response = INSearchForNotebookItemsIntentResponse(code: .success, userActivity: nil) response.tasks = list.items.map { return INTask(title: INSpeakableString(spokenPhrase: $0.name), status: $0.done ? INTaskStatus.completed : INTaskStatus.notCompleted, taskType: INTaskType.notCompletable, spatialEventTrigger: nil, temporalEventTrigger: nil, createdDateComponents: nil, modifiedDateComponents: nil, identifier: "\(list.name)\t\($0.name)") } completion(response) }

Primero, encontramos la lista basada en el título. En este punto, resolveTitle ya se ha asegurado de que obtengamos una coincidencia exacta. Pero si hay un problema, aún podemos devolver un error.

Cuando tenemos un fallo, tenemos la opción de pasar una actividad de usuario. Si su aplicación usa Handoff y tiene una manera de manejar este tipo exacto de solicitud, entonces Siri podría intentar diferir su aplicación para probar la solicitud allí. No hará esto cuando estemos en un contexto de solo voz (por ejemplo, comenzaste con “Oye, Siri”), y no garantiza que lo hará en otros casos, así que no cuentes con eso.

Esto ya está listo para probar. Elija la extensión de la intención en la lista de objetivos en Xcode. Pero antes de ejecutarlo, edite el esquema.

Una captura de pantalla de Xcode que muestra cómo editar un esquema
Edite el esquema de la intención de agregar una frase de muestra para la depuración.

Eso trae una forma de proporcionar una consulta directamente:

Una captura de pantalla de Xcode que muestra el diálogo de esquema de edición
Agregue la frase de muestra a la sección Ejecutar del esquema. (Vista previa grande)

Tenga en cuenta que estoy usando "ListOMat" debido al problema de los guiones mencionado anteriormente. Afortunadamente, se pronuncia igual que el nombre de mi aplicación, por lo que no debería ser un gran problema.

De vuelta en la aplicación, hice una lista de "Tienda de comestibles" y una lista de "Tienda de hardware". Si le pido a Siri la lista de "tiendas", pasará por la ruta de desambiguación, que se ve así:

Un GIF animado que muestra a Siri manejando una solicitud para mostrar la lista de tiendas
Siri maneja la solicitud pidiendo una aclaración. (Vista previa grande)

Si dice "Tienda de comestibles", obtendrá una coincidencia exacta, que va directamente a los resultados.

Agregar elementos a través de Siri

Ahora que conocemos los conceptos básicos de resolución, confirmación y manejo, podemos agregar rápidamente una intención de agregar un elemento a una lista.

Primero, agregue INAddTasksIntent al plist de la extensión:

Una captura de pantalla en XCode que muestra la nueva intención que se agrega a la plist
Agregue INAddTasksIntent a la extensión plist (vista previa grande)

Luego, actualiza la función handle de nuestro IntentHandler .

 override func handler(for intent: INIntent) -> Any? { switch intent { case is INSearchForNotebookItemsIntent: return SearchItemsIntentHandler() case is INAddTasksIntent: return AddItemsIntentHandler() default: return nil } }

Agrega un stub para la nueva clase:

 class AddItemsIntentHandler: ListOMatIntentsHandler, INAddTasksIntentHandling { }

Agregar un elemento requiere una resolve similar para la búsqueda, excepto con una lista de tareas de destino en lugar de un título.

 func resolveTargetTaskList(for intent: INAddTasksIntent, with completion: @escaping (INTaskListResolutionResult) -> Void) { guard let title = intent.targetTaskList?.title else { completion(.needsValue()) return } let possibleLists = getPossibleLists(for: title) completeResolveTaskList(with: possibleLists, for: title, with: completion) }

completeResolveTaskList es como completeResolveListName , pero con tipos ligeramente diferentes (una lista de tareas en lugar del título de una lista de tareas).

 public func completeResolveTaskList(with possibleLists: [INSpeakableString], for listName: INSpeakableString, with completion: @escaping (INTaskListResolutionResult) -> Void) { let taskLists = possibleLists.map { return INTaskList(title: $0, tasks: [], groupName: nil, createdDateComponents: nil, modifiedDateComponents: nil, identifier: nil) } switch possibleLists.count { case 0: completion(.unsupported()) case 1: if possibleLists[0].spokenPhrase.lowercased() == listName.spokenPhrase.lowercased() { completion(.success(with: taskLists[0])) } else { completion(.confirmationRequired(with: taskLists[0])) } default: completion(.disambiguation(with: taskLists)) } }

Tiene la misma lógica de desambiguación y se comporta exactamente de la misma manera. Decir "Tienda" debe eliminarse la ambigüedad, y decir "Tienda de comestibles" sería una coincidencia exacta.

Dejaremos la confirm sin implementar y aceptaremos el valor predeterminado. Para handle , necesitamos agregar un elemento a la lista y guardarlo.

 func handle(intent: INAddTasksIntent, completion: @escaping (INAddTasksIntentResponse) -> Void) { var lists = loadLists() guard let taskList = intent.targetTaskList, let listIndex = lists.index(where: { $0.name.lowercased() == taskList.title.spokenPhrase.lowercased() }), let itemNames = intent.taskTitles, itemNames.count > 0 else { completion(INAddTasksIntentResponse(code: .failure, userActivity: nil)) return } // Get the list var list = lists[listIndex] // Add the items var addedTasks = [INTask]() for item in itemNames { list.addItem(name: item.spokenPhrase, at: list.items.count) addedTasks.append(INTask(title: item, status: .notCompleted, taskType: .notCompletable, spatialEventTrigger: nil, temporalEventTrigger: nil, createdDateComponents: nil, modifiedDateComponents: nil, identifier: nil)) } // Save the new list lists[listIndex] = list save(lists: lists) // Respond with the added items let response = INAddTasksIntentResponse(code: .success, userActivity: nil) response.addedTasks = addedTasks completion(response) }

Obtenemos una lista de elementos y una lista de objetivos. Buscamos la lista y agregamos los elementos. También debemos preparar una respuesta para que Siri la muestre con los elementos agregados y la envíe a la función de finalización.

Esta función puede manejar una frase como "En ListOMat, agregue manzanas a la lista de compras". También puede manejar una lista de elementos como "arroz, cebollas y aceitunas".

Una captura de pantalla del simulador que muestra a Siri agregando elementos a la lista de la tienda de comestibles
Siri agrega algunos artículos a la lista de la tienda de comestibles

Casi listo, solo unas pocas configuraciones más

Todo esto funcionará en su simulador o dispositivo local, pero si desea enviarlo, deberá agregar una clave NSSiriUsageDescription al plist de su aplicación, con una cadena que describa para qué está usando Siri. Algo como "Tus solicitudes sobre listas se enviarán a Siri" está bien.

También debe agregar una llamada a:

 INPreferences.requestSiriAuthorization { (status) in }

Ponga esto en viewDidLoad de su controlador de vista principal para pedirle al usuario acceso a Siri. This will show the message you configured above and also let the user know that they could be using Siri for this app.

A screenshot of the dialog that a device pops up when you ask for Siri permission
The device will ask for permission if you try to use Siri in the app.

Finally, you'll need to tell Siri what to tell the user if the user asks what your app can do, by providing some sample phrases:

  1. Create a plist file in your app (not the extension), named AppIntentVocabulary.plist .
  2. Fill out the intents and phrases that you support.
A screenshot of the AppIntentVocabulary.plist showing sample phrases
Add an AppIntentVocabulary.plist to list the sample phrases that will invoke the intent you handle. (Vista previa grande)

There is no way to really know all of the phrases that Siri will use for an intent, but Apple does provide a few samples for each intent in its documentation. The sample phrases for task-list searching show us that Siri can understand “Show me all my notes on <appName>,” but I found other phrases by trial and error (for example, Siri understands what “lists” are too, not just notes).

Resumen

As you can see, adding Siri support to an app has a lot of steps, with a lot of configuration. But the code needed to handle the requests was fairly simple.

There are a lot of steps, but each one is small, and you might be familiar with a few of them if you have used extensions before.

Here is what you'll need to prepare for a new extension on Apple's developer website:

  1. Make an app ID for an Intents extension.
  2. Make an app group if you don't already have one.
  3. Use the app group in the app ID for the app and extension.
  4. Add Siri support to the app's ID.
  5. Regenerate the profiles and download them.

And here are the steps in Xcode for creating Siri's Intents extension:

  1. Add an Intents extension using the Xcode template.
  2. Update the entitlements of the app and extension to match the profiles (groups and Siri support).
  3. Add your intents to the extension's plist .

And you'll need to add code to do the following things:

  1. Use the app group sandbox to communicate between the app and extension.
  2. Add classes to support each intent with resolve, confirm and handle functions.
  3. Update the generated IntentHandler to use those classes.
  4. Ask for Siri access somewhere in your app.

Finally, there are some Siri-specific configuration settings:

  1. Add the Siri support security string to your app's plist .
  2. Add sample phrases to an AppIntentVocabulary.plist file in your app.
  3. Run the intent target to test; edit the scheme to provide the phrase.

OK, that is a lot, but if your app fits one of Siri's domains, then users will expect that they can interact with it via voice. And because the competition for voice assistants is so good, we can only expect that WWDC 2018 will bring a bunch more domains and, hopefully, much better Siri.

Otras lecturas

  • “SiriKit,” Apple
    The technical documentation contains the full list of domains and intents.
  • “Guides and Sample Code,” Apple
    Includes code for many domains.
  • “Introducing SiriKit” (video, Safari only), WWDC 2016 Apple
  • “What's New in SiriKit” (video, Safari only), WWDC 2017, Apple
    Apple introduces lists and notes
  • “Lists and Notes,” Apple
    The full list of lists and notes intents.