Cómo migrar código de UIKit a SwiftUI
Cómo migrar código de UIKit a SwiftUI

¿Cómo migrar de UIKit a SwiftUI en Xcode?

Migrar de UIKit a SwiftUI es muy simple. Apple está haciendo todo lo posible para que esta migración sea simple para los ios developers. Es por eso que usando UIHostingController o UIHostingConfiguration podemos añadir vistas de SwiftUI a nuestra app en UIKit

SwiftBeta

Tabla de contenido


👇 SÍGUEME PARA APRENDER SWIFTUI, SWIFT, XCODE, etc 👇
Aprende a migrar una app creada en UIKit a SwiftUI
Aprende a migrar una app creada en UIKit a SwiftUI

Hoy en SwiftBeta vamos a aprender a cómo usar SwiftUI en un proyecto que está construido 100% con el framework UIKit. Apple es muy consciente de que hay un altísimo porcentaje de apps dentro del App Store que aún usan UIKit, y es por eso que poco a poco nos van proporcionando opciones muy viables para que empecemos a ir migrando nuestra app, y así ir añadiendo SwiftUI poco a poco en nuestro proyecto.

Pues bien, en el video de hoy vamos a partir de un proyecto creado en UIKit y vamos a:

  • Sustituir una vista creada por UIKit a SwiftUI
  • Crear en SwiftUI las celdas de nuestro UITableView

Vas a ver lo sencillo y limpio que es introducir SwiftUI a nuestro proyecto en UIKit.

Pero antes, quieres que siga creando este tipo de contenido puedes suscribirte al canal, de esta manera seguiré subiendo contenido cada semana

La app que vamos a ver usa la API de Rick & Morty para extraer un listado de personajes. El listado de personajes se muestra dentro de una UITableView y por cada celda de nuestra UITableView aparece la información de un personaje. Si pulsamos una celda, navegamos directamente a una nueva pantalla para visualizar la información concreta del personaje.

Hoy vamos a partir de un proyecto ya creado en UIKit, y es este de aquí:

Listado de ficheros de nuestra app de ejemplo en Xcode
Listado de ficheros de nuestra app de ejemplo en Xcode

Vamos a compilar y vamos a probarlo. Este proyecto tiene una vista principal con un UITableView, donde después de hacer una petición HTTP, mostramos el listado de personajes obtenidos, cada personaje obtenido se representa visualmente dentro de una celda de nuestro UITableView.

Migración vista UIKit a SwiftUI

¿Qué vista de UIKit vamos a pasar a SwiftUI? pues va a ser la del CharacterDetailView, la vista a la que estamos navegando para mostrar la información de un personaje de la serie Rick & Morty. Vamos a empezar.Pulsamos COMMAND+N y creamos un fichero nuevo y escogemos que sea de tipo SwiftUI.  Llamamos a nuestro nuevo fichero CharacterDetailSwiftUIView (de esta manera lo diferenciamos de la vista ya creada en UIKit)

Podemos abrir el doble editor e ir replicando nuestra nueva vista en SwiftUI. Primero vamos a apilar 3 Text dentro de un VStack:

import SwiftUI

struct CharacterDetailSwiftUIView: View {
    var model: CharacterModel
    
    init(model: CharacterModel) {
        self.model = model
    }
    var body: some View {
        VStack {
            Text(model.name)
            Text(model.status)
            Text(model.species)
        }
    }
}
Empezamos a crear nuestra vista en SwiftUI

Fíjate que hemos creado un init, dentro de este init espera un parámetro que será el modelo que usará la vista para mostrar toda la información.

Para ir viendo los resultados en la preview del canvas que tenemos a la derecha, puedes crear un modelo con información inventada y pasárselo a la preview:

struct CharacterDetailSwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        let model: CharacterModel = .init(name: "SwiftBeta", status: "iOS Dev", species: "Human", image: "https://rickandmortyapi.com/api/character/avatar/1.jpeg")
        return CharacterDetailSwiftUIView(model: model)
    }
}
Creamos datos para rellenar nuestra view en el canvas

De esta manera puedes ir viendo como queda la vista final.

A continuación vamos a añadir la Image que mostrará la imagen de nuestro personaje:

struct CharacterDetailSwiftUIView: View {
    var model: CharacterModel
    
    init(model: CharacterModel) {
        self.model = model
    }
    var body: some View {
        HStack(alignment: .top) {
            AsyncImage(url: URL(string: model.image)) { image in
                image
                    .resizable()
                    .frame(width: 200, height: 200)
                    .scaledToFit()
                    .padding(12)
            } placeholder: {
                ProgressView()
            }
            VStack(alignment: .leading) {
                Text(model.name)
                    .padding(.bottom, 6)
                Text(model.status)
                    .padding(.bottom, 6)
                Text(model.species)
                    .padding(.bottom, 6)
            }
            .padding(.vertical, 12)
            Spacer()
        }
        Spacer()
    }
}
Añadimos un AsyncImage para mostrar la imagen del personaje

Si miramos la preview, vemos que nuestra nueva vista creada con SwiftUI es idéntica a la que teníamos en UIKit. Ahora, solo nos falta sustituir la vista de UIKit por la nueva que acabamos de crear en SwiftUI. Y para hacerlo necesitamos usar una clase llamada UIHostingController, esta clase espera una rootView como parámetro, y este parámetro será una instancia de nuestra vista creada en SwiftUI.

Nos vamos al fichero CharacterListViewController y dentro del método viewDidLoad vamos a modificar esta linea:

let characterModel = dataSource.characters[index]
let viewController = CharacterDetailViewController(characterDetailModel: characterModel)
Sustituimos la instancia de nuestro CharacterDetailViewController

por:

let characterModel = dataSource.characters[index]
let viewController = UIHostingController(rootView: CharacterDetailSwiftUIView(model: characterModel))
Creamos instancia de UIHostingController con la rootView en SwiftUI

La única diferencia es que en lugar de crear una instancia de CharactrerDetailViewController, estamos creando un wrapper sobre la vista creada en SwiftUI creando una instancia de UIHostingController.

Vamos a compilar y vamos a ver como se muestra nuestra nueva vista. Perfecto! funciona exactamente igual que la vista creada con UIKit.

Celdas del UITableView creadas con SwiftUI

Ahora, vamos a configurar la vista que hemos creado por cada celda de nuestra UITableView. Vas a ver que es muy sencillo, ya que en Xcode 14 podemos usar una clase llamada UIHostingConfiguration, y ¿cómo se usa? muy sencillo, vámonos al fichero que se encarga de configurar celda por celda, ese fichero es ListOfCharactersTableViewDataSource.

Una vez estamos en esta clase, nos vamos al método:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
Método que se encarga de mostrar las celdas de un UITableView

Aquí vamos a usar la clase que te comentaba hace un momento, UIHostingConfiguration. Comentamos la configuración de la celda, y añadimos el siguiente código:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "CharacterListCellView", for: indexPath) as! CharacterListCellView
    
    let character = characters[indexPath.row]
    cell.contentConfiguration = UIHostingConfiguration {
        Text("Suscríbete a SwiftBeta!")
    }
    
    return cell
}
Creamos nuestra primera celda con código en SwiftUI
Importa el framework SwiftUI, si no lo haces tendrás un error del compilador

Y a continuación vamos a compilar. Por cada celda estamos mostrando la vista en SwiftUI que hemos creado directamente dentro del closure. Esto es tremendo. Vamos a replicar la misma vista que estábamos usando en UIKit.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CharacterListCellView", for: indexPath) as! CharacterListCellView
        
        let model = characters[indexPath.row]
        cell.contentConfiguration = UIHostingConfiguration {
            HStack(alignment: .top) {
                AsyncImage(url: URL(string: model.image)) { image in
                    image
                        .resizable()
                        .frame(width: 80, height: 80)
                        .scaledToFit()
                        .padding(12)
                } placeholder: {
                    ProgressView()
                }
                VStack(alignment: .leading) {
                    Text(model.name)
                        .padding(.bottom, 4)
                    Text(model.status)
                        .padding(.bottom, 4)
                    Text(model.species)
                        .padding(.bottom, 4)
                }
                .padding(.vertical, 12)
                Spacer()
            }
        }
        
        return cell
    }
Creamos la vista de la celda en SwiftUI

Podríamos extraer la vista que hemos creado dentro de UIHostingConfiguration a un fichero aparte. Es decir, pulsamos COMMAND+N y creamos una nueva vista llamada CharacterListSwiftUICellView:

struct CharacterListSwiftUICellView: View {
    var model: CharacterModel
    
    init(model: CharacterModel) {
        self.model = model
    }
    
    var body: some View {
        HStack(alignment: .top) {
            AsyncImage(url: URL(string: model.image)) { image in
                image
                    .resizable()
                    .frame(width: 80, height: 80)
                    .scaledToFit()
                    .padding(12)
            } placeholder: {
                ProgressView()
            }
            VStack(alignment: .leading) {
                Text(model.name)
                    .padding(.bottom, 4)
                Text(model.status)
                    .padding(.bottom, 4)
                Text(model.species)
                    .padding(.bottom, 4)
            }
            .padding(.vertical, 12)
            Spacer()
        }
    }
    
    struct CharacterListSwiftUICellView_Previews: PreviewProvider {
        static var previews: some View {
            let model: CharacterModel = .init(name: "SwiftBeta", status: "iOS Dev", species: "Human", image: "https://rickandmortyapi.com/api/character/avatar/1.jpeg")
            CharacterListSwiftUICellView(model: model)
        }
    }
}
Extraemos la vista en SwiftUI en un nuevo fichero

Y podríamos simplificar nuestro código usando la clase que acabamos de crear:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "CharacterListCellView", for: indexPath) as! CharacterListCellView
    
    let model = characters[indexPath.row]
    cell.contentConfiguration = UIHostingConfiguration {
        CharacterListSwiftUICellView(model: model)
    }
    
    return cell
}
Sustituimos el código por la clase de la nueva vista

Si compilamos observamos exactamente el mismo resultado, solo que hemos simplificado nuestro código y lo hemos abstraido al sitio que corresponde, en una vista en SwiftUI llamada CharacterListSwiftUICellView

Conlusión

Hoy en SwiftBeta hemos aprendido a cómo ir migrando poco a poco a SwiftUI una aplicación que esta creada en UIKit. Hemos creado una vista en SwiftUI y la hemos usado dentro de UIKit con una clase llamada UIHostingController. Y a continuación hemos usado una vista en SwiftUI para mostrar las celdas de un UITableView. Hasta aquí el video de hoy