Guía de NearbyInteraction Framework

Imagina que tuvieras una forma de saber a que distancia te encuentras de otros dispositivos y además supieras en que dirección se encuentra. Pues para eso Apple nos trae el framework NearbyInteraction presentado en esta WWDC 2020.

Esto es posible gracias al chip U1 diseñado por Apple que incorporan los iPhone 11 y al nuevo iOS 14 que se encuentra en fase beta en estos momentos y que son los requisitos mínimos para poder usar este framework.

Para ilustrar este artículo voy a usar una app muy sencilla que busca a otro dispositivo y nos dirá la distancia y su posición relativa.

Presentado a los actores

La forma de trabajar con NearbyInteraction es mediante sesiones. Una sesión es una conexión entre dos dispositivos, y un dispositivo puede conectar con más de un dispositivo.

La clase que representa la sesión se llama NISession y es la puerta de entrada de nuestra app al uso del framework. Con ella podremos comprobar si el dispositivo tiene soporte para el framework, iniciar, pausar y reanudar una sesión con otro dispositivo.

Cuando queramos iniciar una sesión deberemos usar la función run a la que le pasaremos una configuración representada por la clase NINearbyPeerConfiguration que acepta como parámetro el token del dispositivo con el que queremos entablar una sesión.

Un momento, ¿qué es eso del token del cliente? Pues es un identificador único que representa a uno de los dispositivos, o puntos, de la sesión o las diferentes sesiones. Y no, no tienes que generarlo, NISession crear el token de tu dispositivo cuando instancias la clase.

Para recibir actualizaciones del estado de la sesión, como por ejemplo cambios en la distancia y posición con respecto al otro dispositivo, si la sesión de pronto es inválida o se pierde la conexión con el otro punto de la sesión, usaremos el patrón delegate mediante el delegado NISessionDelegate

Una de las razones por la que nuestra sesión puede quedar invalidad es por que la app pasa a ejecutarse en segundo plano, momento en el que el delegado invoca la función sessionWasSuspended.

Si la app vuelve a ejecutarse en primer plano dentro de un tiempo razonable el delegado llama a la función sessionSuspensionEnded. En ese momento deberemos volver a llamar a la función run de NISession pasando de nuevo la configuración.

Si la app pasa demasiado tiempo en background ya no habrá posibilidad de reiniciar la sesión y seremos informados con la función session(_:didInvalidateWith:)

Y lo que más nos interesa, la función donde recibiremos las actualizaciones de distancia y posición se llama session(_:didUpdate:) Es importante saber que puede haber momento en los que la distancia, la posición o ambas tengas valores nulos, por lo que la app debe estar preparada para ello.

Un momento… ¿Cómo paso mi token al otro dispositivo?

Efectivamente, no hay ninguna manera de que el framework NearbyInteraction pueda pasar información a otro dispositivo. Para ello debemos usar otro forma de comunicarnos con los otros puntos de la sesión.

Se pueden usar iCloud, el framework MultipeerConnectivity, sockets, Bonjour, etc. En la sesión de la WWDC han decidido usar el framework MultipeerConnectivity y para el ejemplo lo usaré también.

El hecho de emplear este framework puedes venir motivado por la similitud en lo que a la distancia entre los usuarios se refiere.

Ambos necesitan que no haya mucha distancia entre los puntos por lo que el descubrimiento de un dispositivo con MultipeerConnectivity quiere decir que es muy posible que podamos establecer una sesión de NearbyInteraction.

Ahora que ya tenemos el cuadro completo vamos a entrar en materia usando un proyecto llamado Cerca (Closer).

En su código veremos un ciclo completo de NearbyInteraction

  1. Verificar si podemos usar el framework
  2. Descubrimiento
  3. Intercambio de tokens
  4. Actualización de dirección y distancia

Vamos a ello.

Estructura del proyecto

Al abrir la app en Xcode 12 beta tenemos que centrarnos en 4 archivos.

  • CercaApp. Aquí comprobamos si el dispositivo soporta el framework se puede ejecutar la app
  • NearbyView. Presenta los datos de distancia y dirección hacia el otro dispositivo.
  • ErrorView. Si el dispositivo no soporta NearbyInteractions se lo hacemos saber al usuario
  • CercaViewModel. Contiene toda la lógica para manejar las sesiones de NearbyInteractions y MultipeerConnectivity.

Sobre CercaViewModel, decir que para mayor comodidad a la hora de seguir el código se ha concentrado toda la operativa en el mismo y me he permitido alguna licencia con la arquitectura MVVM.

Estructura de la app

Verificación

Los primero que haremos nada más arrancar la app es comprobar si el usuario podrá ejecutar la app, para ello recordamos que el dispositivo debe contar con el chip U1 corriendo sobre iOS14.

La clase NISession tiene una propiedad estática llamada isSupported que nos dice si el dispositivo puede trabajar con NearbyInteractions. Debemos recurrir a esta propiedad para saber si podemos continuar con la ejecución de la app.

En nuestro caso preguntamos por el valor de la propiedad y dependiendo del resultado mostramos la vista principal o una vista donde informamos del error.

Descubriendo otros dispositivos

Si nuestro dispositivo (tanto físico o en el simulador) tiene soporte ahora nos toca descubrir otros dispositivos y establecer una sesión de NearbyInteraction y otra de MultipeerConnectivity. La secuencia a seguir es la siguiente

  1. Iniciamos una sesión NISession
  2. Intercambiar nuestro token con otros dispositivo si es que es una reconexión
  3. Iniciar una sesión de MultipeerConnectivity
    1. Creamos la sesión
    2. Arrancamos el advertiser (Anunciamos nuestra presencia)
    3. Arrancamos el browser (Buscamos otros dispositivos)

Como hemos dicho lo primero es crear la sesión para NearbyInteraction (1), asignarla la clase que implementa el delegado (2) e intercambiamos el token si es que estamos reconectando con un cliente (3)

Ahora le toca el turno a la sesión de MultipeerConnectivity, que como hemos dicho, lo usaremos para intercambiar el token. De nuevo creámos una sesión (4) donde decimos quienes somos usando la clase MCPeerID (para el ejemplo usamos el nombre del modelo dispositivo).

Luego tenemos que crear los servicios de advertising (5) para que los demás sepan que estamos aquí y el de descubrimiento (6) para saber quienes están ahí fuera. Luego tenemos que establecer los delegados para cada uno de ellos (7) y arrancar las sesiones del advertir y del browser (8).

Vamos a detenernos un momento en las creación de la sesión de advertising, MCNearbyServiceAdvertiser y de descubrimiento, MCNearbyServiceBrowser. Verás que se pasa un parámetro llamado serviceType, que debe cumplir con la nomenclatura de servicios Bonjour. El parámetro discovertyInfo es el identificador de nuestro servicio, al que se recomienda llamar usando la nomenclatura dns-reverse.

Conectando a otros dispositivos

Vamos a suponer que hay otro dispositivo cerca de nosotros y tiene arrancada nuestra app, ha llegado el momento de intercambiar los token para poder empezar a obtener datos de la sesión NearbyInteraction.

Sabremos que hay otro dispositivo porque nos avisan de ello en la función browser(_: foundPeer:) (1) y si el identificador de servicio es el de nuestra app procedemos a invitarle a unirse en una sesión de MultipeerConnectivity.

Si la invitación es aceptada sabremos como va el proceso de conexión gracias a la función session(_: peer: didChange:) (2) donde sabremos el momento exacto en que conectamos con el otro dispositivo.

Será en ese preciso instante cuando tendremos que hacer el intercambio de tokens (3) con el otro cliente.

El intercambio de token se hace usando la sesión de MultipeerConnectivity. Previamente hemos tenido que codificarlos usando NSKeyedArchiver, además, y gracias a que NIDiscoveryToken implementa el protocolo NSSecureCoding, podemos enviarlo al otro dispositivo de una forma segura.

Pero hemos dicho que intercambiamos los token por lo que el otro punto de la conexión nos enviará su token, que usaremos para arrancar la sesión de NearbyInteraction.

Una vez que hemos descodificado el token lo usamos para crear la configuración de la sesión NISession y luego arrancarla. Con esto ya estamos listo para recibir datos sobre la posición del otro dispositivo.

Distancia y Posición

Ahora el delegado de NISession entra en escena. En aquí donde recibiremos las actualizaciones de posición y distancia al otro dispositivo.

Hay que tener en cuenta que ambos dos pueden ser nulos ya que hay veces que no será posible establecer la posición con respecto al otro dispositivo por obstáculos que se interpongan entre ambos.

La posición es un campo que vendrá nulo si no se tiene una línea visual sobre el dispositivo. Dicha línea visual es la misma que la de la cámara gran angular del iPhone.

Fotograma de la sesión Meet Nearby Interaction de la WWDC 2020

La distancia es un tipo Float cuyo valor se mide en metros y la dirección es un vector de tipo simd_float3, que contiene los valores para los ejes X (izquierda o derecha), Y (arriba o abajo) Z (cerca o lejos) que nos dicen la dirección en la que se encuentra el otro dispositivo.

Todo esto llega a través de la función session(_: didUpdate:) que recibe un array de NINearbyObject, que son los objetos que contiene la distancia en la variable distance y la dirección en la variable direction

Y con esto ya somos capaces de conocer donde se encuentra el otro dispositivo.

Tiempo de vida de la sesión

Tenemos que tener cuidado con la sesión y los token, ya que cuando se pierde la sesión hay que volver a intercambiar los token entre los dispositivos.

Una perdida de sesión puede suceder porque el usuario cierra la app o deja de estar en primer plano, por ejemplo.

Es muy importate recordar que las sesiones pueden quedar invalidadas y que es necesario realizar el intercambio de tokens.

Buenas prácticas

Los ingenieros de Cupertino, que han desarrollado este framework y saben de los que hablan nos recomiendan lo siguiente:

  • Comprobar si el dispositivo soporte el framework NearbyInteraction
  • Tener en cuenta que la distancia y la posición pueden ser nulos y eso no significa que sea un error.
  • Ejecutar la aplicación en modo retrato si es posible.

Ahora sólo te queda pasártelo bien usando el framework.

Código fuente

En este repositorio de GitHub encontraréis el proyecto que acompaña este artículo.

Enlaces de interés

Un comentario

Los comentarios están cerrados.