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 present, push, 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.
- Añadir el hijo:
addChild(hostingController) - Añadir la vista:
view.addSubview(hostingController.view) - Configurar Constraints: Usar Auto Layout para decirle a UIKit dónde va esa vista.
- 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
heightForRowAty 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
UINavigationControllerpadre.
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 = falseen 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
UIHostingControllerajuste 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
UIHostingControllerque actualice supreferredContentSize.
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.
- 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.
- 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.
- 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
Combineo 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










