Programación en Swift y SwiftUI para iOS Developers

Como integrar una vista en SwiftUI en UIKit

Introducción: La Realidad del Desarrollo iOS Moderno

Si eres desarrollador iOS hoy en día, es probable que vivas en una dicotomía constante. Por un lado, tienes SwiftUI, el framework declarativo, moderno y brillante que Apple promociona en cada WWDC. Por otro lado, tienes la realidad: una base de código inmensa, construida durante años en UIKit, que funciona, genera dinero y es demasiado grande para reescribirla desde cero.

La pregunta que todos nos hacemos no es “¿Debo usar SwiftUI?”, sino “¿Cómo empiezo a usar SwiftUI sin tirar a la basura años de trabajo en UIKit?”.

La buena noticia es que Apple diseñó SwiftUI con la interoperabilidad como pilar fundamental. No tienes que elegir uno u otro. Puedes (y debes) mezclarlos. Esta estrategia, conocida como “adopción incremental”, te permite escribir las nuevas pantallas en SwiftUI mientras mantienes el núcleo legacy en UIKit.

En este tutorial exhaustivo, vamos a desglosar la herramienta mágica que hace esto posible: UIHostingController. Aprenderás a presentar vistas modales, incrustar componentes pequeños dentro de view controllers existentes e incluso cómo usar celdas de SwiftUI dentro de un viejo UITableView.


Parte 1: Entendiendo al Protagonista: UIHostingController

Antes de escribir código, hablemos de arquitectura. El puente entre estos dos mundos no es magia negra, es una clase muy específica llamada UIHostingController.

¿Qué es?

UIHostingController es, en esencia, un envoltorio (wrapper). Es una clase que hereda de UIViewController, pero su contenido (su rootView) es una vista de SwiftUI.

Para UIKit, UIHostingController es solo otro View Controller más. Puedes hacerle presentpush, o añadirlo como hijo (addChild). Pero para SwiftUI, es el contenedor que gestiona el ciclo de vida y el renderizado.

// En tu código UIKit
let swiftUIView = MiVistaModerna()
let controller = UIHostingController(rootView: swiftUIView)

Una vez tienes esa variable controller, ya estás en terreno conocido de UIKit.


Parte 2: Escenario A – Presentación Modal (Navegación Simple)

El caso de uso más sencillo para empezar a integrar SwiftUI es cuando creas una pantalla completamente nueva. Imagina que en tu app UIKit necesitas añadir una pantalla de “Configuración” o un “Onboarding”. Es el candidato perfecto para SwiftUI.

Paso 1: Crea tu Vista SwiftUI

Supongamos que tenemos una vista simple de detalles de usuario.

import SwiftUI

struct UserDetailView: View {
    var username: String
    var onDismiss: () -> Void // Hablaremos de esto luego

    var body: some View {
        VStack(spacing: 20) {
            Image(systemName: "person.circle.fill")
                .resizable()
                .frame(width: 100, height: 100)
                .foregroundColor(.blue)
            
            Text("Hola, \(username)")
                .font(.title)
                .fontWeight(.bold)
            
            Text("Esta pantalla está construida 100% en SwiftUI pero vive en una app UIKit.")
                .multilineTextAlignment(.center)
                .padding()
            
            Button(action: {
                onDismiss()
            }) {
                Text("Cerrar")
                    .bold()
                    .frame(width: 200, height: 50)
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
        }
    }
}

Paso 2: Preséntalo desde tu UIViewController

En tu código legacy (por ejemplo, HomeViewController.swift), cuando el usuario toca un botón, instanciamos el hosting controller y lo presentamos.

import UIKit
import SwiftUI // No olvides importar SwiftUI en el archivo UIKit

class HomeViewController: UIViewController {

    @objc func didTapShowProfile() {
        // 1. Configuramos la vista SwiftUI
        // Pasamos un closure para manejar el dismiss desde dentro de SwiftUI si es necesario
        let detailView = UserDetailView(username: "Carlos") { [weak self] in
            self?.dismiss(animated: true, completion: nil)
        }
        
        // 2. Creamos el Hosting Controller
        let hostingController = UIHostingController(rootView: detailView)
        
        // 3. Opcional: Configurar propiedades del modal de UIKit
        hostingController.modalPresentationStyle = .pageSheet
        
        if let sheet = hostingController.sheetPresentationController {
            sheet.detents = [.medium(), .large()] // ¡Magia de iOS 15+!
        }
        
        // 4. Presentar como cualquier otro VC
        self.present(hostingController, animated: true)
    }
}

¿Por qué funciona tan bien? Porque no estás mezclando layouts. El UIHostingController ocupa toda la pantalla (o el sheet). UIKit maneja la transición, y SwiftUI maneja el contenido. Es la integración más limpia y con menos riesgo de bugs visuales.


Parte 3: Escenario B – Incrustando SwiftUI (Componentes Híbridos)

Aquí es donde las cosas se ponen interesantes (y un poco más complejas). ¿Qué pasa si no quieres una pantalla nueva, sino que quieres insertar un gráfico de SwiftUI dentro de un UIViewController existente que ya tiene otros elementos de UIKit?

Aquí debemos usar el patrón de View Controller Containment (Contención de Controladores). Es un ritual de 4 pasos que todo desarrollador iOS senior conoce bien.

El Ritual de Inclusión

Supongamos que tienes un DashboardViewController y quieres añadir un widget de clima hecho en SwiftUI en la parte superior.

  1. Añadir el hijo: addChild(hostingController)
  2. Añadir la vista: view.addSubview(hostingController.view)
  3. Configurar Constraints: Usar Auto Layout para decirle a UIKit dónde va esa vista.
  4. Confirmar la mudanza: hostingController.didMove(toParent: self)

Implementación Práctica

class DashboardViewController: UIViewController {
    
    // Un contenedor UIView que ya pusiste en el Storyboard o por código
    @IBOutlet weak var chartContainerView: UIView! 
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupSwiftUIChart()
    }
    
    private func setupSwiftUIChart() {
        // 1. Instanciar la vista SwiftUI
        let chartView = MarketChartView(data: [10, 20, 15, 30, 25])
        
        // 2. Crear el Hosting Controller
        let hostingController = UIHostingController(rootView: chartView)
        
        // 3. Añadir como hijo al VC actual
        addChild(hostingController)
        
        // 4. Añadir la vista del hosting al contenedor
        // Importante: La vista del hosting necesita configurarse para Auto Layout
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        chartContainerView.addSubview(hostingController.view)
        
        // 5. Configurar Constraints (Anclar a los bordes del contenedor)
        NSLayoutConstraint.activate([
            hostingController.view.topAnchor.constraint(equalTo: chartContainerView.topAnchor),
            hostingController.view.bottomAnchor.constraint(equalTo: chartContainerView.bottomAnchor),
            hostingController.view.leadingAnchor.constraint(equalTo: chartContainerView.leadingAnchor),
            hostingController.view.trailingAnchor.constraint(equalTo: chartContainerView.trailingAnchor)
        ])
        
        // 6. Notificar que la transición ha terminado
        hostingController.didMove(toParent: self)
        
        // Truco Pro: Hacer el fondo transparente si es necesario
        hostingController.view.backgroundColor = .clear
    }
}

Consideraciones de Rendimiento: SwiftUI es muy eficiente, pero UIHostingController no es gratuito. Evita crear cientos de estos controladores si puedes evitarlo. Para listas largas, mira la siguiente sección.


Parte 4: Escenario C – SwiftUI en UITableView y UICollectionView

Históricamente, usar SwiftUI dentro de celdas de UIKit era un dolor de cabeza de rendimiento y problemas de reciclaje de celdas. Sin embargo, desde iOS 16, Apple nos regaló UIHostingConfiguration.

Si tu proyecto soporta iOS 16+, olvida todo lo que sabías sobre meter controladores en celdas. Esta es la nueva forma de hacerlo.

Usando UIHostingConfiguration (La forma moderna)

Imagina que tienes un UITableView clásico y quieres que las celdas sean renderizadas con SwiftUI para aprovechar su facilidad de diseño.

class MyTableViewController: UITableViewController {
    
    let data = ["Elemento 1", "Elemento 2", "Elemento 3"]
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        let item = data[indexPath.row]
        
        // Magia de iOS 16+
        cell.contentConfiguration = UIHostingConfiguration {
            // Aquí dentro escribes SwiftUI puro
            HStack {
                Image(systemName: "star.fill")
                    .foregroundColor(.yellow)
                VStack(alignment: .leading) {
                    Text(item)
                        .font(.headline)
                    Text("Descripción generada en SwiftUI")
                        .font(.caption)
                        .foregroundColor(.gray)
                }
                Spacer()
                Toggle("", isOn: .constant(true))
            }
            .padding()
        }
        
        return cell
    }
}

Ventajas:

  • Maneja automáticamente el Self-Sizing. Ya no tienes que pelearte con heightForRowAt y cálculos matemáticos para saber la altura de la celda. SwiftUI calcula su tamaño y se lo dice a la tabla.
  • Es increíblemente limpio.
  • Gestiona los márgenes y separadores del sistema automáticamente.

Parte 5: Comunicación de Datos (El Reto Real)

Poner una vista en pantalla es fácil. Hacer que esa vista hable con el resto de tu app UIKit es donde muchos desarrolladores fallan.

Tenemos tres estrategias principales para pasar datos entre UIKit y SwiftUI.

1. Datos de Entrada (Inicialización)

Es la más simple. Pasas los datos en el init de la vista de SwiftUI.

  • Tipo: Unidireccional (UIKit -> SwiftUI).
  • Uso: Mostrar información estática o inicial.

2. Delegación mediante Closures

SwiftUI no usa el patrón delegate clásico de UIKit (aunque podrías forzarlo), prefiere closures.

  • Tipo: Eventos (SwiftUI -> UIKit).
  • Ejemplo: Un botón en SwiftUI que debe activar una navegación en el UINavigationController padre.
struct ActionView: View {
    var onAction: () -> Void
    
    var body: some View {
        Button("Realizar Acción") {
            onAction() // Llama al closure
        }
    }
}

// En UIKit
let view = ActionView {
    print("El botón de SwiftUI fue presionado. UIKit responde.")
    self.navigationController?.pushViewController(OtroVC(), animated: true)
}

Este patrón es poderoso porque desacopla la vista de la lógica. UIKit reacciona a los datos, no a la vista.


Parte 6: Problemas Comunes y Cómo Solucionarlos

Integrar dos frameworks de UI tan diferentes no está exento de trampas. Aquí están las más comunes que he encontrado en producción.

1. El problema del “Safe Area”

A veces, UIHostingController añade insets extraños o doble espaciado en la parte superior/inferior.

  • Solución: A menudo necesitas configurar hostingController.view.insetsLayoutMarginsFromSafeArea = false en UIKit, o usar .ignoresSafeArea() en la vista SwiftUI, dependiendo de quién quieras que controle los bordes.

2. Auto-Sizing (Contenido intrínseco)

Cuando pones un UIHostingController dentro de un UIView contenedor, a veces UIKit no sabe cuánto mide la vista de SwiftUI, resultando en una vista con altura 0.

  • Solución: Asegúrate de anclar los 4 lados (top, bottom, leading, trailing) con constraints.
  • Avanzado: Si necesitas que el UIHostingController ajuste su tamaño al contenido de SwiftUI (por ejemplo, en un StackView de UIKit), habilita:
hostingController.view.invalidateIntrinsicContentSize()
  • Y en ocasiones, necesitarás una subclase de UIHostingController que actualice su preferredContentSize.

3. Ciclo de navegación

Evita usar NavigationView o NavigationStack dentro de una vista SwiftUI que ya está dentro de un UINavigationController de UIKit. Terminarás con dos barras de navegación (una encima de la otra).

  • Regla: Si UIKit maneja la navegación, SwiftUI solo debe renderizar el contenido. Deja que UIKit haga el “Push”.

Parte 7: ¿Vale la pena el esfuerzo?

La respuesta corta es: Sí, absolutamente.

Integrar SwiftUI en UIKit no es solo una cuestión técnica, es una cuestión de estrategia de equipo y producto.

  1. Velocidad de iteración: Una vez configurado el “puente”, crear interfaces complejas en SwiftUI es 3x o 4x más rápido que usar Auto Layout y Storyboards.
  2. Preparación para el futuro: UIKit no va a desaparecer mañana, pero las nuevas APIs de Apple (Widgets, Live Activities, visionOS) son SwiftUI-first o SwiftUI-only. Tener una arquitectura híbrida te prepara para esas plataformas.
  3. Moral del equipo: Los desarrolladores quieren trabajar con tecnologías nuevas. Permitir SwiftUI en un proyecto legacy ayuda a retener talento.

Resumen de la Hoja de Ruta

  • Empieza pequeño: No reescribas tu MainViewController. Empieza con la pantalla de “Ajustes” o “Acerca de”.
  • Usa UIHostingConfiguration: Si tienes listas, es el lugar más fácil para empezar en iOS 16+.
  • Respeta el flujo de datos: Usa Combine o Closures para mantener el estado sincronizado, no intentes hackear referencias directas.

SwiftUI y UIKit pueden ser mejores amigos. Solo necesitan un buen UIHostingController que los presente.


Recursos Adicionales

  • Documentación de Apple: Interfacing with UIKit.
  • WWDC Videos: “Use SwiftUI with UIKit” (busca los de los últimos 3 años para ver la evolución).

Si tienes cualquier duda sobre este artículo, contacta conmigo y estaré encantado de ayudarte 🙂. Puedes contactar conmigo en mi perfil de X o en mi perfil de Instagram

Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Article

Cómo aprender a programar en Swift

Next Article

SwiftUI ForEach explicado con código y ejemplos

Related Posts