SwiftUI — el framework declarativo de Apple para construir interfaces de usuario en todas las plataformas del ecosistema, presentado en WWDC 2019. A diferencia de UIKit imperativo con su viewDidLoad y actualización manual de la pantalla, SwiftUI describe la UI como una colección de estructuras simples que conforman el protocolo View. Según Swift.org (2025), SwiftUI se utiliza en el 65% de los nuevos proyectos publicados en App Store. El framework gestiona automáticamente las actualizaciones de la interfaz mediante el mecanismo State y Data Flow — cuando los datos cambian, la View se redibuja sin llamadas manuales a reloadData.
Puntos clave
SwiftUI — el framework declarativo de UI de Apple, radicalmente diferente de UIKit. En lugar de crear controladores, vistas y gestionar manualmente su ciclo de vida, el desarrollador describe la interfaz como declaraciones: qué debe haber en la pantalla, no cómo construirlo. SwiftUI se basa en el principio de reactividad: la interfaz es una función del estado. Cuando el estado cambia, SwiftUI recalcula automáticamente el body de todas las Views dependientes y actualiza solo las partes cambiadas de la pantalla. SwiftUI está disponible en iOS 13+, iPadOS 13+, macOS 10.15+, watchOS 6+, tvOS 13+ y visionOS 1+. El código SwiftUI es multiplataforma: un mismo archivo funciona en iPhone, iPad, Mac y Apple Watch con adaptaciones mínimas de plataforma. Según Apple WWDC Session 101 (2024), SwiftUI cubre más del 90% de los patrones de UI estándar de App Store.
UIKit — framework imperativo (2008): el desarrollador crea un UIViewController, configura subviews en viewDidLoad, implementa delegate/datasource para UITableView y actualiza la pantalla mediante reloadData o setNeedsLayout. SwiftUI reemplaza los controladores con estructuras View simples, los delegados con bindings y onChange, Auto Layout con HStack/VStack/ZStack y modificadores (padding, frame, offset). UIKit requiere gestión manual de memoria mediante ARC; SwiftUI usa estructuras que no necesitan conteo de referencias. El rendimiento de SwiftUI es comparable al de UIKit: el framework usa un algoritmo de diffing para conjuntos mínimos de cambios. En IT Sectr, SwiftUI se usa para nuevos proyectos con target iOS 17+; los proyectos con soporte iOS 14–15 requieren UIKit debido a la compatibilidad limitada de SwiftUI.
En SwiftUI, la interfaz se describe mediante ViewBuilder — un result builder que transforma un conjunto de Views en una tupla o Group. Los modificadores (.padding(), .font(), .foregroundColor()) crean nuevas Views con configuraciones modificadas en lugar de mutar el objeto original. Cada modificador devuelve una nueva View, permitiendo encadenamiento. ViewBuilder admite if/else, switch, ForEach — renderizado condicional y cíclico sin controladores separados. View en SwiftUI es un value type (struct), lo que garantiza un comportamiento predecible y elimina condiciones de carrera.
View — un protocolo con un único requisito: la computed property body de tipo some View. Cada estructura que conforma View describe su parte de la pantalla en body. El tipo some View es un tipo de retorno opaco que oculta el tipo concreto de la View devuelta (apilamiento VStack, HStack, ZStack, Text, Image, etc.). El compilador de Swift infiere el tipo concreto en tiempo de compilación, preservando el rendimiento de las llamadas directas sin borrado de tipo.
import SwiftUI
struct GreetingView: View {
var name: String
var body: some View {
VStack(spacing: 12) {
Text("¡Hola, \(name)!")
.font(.largeTitle)
.foregroundColor(.primary)
Text("Bienvenido a SwiftUI")
.font(.body)
.foregroundColor(.secondary)
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(.ultraThinMaterial)
)
}
}La estructura GreetingView toma un parámetro name y muestra dos bloques de texto en una pila vertical. Los modificadores .font, .foregroundColor, .padding y .background configuran la apariencia. SwiftUI llama a body cada vez que los parámetros de entrada (name) cambian — el redibujado ocurre solo en las partes modificadas. El ejemplo usa RoundedRectangle con .ultraThinMaterial — un fondo blur nativo integrado en SwiftUI.
@State — un property wrapper que declara estado local perteneciente a una sola View. SwiftUI gestiona la memoria de State automáticamente: cuando el valor cambia, el body se redibuja, pero solo para las Views que usan ese State. State es la fuente de verdad para tipos simples (String, Int, Bool, enum). No use @State para modelos de datos complejos — use @StateObject y @ObservedObject en su lugar. State debe ser privado y almacenarse dentro de la propia View, no pasarse entre componentes.
import SwiftUI
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
Text("Recuento: \(count)")
.font(.system(size: 48, weight: .bold))
Button(action: { count += 1 }) {
Label("Incrementar", systemImage: "plus.circle")
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}El valor inicial de count = 0. Cada pulsación de botón incrementa count; SwiftUI redibuja automáticamente todo CounterView (todas las Views). En UIKit, un escenario similar requeriría IBOutlet, IBAction y actualizaciones manuales de label.text. @State garantiza que una View se redibuje solo cuando cambia un State específico — el algoritmo de diffing de SwiftUI encuentra los cambios mínimos en el árbol.
@Binding — un property wrapper que crea una conexión bidireccional entre una View y datos que la View no posee. Un Binding es una referencia a State (u otra fuente de verdad), que permite a una View hija leer y modificar el valor almacenado en el padre. Binding se denota con el prefijo $: $count pasa un Binding<Int> a la View hija. Sin Binding, una View hija no puede modificar los datos del padre — solo puede leerlos.
import SwiftUI
struct StepperControl: View {
@Binding var value: Int
let range: ClosedRange<Int>
var body: some View {
HStack {
Button(action: { if value > range.lowerBound { value -= 1 } }) {
Image(systemName: "minus.circle")
}
Text("\(value)")
.frame(minWidth: 40)
Button(action: { if value < range.upperBound { value += 1 } }) {
Image(systemName: "plus.circle")
}
}
}
}
struct ParentView: View {
@State private var quantity = 5
var body: some View {
StepperControl(value: $quantity, range: 1...10)
}
}ParentView posee el State quantity y pasa Binding mediante $quantity. StepperControl puede cambiar el valor, y quantity en el padre se sincroniza automáticamente. Un Binding no es una copia de los datos, sino un puente hacia la fuente de verdad. Use @Binding para controles personalizados, editores y componentes reutilizables que necesiten modificar los datos del padre.
@StateObject — un property wrapper para crear y poseer una instancia de una clase que conforma ObservableObject. La View crea el objeto una vez por ciclo de vida y se redibuja cuando cambian sus propiedades @Published. @ObservedObject — un wrapper similar, pero la View no posee el objeto — el objeto se crea y almacena fuera de la View (se pasa mediante un inicializador). Apple recomienda @StateObject para la fuente de verdad en una jerarquía de Views y @ObservedObject para inyección de dependencias.
import SwiftUI
import Combine
class UserSettings: ObservableObject {
@Published var username: String = "Guest"
@Published var isLoggedIn = false
}
struct ProfileView: View {
@StateObject private var settings = UserSettings()
var body: some View {
VStack {
TextField("Username", text: $settings.username)
.textFieldStyle(.roundedBorder)
Toggle("Logged In", isOn: $settings.isLoggedIn)
if settings.isLoggedIn {
Text("¡Bienvenido, \(settings.username)!")
.font(.headline)
}
}
.padding()
}
}UserSettings — un ObservableObject con dos propiedades @Published. ProfileView posee el objeto mediante @StateObject. Los cambios en username o isLoggedIn redibujan automáticamente ProfileView. @Published usa Combine Publisher para notificar a SwiftUI sobre los cambios. Para pasar settings a Views hijas, use @ObservedObject:
Apple define cuatro niveles de Data Flow en SwiftUI: @State (local, value type), @Binding (bidireccional), @StateObject/@ObservedObject (reference type con ObservableObject), @EnvironmentObject (global, inyectado a través del entorno). EnvironmentObject permite pasar datos por toda la jerarquía de Views sin pase explícito en el inicializador. Adicionalmente, @AppStorage funciona con UserDefaults, @SceneStorage con el estado de la escena, @FetchRequest con Core Data. La elección del nivel de Data Flow determina la arquitectura de la aplicación: las pantallas simples usan State/Binding, las modulares usan ObservedObject, las de gran escala usan EnvironmentObject + soluciones tipo Redux (TCA, Composable Architecture).
| Property Wrapper | Propiedad | Tipo | Cuándo usarlo |
|---|---|---|---|
| @State | Local | Value (struct, enum) | Estado simple de una sola View (contador, toggle, campo de texto) |
| @Binding | Externa | Referencia a State | View hija que modifica datos del padre |
| @StateObject | Propiedad de View | Reference (class) | Fuente de verdad para modelo de datos complejo |
| @ObservedObject | Inyección | Reference (class) | Modelo creado fuera de View (pasado mediante init) |
| @EnvironmentObject | Global | Reference (class) | Datos disponibles para toda la jerarquía (auth, tema) |
Preguntas frecuentes
@State — para value types (struct, enum, String, Int) y estado local de una sola View. SwiftUI gestiona la memoria de State automáticamente. @StateObject — para reference types (class) que conforman ObservableObject. @StateObject posee el objeto y redibuja la View cuando cambian las propiedades @Published. Para contadores simples use @State; para modelos con lógica de negocio use @StateObject.
Sí, SwiftUI se integra con UIKit mediante UIHostingController (SwiftUI dentro de UIKit) y UIViewRepresentable (UIKit dentro de SwiftUI). UIHostingController envuelve una SwiftUI View en un UIViewController. UIViewRepresentable permite usar componentes de UIKit (MKMapView, WKWebView) en SwiftUI. Este es el enfoque estándar para migrar proyectos de UIKit a SwiftUI.
ViewBuilder — un result builder (Swift 5.1) que transforma un conjunto de Views en un único valor de tipo TupleView, Group o ConditionalContent. ViewBuilder permite escribir if/else y switch imperativos dentro de un body declarativo. Sin ViewBuilder tendría que devolver AnyView o Group para cada bloque condicional. ViewBuilder es la razón por la que body no necesita comas entre Views.
Sí, SwiftUI es compatible con iOS 13+, iPadOS 13+, macOS 10.15+, watchOS 6+, tvOS 13+ y visionOS 1+. Sin embargo, algunas API solo están disponibles en versiones más recientes: por ejemplo, navigationStack (iOS 16+), Observable macro (iOS 17+). Para compatibilidad hacia atrás, use #available y adaptaciones de UIKit.
Xcode Debug View Hierarchy muestra el árbol de Views SwiftUI con modificadores y marcos. La herramienta SwiftUI Inspector (panel derecho de Xcode) permite modificar modificadores en tiempo real. self._printChanges() en body registra las razones de redibujado. Instruments con la plantilla SwiftUI traza el rendimiento de las Views e identifica redibujados excesivos.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también