Aprende a programar tu primera app para las Vision PRO
Aprende a programar tu primera app para las Vision PRO 

Aprende a PROGRAMAR tu PRIMERA APP para las VISION PRO

Aprende a programar tu primera app para las Vision PRO, las nuevas gafas de Apple para crear experiencias en Realidad Aumentada. Con el framework visionOS y SwiftUI podrás crear apps con muy pocas líneas de código y muy sencillas de crear.

SwiftBeta

Tabla de contenido


👇 SÍGUEME PARA APRENDER SWIFTUI, SWIFT, XCODE, etc 👇
Aprende a crear tu primera app para las Vision Pro
Aprende a crear tu primera app para las Vision Pro

Hoy en SwiftBeta vamos a aprender a crear nuestra primera aplicación para las Vision PRO. Voy a explicar todo paso por paso, de esta manera te será muy fácil seguir el video y ver muchas de las novedades que Apple ha lanzado con este nuevo dispositivo

Para hacerlo, usaremos muchos de los conceptos que hemos visto en el canal. E incluso usaremos alguna nueva vista que solo está disponible para las Vision PRO, como la ornament. Pero no me voy adelantar, va a ser un video muy completo y fácil de seguir.

La app que vamos a crear va a ser muy interesante. Haremos una petición HTTP para obtener un listado de videos del canal de Youtube de SwiftBeta. Al pulsar en uno de estos videos, se abrirá en la pantalla central el video para poderlo reproducir junto con su título. Añadiremos varias vistas para poder marcar como favorito un video, y varios ViewModifiers nuevos para resaltar contenido en nuestra aplicación.

También vamos a ver a cómo usar el Canvas y el Simulador de las Vision Pro para cerrar apps, posicionarlas, crear videos o gifs, etc. Va a ser un video muy completo.

Instalar Xcode 15 Beta 2 (en adelante)

¿Qué es lo primero que necesitamos? Necesitamos tener descargado Xcode 15, y para ser exactos, a partir de la Beta 2 (en esta Beta Apple permite descargar visionOS, y por lo tanto poder crear aplicaciones para las Vision PRO). Aquí te dejo un enlace de descarga.

Downloads and Resources - Xcode - Apple Developer
Find Xcode downloads, tools, documentation, tutorials, videos, and more.

Una vez lo tengas descargado, tan solo debes instalarlo y ya estarías listo para crear tu primer proyecto para las Vision PRO.

Creamos nuestra primera app para las Vision PRO

Lo primero de todo que vamos hacer es crear nuestro proyecto, nuestra app en Xcode 15. En este caso, seleccionamos Multiplatform visionOS, y en Application seleccionamos App y le damos a Next.

Una vez hecho, nos aparece una ventana muy parecida a la que nos aparece cuando creamos una app para iOS. Vamos a añadir un nombre a nuestra app, en mi caso SwiftBetaYoutube. Y fíjate, que al escoger que nuestra app es con visionOS aparecen 2 nuevas opciones:

  • Initial Scene
  • Immersive Space

En nuestro caso, vamos a escoger como Initial Scene, window ya que vamos a trabajar con simples ventanas, no queremos trabajar con 3D. Y en la opción Immersiva Space seleccionamos el None.

Y una vez hemos seleccionado esta información, ya estamos listos para crear nuestra app. Le damos a create, y en mi caso creo el proyecto en el escritorio.

Preview visionOS

Al crear nuestra primera app para las Vision Pro, lo primero que destaca es la preview, en el canvas de Xcode podemos ver una demo de una app que nos ha pregenerado Xcode al crear nuestra app con visionOS. Es muy parecido al Canvas que solemos usar al crear una app para iPhone, vamos a ver unas breves pinceladas.

Lo primero que podemos ver es que nuestra app está usando un NavigationSplitView, esto no es más que una vista en SwiftUI donde tenemos esta parte izquierda, donde podemos mostrar un listado. Y luego tenemos una vista llamada detailView, que sería la que coge toda la atención. Esta la usaremos para reproducir nuestro video de Youtube.

Si miramos un poco más abajo del canvas podemos ver una serie de Buttons abajo a la derecha, voy a ir rápidamente expicando uno por uno. Ya que son realmente importantes para interactuar con nuestra app.

Interact, este Button nos permite interactuar con nuestra preview. Fíjate que puedo mover la ventana si selecciono la línea horizontal que aparece en la parte inferior de la ventana, y mantengo pulsado puedo desplazar la ventana en diferentes partes de mi escena. También podemos agrandar la ventana si nos movemos hacía alguna de las esquinas inferiores.
Si tuvieramos algún button en la ventana o un scroll podríamos interactuar, esto lo veremos más adelante. Vamos a ver el siguiente Button.

Free Look, al seleccionar este Button tenemos un punto fijo pero podemos voltear la vista. Eso sí, siempre desde el mismo punto, es como si giraras la cabeza para mirar a ambos lados, el derecho o izquierdo, estás girando desde un mismo punto. Y si queremos cambiar el punto, debemos utilizar el siguiente Button.

Pan, nos sirve para movernos por 2 ejes del plano. Este es muy sencillo, vamos a por el siguiente Button.

Orbit, al seleccionar este Button podemos desplazarnos como con el Free Look que hemos visto antes, pero con una curva, como si de una órbita se tratara. Pero ¿qué ocurre si queremos hacer Zoom? aquí es donde entra en juego el último Button

Dolly, con este Button podemos hacer Zoom dentro de nuestra escena. Si tenemos un ratón podemos usar la ruedecilla o incluso si tenemos un trackpad podemos usar el pan gesture para acercanos o alejarnos de un punto.

Una vez hemos hecho un breve repaso, ¿qué ocurre si no quieres trabajar en esta escena o con este fondo? Puedes seleccionar varios escenarios simulados si clickas en el Button que parecen 2 montañas. Aquí puedes ver diferentes estancias de una casa con diferente iluminación. Muy útil para poder testear cómo se comporta tu app.

Y el siguiente Button, el que es una cámara, sirve para posicionarnos dentro de nuestra escena rápidamente. Si pulsas verás que hay diferentes opciones, top, bottom, leading, etc. Si pulsamos en front volvemos al punto inicial, perfecto para empezar a escribir código.

Modelo Video

Lo siguiente que vamos hacer es crear el modelo. ¿para qué nos va a servir el modelo? El modelo va a ser fundamental para:

  • Transformar el JSON obtenido de nuestra petición HTTP a un modelo de nuestro dominio (un modelo que va a usar nuestra aplicación).
  • Y también nos va a servir para mostrar esta información en la Vista.

Imagina que el modelo de tu app es como un molde, un cascaron, una plantilla para crear un muñeco. Al hacer la petición HTTP va a venir información en formato JSON que vamos a meter en nuestro molde. Y así crearemos nuestro modelo. Pues, vamos a crear el modelo con el que vamos a trabajar en nuestra aplicación, pulsamos el atajo de teclado COMMAND+N y llamamos a nuestro modelo Video y va a tener varias propiedades:

struct Video: Identifiable, Decodable, Hashable {
    let id: UUID = UUID()
    let url: String
    let title: String
    let image: String
}

Una vez creado el modelo, lo siguiente que vamos hacer es crear nuestro ViewModel. Dentro del ViewModel es donde haremos la petición HTTP, y transformaremos el JSON al modelo video. De esta manera nuestra aplicación usará las propiedades de Video para mostrar la información en las vistas de nuestra app.

Vamos a crear el ViewModel, pulsamos el atajo de teclado COMMAND+N, y llamamos a nuestro nuevo fichero ViewModel:

import Foundation

class ViewModel {
    
    init() {
        
    }
Antes de continuar, si quieres apoyar el contenido que subo cada semana, suscríbete. De esta manera seguiré publicando contenido completamente grautito en Youtube.

Lo siguiente que vamos hacer es añadir 1 propiedad llamada Videos que será de tipo Array de Video.

import Foundation

class ViewModel {
    var videos: [Video] = []
    
    init() {
    }

Cada vez que se modifique el valor de la propiedad videos, queremos actualizar nuestra vista en SwiftUI para mostrar lo últimos datos. Para tener esta funcionalidad, debemos usar el framework observation, y marcar nuestra class como Observable:

import Foundation
import Observation

@Observable
class ViewModel {
    var videos: [Video] = []
    
    init() {
    }

Y por último, debemos hacer nuestra petición HTTP, para que al recuperar los datos de los videos de nuestro servidor, se transformen al modelo Video, y así actualizar la propiedad videos, voy a poner el modo rápido y lo explico linea por linea:

import Foundation
import Observation

@Observable
class ViewModel {
    var videos: [Video] = []
    
    init() {
        getVideos()
    }
    
    func getVideos() {
        Task {
            do {
                guard let url = URL(string: "https://raw.githubusercontent.com/SwiftBeta/YoutubeStaticBackend/main/getVideos.json") else {
                    return
                }
                let (jsonData, _) = try await URLSession.shared.data(from: url)
                videos = try JSONDecoder().decode([Video].self, from: jsonData)
            } catch {
                videos = []
            }
        }
    }
}

Hemos creado un método llamado getVideos. Dentro de este método creamos la URL con el endpoint al que vamos a llamar y así obtener el JSON. Este JSON lo obtenemos como un tipo data que lo usamos en el JSONDecoder para crear la transformación al modelo Video. Y si todo funciona correctamente asignamos los nuevos valores a la propiedad videos.

Un detalle importante, es que estamos usando una URL a mi repositorio de Github donde tengo un JSON estático. Si queremos obtener información que se vaya actualizando en el tiempo debemos usar la API de Youtube. Es muy sencilla usarla, si quieres que lo explique en futuros videos ponlo en los comentarios de este video.

Creamos la vista en SwiftUI

Una vez obtenemos la información y la almacenamos en el Array Videos, ahora ya estamos listos para mostrarla en nuestra vista. Esta información la mostraremos dentro de nuestro NavigationSplitView en un List. Y para poder acceder a esta información, debemos crear una instancia de ViewModel en ContentView.

struct ContentView: View {
        
    @State var viewModel = ViewModel()
    
    ...
}

Lo siguiente que vamos hacer es crear nuestro List, esta vista nos permite crear una colección de vistas, y por lo tanto es perfecta para mostrar nuestro Array Videos. Dentro de List voy a crear una vista muy sencilla con un VStack. Vamos a ello:

struct ContentView: View {
        
    @State var viewModel = ViewModel()
    
    var body: some View {
        NavigationSplitView {
            List(viewModel.videos) { video in
                VStack {
                    
                }
                .padding()
            }
            .navigationTitle("+180 Videos")
        } detail: {
        ...
}

Hasta aquí nada nuevo, un List que por cada video creará la vista que añadamos dentro de VStack. Y es justo lo que vamos hacer ahora, tenemos el parámetro video con la información que queremos mostrar en nuestra Vista, vamos a crear un AsyncImage para poder mostrar la imagen del video, y un text para mostrar el title del video, nos quedaría algo parecido al siguiente código:

struct ContentView: View {
        
    @State var viewModel = ViewModel()
    
    var body: some View {
        NavigationSplitView {
            List(viewModel.videos) { video in
                VStack {
                    AsyncImage(url: .init(string: video.image), scale: 1.0) { image in
                        image
                            .resizable()
                            .scaledToFit()
                            .cornerRadius(20)
                    } placeholder: {
                        ProgressView()
                    }
                    
                    Text(video.title)
                        .font(.title)
                        .padding(.top, 2)
                }
                .padding()
            }
            .navigationTitle("Videos SwiftBeta")
...
}

Perfecto, molaría poder probar todo este código que hemos hecho hasta ahora, y es justo lo que vamos hacer. Si vamos al canvas, vemos que aparece la información correctamente.

Vamos a repasar lo que hemos visto hasta ahora. Hemos creado un ViewModel, que hace una petición y transforma el JSON a un Array de tipo Video. Al actualizar esta propiedad, la UI se refresca mostrando los nuevos datos, y por lo tanto aquí en la vista de la izquierda podemos ver un listado de Videos. En esta vista hemos usado un AsyncImage, donde le pasamos la URL de la imagen, y mientras carga mostramos un ProgressView. Y justo aquí abajo hemos creado un Text para mostrar el título del video.

Ornament View

Antes de hacer la vista más importante donde se reproducirá el video, vamos a usar una nueva vista llamada ornament. Esta vista es muy sencilla de usar y la verdad que me gusta bastante.

Nos vamos a nuestra vista NavigationSplitView, y justo donde se cierra la última llave añadimos .ornament. Aquí podemos ver varios parámetros que son customizables. Antes de escribir el primer parámetro, vamos a generar la vista que queremos mostrar:

.ornament(attachmentAnchor: <#T##OrnamentAttachmentAnchor#>) {
    VStack {
        Text("¡Suscríbete a SwiftBeta en Youtube! 🚀")
    }
    .frame(width: 400, height: 60)
}

Es una vista muy sencilla donde añadimos un VStack con un Text que pone "Suscríbete a SwiftBeta en Youtube", si no lo has hecho hazlo, de esta manera ayudas a que siga creando contenido completamente gratuito en Youtube.

Vamos a ver cómo de customizable es esta vista, vamos al primer parámetro y añadimos un ., aquí solo podemos escoger la escena, pero fíjate que espera otro parámetro de alineamiento. Vamos a probar algunos de ellos para que veas cómo se ven en el Canvas. Al poner .bottom, o .leading, vemos que la vista ornament se puede poner en varias partes de nuestra app, pero es apenas visible. Vamos a arreglarlo usando un ViewModifier llamado glassBackgroundEffect

.ornament(attachmentAnchor: .scene(alignment: .bottom), contentAlignment: .center) {
    VStack {
        Text("¡Suscríbete a SwiftBeta en Youtube! 🚀")
    }
    .frame(width: 400, height: 60)
    .glassBackgroundEffect(in: .rect(cornerRadius: 10))
}

El cambio es abismal, mucho más legible. Vamos a dejarlo así. Puedes probar varias posiciones cambiando el valor de alignment.

Una vez hemos aprendido a usar la vista Ornament, vamos a crear la vista más importante, la vista central que nos permitirá reproducir un video de Youtube.

YoutubeView en SwiftUI

Lo que vamos hacer es usar una vista llamada WKWebView que nos permite cargar URL y así poder reproducir el video de Youtube, son 10 líneas de código. Creamos un fichero nuevo, pulsamos COMMAND+N. Llamamos a nuestro nuevo fichero YoutubeView.

Pongo el modo rápido y lo explico a continuación:

import Foundation
import WebKit
import SwiftUI

struct YouTubeView: UIViewRepresentable {
    let videoId: String
    
    func makeUIView(context: Context) ->  WKWebView {
        WKWebView()
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) {
        guard let youtubeURL = URL(string: "https://www.youtube.com/embed/\(videoId)") else { return }
        uiView.scrollView.isScrollEnabled = false
        uiView.load(URLRequest(url: youtubeURL))
    }
}
  1. Hemos creado una nueva View llamada YoutubeView que conforma UIViewRepresentable. Conformar este protocolo es necesario para poder usar una vista de UIKit en SwiftUI, y en este caso WKWebView está creada con el framework UIKit.
  2. Lo siguiente que hacemos es crear una instancia de WKWebView
  3. Y el último método nos sirve para configurar el WKWebView. Lo único que hacemos es cargar la URL como si de safari se tratara, e indicamos que no queremos tener scroll. Fíjate que aquí le estamos pasando un parámetro muy importante para indicar qué video queremos cargar. Y este parámetro es el videoId.

Una vez tenemos la vista YoutubeView creada, ya la podemos usar en ContentView.

Borramos todo lo que tenemos dentro del closure detail, y añadimos un VStack y dentro nuestra vista YoutubeView. ¿Pero qué ocurre?

detail: {
    VStack {
        YouTubeView(videoId: )
            .padding()
    }
}

No tenemos ni idea de qué videoId queremos pasarle, vamos a solucionar de una forma muy fácil. Si volvemos a nuestro List, en la documentación, si pulsamos COMMAND+CLICK podemos ver que podemos pasar otro parámetro que es opcional llamado selection. Cada vez que pulsemos en un video del listado, podemos almacenarlo para que la vista detail sepa que video toca mostrar.

List selection para saber el video a reproducir

Añadimos este parámetro en List, y nos vamos al ViewModel para crear la propiedad que almacenará esta información:

List(viewModel.videos, selection: )

No te preocupes por los errores, lo solucionaremos en un momento. Vamos al ViewModel y creamos la siguiente propiedad:

var videoIds: Set<Video.ID> = []

Un set es como un Array, pero no tienen orden y evitan tener elementos duplicados. Una vez hemos añadido esta propiedad, creamos 3 métodos en nuestro ViewModel.

  • El primer método nos va a servir para saber si nuestra nueva propiedad está vacía.
func isVideoIdsEmpty() -> Bool {
    videoIds.isEmpty
}

Muy sencillo

  • El siguiente método nos va a servir para obtener el video que el user ha seleccionado. De esta manera cargaremos el video correcto en la vista detalle:
func getSelectedVideoId() -> String {
    videos.first(where: { $0.id == videoIds.first })?.url ?? ""
}
  • El último método es para obtener el título de nuestro video, de esta manera también podemos mostrarlo en la vista detalle:
func getSelectedVideoTitle() -> String {
    videos.first(where: { $0.id == videoIds.first })?.title ?? "No title"
}

Ahora ya podemos decir que hemos terminado con nuestro ViewModel. Solo debemos usar estos nuevos métodos y propiedad en nuestra vista ContentView. Vamos a ContentView, y usamos la propiedad videoId en el parámetro que hemos dejado vacío de List:

List(viewModel.videos, selection: $viewModel.videoIds)

Ahora podemos ir al siguiente error, vamos a la vista de YoutubeView y usamos el método que nos retorna el Id, el identificador del video pulsado:

detail: {
    VStack {
        YouTubeView(videoId: viewModel.getSelectedVideoId())
            .padding()
    }
}

Ya que estamos aquí, vamos a comprobar que si el Set de videoIds no está vacío, que muestre la vista de YoutubeView, y también que muestre el título del Video:

detail: {
    VStack {
        if !viewModel.isVideoIdsEmpty() {
            VStack {
                Text(viewModel.getSelectedVideoTitle())
                    .font(.largeTitle)
                YouTubeView(videoId: viewModel.getSelectedVideoId())
                    .padding()
            }
            
        }
    }
}

Si ahora clickamos en uno de los videos del listado, vemos que se carga el video en la vista Detail! vamooooooooos! si das al play puedes reproducir el video, con sonido incluido.

Si ves que la pantalla es pequeña, puedes agrandar la ventana. Lo puedes hacer desde la esquina inferior derecha, y allí podrás estirar para agrandar la ventana.

toolbar en vista detail

Antes de compilar nuestra aplicación, vamos a añadir una toolbar a la vista detail de nuestra app. De esta manera podremos simular que marcamos un video como favorito.

Creamos una nueva propiedad @State de tipo Bool llamada selectedFavorite

@State var selectedFavorite: Bool = false

y a continuación usamos el ViewModifier toolbar como hemos visto varias veces en el canal.

.toolbar {
    ToolbarItem {
        Button {
            selectedFavorite.toggle()
        } label: {
            Label("Favorite", systemImage: "star")
                .symbolVariant(selectedFavorite ? .fill : .none)
        }
    }
}

Ahora desde el Canvas podemos ver un Button con forma de estrella en la parte superior de la vista Detail. Si clickamos vemos como cambia, indicando un relleno cuando lo hemos marcado como favorito.

Bastante chulo la verdad! Ahora, vamos a continuar con la última parte y es compilar nuestra app.

Compilar la app en el Simulador

Compilamos nuestra app para ver cómo se vería fuera del Canvas. De esta manera también te enseñaré como cerrar apps e interactuar con varias pantallas abiertas.

Una vez lanzada la app, podemos clickar y reproducir los videos, igual que hemos visto en el Canvas. Ahora imagina que quieres abrir más aplicaciones, puedes pulsar el icono de la casa de arriba, o el atajo de teclado COMMAND+SHIFT+H, al hacerlo podrás abrir otras apps.

Puedes abrir otra para ver cómo posicionar varias a la vez, e incluso cerrarlas. Abre otra app ¿Sabes cómo cerrarla? muy sencillo, vas justo a la barra inferior de la ventana, y a la izquierda aparece un punto, si acercamos el ratón, vemos que se transforma en una X y si pulsamos podemos cerrar la app.

Incluso, si te has quedado hasta el final, puedes pulsar COMMAND+R para grabar un video o gif de los que estás realizando en el simulador. Para parar la grabación, solo tienes que dar al icono stop de la parte superior derecha. Este Button cuando no está grabando también sirve para realizar capturas.

Conclusión

Hoy hemos aprendido a crear nuestra primera app para las Vision Pro. Ha sido nuestro primer contacto, pero como ves no es muy diferente a lo que ya estábamos haciendo en el canal, si quieres seguir aprendiendo más novedades, no dudes en suscribirte al canal.

Y hasta aquí el video de hoy!