https://a.storyblok.com/f/270183/93755/d624bc5e0b/blog_swiftui_phone-call_1200x600.png

Cómo hacer llamadas telefónicas con SwiftUI

Publicado el July 5, 2023

Tiempo de lectura: 15 minutos

En este tutorial, crearás una aplicación SwiftUI que pueda realizar llamadas a un número de teléfono especificado utilizando el Vonage Client SDK para iOS. SwiftUI es un marco de interfaz de usuario lanzado por Apple en 2019, que ayuda a crear aplicaciones en todas las plataformas de Apple con menos código que sus predecesores. Como verás, SwiftUI tiene una sintaxis declarativa que te permite declarar lo que debe hacer la interfaz de usuario, lo que facilita la lectura de la aplicación.

Requisitos previos

  • Una Account API de Vonage. Si aún no tienes una, puedes inscribirte hoy.

  • Xcode 12 y Swift 5 o superior.

  • Cocoapods para instalar el Client SDK de Vonage para iOS.

  • La CLI de Vonage. Si aún no la tiene, puede instalarla con npm install -g @vonage/cli.

Visión general

Este proyecto consta de 3 partes, la aplicación de Vonage, el servidor webhook y la aplicación de iOS. La aplicación de Vonage es cómo puedes usar los productos de Vonage, en este caso, la Voice API. La aplicación de Vonage que crearás requiere 2 URL, una answer_url y una event_url. La dirección answer_url debe devolver un objeto de control de llamadas, que le indicará a Vonage cómo manejar la llamada. La parte del servidor del proyecto devolverá un objeto de control de llamadas basado en el número especificado en la aplicación iOS.

Creación del servidor

Como se mencionó anteriormente, es necesario crear un servidor webhook que expone dos direcciones URL, una de las cuales devuelve un objeto de control de llamada Objeto de control de llamada. Contendrá una acción de conexión que reenviará la llamada al número de teléfono PSTN. Para ello, extraerá el número de destino del parámetro de consulta to y devolviéndolo en la respuesta.

Cree un nuevo directorio de proyecto en un destino de su elección y cambie a él:

mkdir vonage-tutorial cd vonage-tutorial

Dentro de la carpeta, inicializa un nuevo proyecto Node.js ejecutando este comando:

npm init -y npm install express localtunnel --save

Necesitarás un número de Vonage. Puedes comprar un número usando la CLI de Vonage. El siguiente comando compra un número disponible en los EE. UU. Especifica un código de país alternativo de dos caracteres para comprar un número en otro país.

vonage numbers:search US vonage numbers:buy 15555555555 US

Dentro de la carpeta del proyecto, cree un archivo llamado server.js y agrega el código como se muestra a continuación; asegúrate de reemplazar NUMBER por tu número de Vonage (en E.164 ), así como SUBDOMAIN con un valor único. El valor utilizado formará parte de las URL que establecerás como webhooks en el siguiente paso.

Creación de la aplicación de Vonage

En este paso, crearás una aplicación de Vonage Vonage con capacidad para casos de uso de comunicación de voz dentro de la aplicación.

Crear una aplicación

Crea una aplicación de Vonage copiando y pegando el siguiente comando en el terminal. Asegúrate de cambiar los valores de --voice_answer_url y --voice_event_url sustituyendo SUBDOMAIN por el valor real utilizado en el paso anterior:

vonage apps:create "SwiftUICall" --voice_answer_url=https://SUBDOMAIN.loca.lt/voice/answer --voice_event_url=https://SUBDOMAIN.loca.lt/voice/event

Se crea un archivo llamado vonage_app.json en el directorio de tu proyecto y contiene el nuevo ID de aplicación de Vonage y la clave privada. También se crea un archivo de clave privada llamado SwiftUICall.key también se creará. Ahora que tu aplicación está creada, vincula tu número de Vonage a tu aplicación:

vonage apps:link APPLICATION_ID --number=YOUR_VONAGE_NUMBER

Crear un usuario

La Conversation API de Vonage tiene el concepto de usuarios. Un usuario representa un usuario único de tu aplicación y, por lo tanto, tiene un ID único. Así es como tu aplicación iOS se autenticará con los servidores de Vonage y será identificada por ellos. Puedes usar la CLI para crear un usuario:

vonage apps:users:create "Alice"

Esto añadirá un usuario, con el nombre de usuario Alice, a tu aplicación y mostrará su ID único.

Crear un JWT

Los SDK para clientes de Vonage utilizan tokens web JSON (JWT) para la autenticación. Los JWT son un método para representar reclamos de manera segura entre dos partes. Puedes leer más sobre los JWT en JWT.io o las reclamaciones que admite la Conversation API admite. Utilizaremos la clave privada, el ID de la aplicación y el nombre de usuario de las secciones anteriores para crear el JWT necesario para tu aplicación iOS. Una vez más, esto se hace a través de la CLI. Sustituye la variable APPLICATION_ID con el id de tu aplicación y PRIVATE_KEY con el nombre de su archivo de clave privada.

vonage jwt --app_id=APPLICATION_ID --subject=Alice --key_file=./PRIVATE_KEY --acl='{"paths":{"/*/users/**":{},"/*/conversations/**":{},"/*/sessions/**":{},"/*/devices/**":{},"/*/image/**":{},"/*/media/**":{},"/*/push/**":{},"/*/knocking/**":{},"/*/legs/**":{}}}'

Esto mostrará el JWT para el usuario Alice que ha sido firmado utilizando su clave privada. Guarde esto, ya que lo necesitará más adelante.

Creación de la aplicación iOS

Ahora que el servidor y la aplicación de Vonage están configurados, es hora de crear la aplicación para iOS.

Crear un proyecto Xcode

Para empezar, abre Xcode y crea un nuevo proyecto yendo a Archivo > Nuevo > Proyecto. Selecciona una plantilla de App y dale un nombre. Seleccione, SwiftUI para la interfaz, SwiftUI App para el ciclo de vida, y Swift para el lenguaje, por último, una ubicación para guardar el proyecto.

Xcode project creation

Instala el Vonage Client SDK

Ahora que el proyecto está creado, puedes agregar el Vonage Client SDK como dependencia. Navega a la ubicación donde guardaste el proyecto en tu terminal y ejecuta los siguientes comandos.

  1. Ejecute el comando pod init para crear un nuevo Podfile para su proyecto.

  2. Abra el Podfile en Xcode utilizando open -a Xcode Podfile.

  3. Actualiza el Podfile para que tenga VonageClientSDKVoice como dependencia.

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'SwiftUICall' do
    # Comment the next line if you don't want to use dynamic frameworks
    use_frameworks!

    # Pods for SwiftUICall
    pod 'VonageClientSDKVoice'

end
  1. Instale el SDK con pod install.

  2. Abra el nuevo xcworkspace en Xcode con open SwiftUICall.xcworkspace.

Permisos de micrófono

Dado que la aplicación utilizará el micrófono para realizar llamadas, es necesario pedir permiso explícitamente para hacerlo. El primer paso es editar el archivo Info.plist archivo. Se trata de un archivo que contiene todos los metadatos necesarios para la aplicación. Añada una nueva entrada al archivo pasando el ratón por encima de la última entrada de la lista y haciendo clic en el pequeño botón + que aparece. Elija Privacy - Microphone Usage Description de la lista desplegable y añada Microphone access required to make and receive audio calls. como valor. Usted hará el segundo paso para solicitar permisos de micrófono más adelante en el tutorial.

Creación de la clase modelo

Antes de empezar a construir la interfaz de usuario (UI) de la aplicación, construirás primero una clase modelo. Esta clase se utiliza para separar la lógica de la aplicación del código de la vista. En este caso, la clase modelo manejará las llamadas delegadas desde el Client SDK y cuándo debe mostrarse el botón de llamada. En la parte superior del archivo ContentView.swift importe el Client SDK y AVFoundation.

import SwiftUI
import AVFoundation
import VonageClientSDKVoice

A continuación, en la parte inferior del archivo, crear una nueva clase llamada CallModel:

final class CallModel: NSObject, ObservableObject, VGVoiceClientDelegate {

}

Dentro de esta clase, defina las propiedades y funciones de ayuda necesarias:

final class CallModel: NSObject, ObservableObject, VGVoiceClientDelegate {
    @Published var status: String = ""
    @Published var isCalling: Bool = false
    let client = VGVoiceClient()
    var number: String = ""
    
    private var callId: String?
    private let audioSession = AVAudioSession.sharedInstance()
    
    func updateStatus(_ text: String) {
        DispatchQueue.main.async {
            self.status = text
        }
    }
    
    func resetState() {
        DispatchQueue.main.async {
            self.callId = nil
            self.isCalling = false
            self.number = ""
        }
    }
}

La envoltura de la propiedad @Published es la forma en que la interfaz de usuario sabrá cuándo reaccionar a los cambios de la clase modelo. Todo esto se maneja para usted como la clase se ajusta a la ObservedObject protocolo. La propiedad client es para el cliente de voz de Vonage, que te permitirá realizar llamadas. callId se utiliza para almacenar el objeto de llamada, y audioSession se utiliza para solicitar los permisos del micrófono. Configura el Client SDK:

func initializeClient() {
    let config = VGClientConfig(region: .US)
    client.setConfig(config)
    client.delegate = self
}

Para completar la solicitud de permisos de micrófono para la aplicación, añada la siguiente función a la clase CallModel clase

func requestPermissionsIfNeeded() {
    if audioSession.recordPermission != .granted {
        audioSession.requestRecordPermission { (isGranted) in
            print("Microphone permissions \(isGranted)")
        }
    }
}

Esto primero comprobará si los permisos ya han sido concedidos; si no, los solicitará e imprimirá el resultado en la consola. A continuación, tienes que iniciar sesión como el usuario Alice con el Client SDK. Para ello, añade la siguiente función a la clase CallModel reemplazando ALICE_JWT con el JWT de antes:

func loginIfNeeded() {
    guard status != "Connected" else { return }
    VGVoiceClient.isUsingCallKit = false
    client.createSession("ALICE_JWT") { error, sessionId in
        if let error {
            self.updateStatus(error.localizedDescription)
        } else {
            self.updateStatus("Connected")
        }
    }
}

Esta función comprueba si el cliente ya ha iniciado sesión; si no, utilizará el JWT que generó anteriormente para iniciar sesión y establecerá el delegado para el cliente en esta clase. El VGVoiceClientDelegate es la forma en que el Client SDK comunica los cambios con los servidores de Vonage a tu aplicación. A continuación, implementa las funciones de delegado requeridas.

func voiceClient(_ client: VGVoiceClient, didReceiveHangupForCall callId: VGCallId, withQuality callQuality: VGRTCQuality, reason: VGHangupReason) {
    self.resetState()
}

func client(_ client: VGBaseClient, didReceiveSessionErrorWith reason: VGSessionErrorReason) {
    let reasonString: String!
    
    switch reason {
    case .tokenExpired:
        reasonString = "Expired Token"
    case .pingTimeout, .transportClosed:
        reasonString = "Network Error"
    default:
        reasonString = "Unknown"
    }
    
    status = reasonString
}

func voiceClient(_ client: VGVoiceClient, didReceiveInviteForCall callId: VGCallId, from caller: String, with type: VGVoiceChannelType) {}
func voiceClient(_ client: VGVoiceClient, didReceiveInviteCancelForCall callId: VGCallId, with reason: VGVoiceInviteCancelReason) {}

Cuando se produce un error de sesión, la propiedad status y si la llamada finaliza, el estado del modelo se reinicia. Las siguientes dos funciones que necesitas agregar permitirán a tu aplicación hacer y terminar llamadas. Añade las siguientes funciones.

func startCall() {
    self.isCalling = true
    client.serverCall(["to": number]) { error, callId in
        if error == nil {
            self.callId = callId
        }
    }
}

func endCall() {
    client.hangup(callId!) { error in
        if error == nil {
            self.resetState()
        }
    }
}

La función callNumber utiliza el Client SDK para realizar una llamada al número de la propiedad number propiedad. Esto utiliza la función answer_url de antes en el tutorial. Vonage realizará entonces una llamada HTTP a su answer_url con el número de la aplicación iOS, que devolverá un objeto de control de llamadas con instrucciones para que Vonage conecte tu aplicación con el número. Si todo esto tiene éxito, el Client SDK devuelve un ID de llamada que se almacena en la clase. La función endCall cuelga la llamada, establece la propiedad callId a nil y devuelve el valor booleano isCalling a false para que la interfaz de usuario pueda actualizarse.

La última función que debe añadir al módulo CallModel llama a initializeClient, requestPermissionsIfNeededy loginIfNeeded funciones.

func setup() {
    initializeClient()
    requestPermissionsIfNeeded()
    loginIfNeeded()
}

Crear la interfaz de usuario

La interfaz de usuario constará de un objeto Text para mostrar el estado, un objeto TextField para introducir los números y un par de objetos Button para iniciar y finalizar llamadas. Añade una propiedad para la clase CallModel que creó anteriormente a la estructura ContentView y utilícela para mostrar el estado del Client SDK.

struct ContentView: View {
    @StateObject var callModel = CallModel()
    
    var body: some View {
        VStack {
            Text(callModel.status)
        }
        .animation(.default)
        .onAppear(perform: self.callModel.setup)
    }
}

Cuando aparezca la vista, llamará a la función setup en la clase del modelo; esto solicitará los permisos del micrófono y registrará al cliente. Al hacerlo, se actualizará la propiedad status de la clase modelo, y el cambio en su valor hará que la función ContentView para que se actualice.

Ahora que el cliente ha iniciado sesión, puede utilizar el resto de las propiedades publicadas en la clase modelo para construir el resto de la interfaz de usuario. Añade el siguiente bloque de código al VStack.

if self.callModel.status == "Connected" {
    TextField("Enter a phone number", text: $callModel.number)
        .textFieldStyle(RoundedBorderTextFieldStyle())
        .multilineTextAlignment(.center)
        .keyboardType(.numberPad)
        .disabled(self.callModel.isCalling)
        .padding(20)
    
    if !self.callModel.isCalling {
        Button(action: { self.callModel.startCall() }) {
            HStack(spacing: 10) {
                Image(systemName: "phone")
                Text("Call")
            }
        }
    }
    
    if self.callModel.isCalling {
        Button(action: { self.callModel.endCall() }) {
            HStack(spacing: 10) {
                Image(systemName: "phone")
                Text("End Call")
            }
            .foregroundColor(Color.red)
        }
    }
}

La página TextField se animará en la vista si el cliente está conectado. El valor de TextField está vinculado a la propiedad number de la clase modelo. Para ello se utiliza el signo $ signo. Una vez que se ha introducido un número de longitud válida, se cumplirán las condiciones para mostrar el botón de llamada, y una vez que se ha iniciado una llamada, ya no se cumplirán las condiciones, pero sí las condiciones para el botón de fin de llamada. Así que sólo se mostrará un botón a la vez, y toda la actualización de la interfaz de usuario será manejada por ti.

Ejecute su aplicación

Si ejecuta el proyecto (CMD + R), primero se le pedirá que autorice los permisos de micrófono. Podrá introducir un número y llamarlo. El formato de los Numbers debe ser del tipo E.164 .

Making a call

¿Y ahora qué?

Puede encontrar el proyecto completo en GitHub. Puedes hacer mucho más con el Client SDK. Más información en developer.vonage.com.

Compartir:

https://a.storyblok.com/f/270183/400x400/19c02db2d3/abdul-ajetunmobi.png
Abdul AjetunmobiVonage Antiguo miembro del equipo

Abdul es desarrollador de Vonage. Ha trabajado en productos de consumo como ingeniero de iOS. En su tiempo libre, le gusta andar en bicicleta, escuchar música y asesorar a aquellos que están comenzando su viaje en la tecnología.