# SwiftUI: Construccion de Interfaces Modernas para iOS > Guia completa para crear interfaces modernas con SwiftUI: sintaxis declarativa, componentes, animaciones y mejores practicas para iOS 18. - Published: 2026-01-05 - Updated: 2026-04-02 - Author: Anthony Fillion-Maillet - Tags: swiftui, ios, swift, ui, apple - Reading time: 12 min --- SwiftUI ha revolucionado el desarrollo de interfaces en las plataformas Apple. Con su sintaxis declarativa y su integracion nativa, este framework permite construir aplicaciones elegantes con menos lineas de codigo que nunca. iOS 18 incorpora mejoras significativas en rendimiento y nuevas capacidades. > **Why SwiftUI in 2026?** > > SwiftUI alcanza su madurez con iOS 18, ofreciendo renderizado optimizado, mejor gestion de memoria e integracion simplificada con UIKit. Se ha consolidado como el estandar para nuevas aplicaciones iOS. ## Comprendiendo el Paradigma Declarativo Antes de escribir codigo, es fundamental entender lo que diferencia a SwiftUI. Con UIKit (el framework anterior), era necesario indicarle a iOS **como** construir la interfaz paso a paso: "crea una etiqueta, colocala aqui, cambia su color cuando el usuario toque". Ese es el paradigma **imperativo**. SwiftUI funciona de manera distinta: se describe **que** se desea mostrar y el framework se encarga del resto. Es como la diferencia entre dar indicaciones paso a paso con un GPS (imperativo) y simplemente indicar el destino (declarativo). ### La Primera Vista En SwiftUI, cada elemento de interfaz es un `View`. Un View es un struct que describe lo que debe aparecer en pantalla. A continuacion, una primera pantalla con titulo, subtitulo y boton: ```swift // ContentView.swift import SwiftUI // Each screen is a struct that implements the View protocol struct ContentView: View { // The "body" property describes what the view displays // "some View" means "a type of View, but Swift figures it out automatically" var body: some View { // VStack = "Vertical Stack": stacks elements vertically // spacing: 20 = 20 points of space between each element VStack(spacing: 20) { // A simple text with style modifiers Text("Welcome to SharpSkill") .font(.largeTitle) // Large title font .fontWeight(.bold) // Bold text Text("Prepare for your iOS interviews") .font(.subheadline) // Smaller font .foregroundColor(.secondary) // Gray secondary color // Button with an action (closure) and a label Button("Get Started") { print("Button tapped!") } .buttonStyle(.borderedProminent) // Filled blue button style } .padding() // Adds margins around the VStack } } ``` La estructura es clara: se declaran los elementos deseados (textos, boton), se apilan verticalmente (VStack) y se aplican estilos mediante **modificadores** (`.font()`, `.padding()`). > **Punto clave**: En SwiftUI no se "crean" objetos de UI, sino que se **describe** la interfaz deseada. SwiftUI se encarga de crear, actualizar y destruir los elementos reales. ### Modificadores: Transformando las Vistas Los modificadores son metodos que se encadenan despues de una vista para transformarla. Funcionan de manera similar a los filtros de Instagram aplicados uno tras otro. **El orden importa**, ya que cada modificador crea una nueva vista que envuelve a la anterior. El siguiente ejemplo ilustra por que el orden es crucial: ```swift // ModifiersOrder.swift // Example 1: padding THEN background Text("SwiftUI") .padding() // 1. Adds 16pt of space around the text .background(.blue) // 2. Blue background covers text + padding .foregroundColor(.white) // Result: white text on blue rectangle with margins // Example 2: background THEN padding (reversed order!) Text("SwiftUI") .background(.blue) // 1. Tight blue background around text only .padding() // 2. Transparent padding around blue background .foregroundColor(.white) // Result: white text on small blue rectangle, surrounded by empty space ``` La diferencia radica en que, en el primer caso, el padding queda "dentro" del fondo. En el segundo, queda "fuera". Un detalle sutil pero esencial para dominar los layouts. ## Organizacion de Interfaces con Stacks SwiftUI ofrece tres contenedores principales para organizar las vistas. Se pueden pensar como cajas que ordenan su contenido de maneras diferentes. ### VStack, HStack y ZStack - **VStack** (Vertical Stack): apila elementos de arriba hacia abajo - **HStack** (Horizontal Stack): alinea elementos de izquierda a derecha - **ZStack** (Z-axis Stack): superpone elementos uno sobre otro El siguiente ejemplo construye una tarjeta de perfil de usuario combinando los tres tipos de stacks. El objetivo: mostrar una foto con insignia de verificacion, luego el nombre y rol del usuario. ```swift // ProfileCard.swift struct ProfileCard: View { var body: some View { // Main HStack: photo on left, info on right HStack(spacing: 16) { // ZStack to overlay the badge on the photo ZStack(alignment: .bottomTrailing) { // Profile image (circle) Image("avatar") .resizable() .frame(width: 60, height: 60) .clipShape(Circle()) // Green "verified" badge at bottom right Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) .background(Circle().fill(.white)) // White circle behind } // VStack to stack name and role vertically VStack(alignment: .leading, spacing: 4) { Text("Marie Dupont") .font(.headline) Text("iOS Developer") .font(.subheadline) .foregroundColor(.secondary) } // Spacer pushes everything to the left Spacer() // Chevron on right indicates it's tappable Image(systemName: "chevron.right") .foregroundColor(.gray) } .padding() .background(Color(.systemGray6)) .cornerRadius(12) } } ``` El truco esta en `Spacer()`: es un elemento invisible que ocupa todo el espacio disponible. Sin el, los elementos quedarian centrados. Con el, se empujan hacia la izquierda y el chevron permanece anclado a la derecha. > **Xcode Tip** > > Usar `command + click` sobre cualquier vista en Xcode permite acceder al inspector visual. Se pueden agregar modificadores sin necesidad de escribir codigo. ## Gestion del Estado con @State y @Binding La gestion del estado es el corazon de SwiftUI. El estado es cualquier dato que puede cambiar y que debe actualizar la interfaz. Cuando el estado cambia, SwiftUI recalcula automaticamente las vistas afectadas. ### @State: El Estado Local de una Vista `@State` es un property wrapper que le indica a SwiftUI: "observa esta variable y refresca la vista cuando cambie". Es ideal para el estado local de una vista individual. El siguiente contador interactivo permite comprender el mecanismo: ```swift // CounterView.swift struct CounterView: View { // @State creates a "source of truth" for this view // private because state shouldn't be modified from outside @State private var count = 0 var body: some View { VStack(spacing: 30) { // This Text updates automatically when count changes Text("\(count)") .font(.system(size: 72, weight: .bold)) HStack(spacing: 40) { // Decrement button Button(action: { count -= 1 // Modifies state → view refreshes }) { Image(systemName: "minus.circle.fill") .font(.largeTitle) } // Increment button Button(action: { count += 1 }) { Image(systemName: "plus.circle.fill") .font(.largeTitle) } } } } } ``` Al tocar un boton, `count` cambia. SwiftUI detecta este cambio y vuelve a ejecutar `body` para actualizar la pantalla. No es necesario actualizar manualmente la etiqueta: es automatico. ### @Binding: Compartiendo Estado entre Vistas En ocasiones, una vista hija necesita modificar el estado de su vista padre. Para eso existe `@Binding`: crea una conexion bidireccional con un `@State` existente. Un ejemplo concreto: un campo de entrada de nombre de usuario con validacion en tiempo real. ```swift // UsernameValidation.swift // Parent view: owns the state struct SignupForm: View { @State private var username = "" // Source of truth @State private var isValid = false // Validation state var body: some View { VStack(spacing: 20) { // We pass BINDINGS (with $) to the child view UsernameField(username: $username, isValid: $isValid) Button("Create Account") { // Submit the form } .disabled(!isValid) // Disabled if invalid .buttonStyle(.borderedProminent) } .padding() } } ``` La vista hija recibe bindings y puede modificarlos: ```swift // Child view: receives and modifies state via @Binding struct UsernameField: View { @Binding var username: String // Connection to parent's @State @Binding var isValid: Bool var body: some View { VStack(alignment: .leading, spacing: 8) { TextField("Username", text: $username) .textFieldStyle(.roundedBorder) .onChange(of: username) { oldValue, newValue in // Validation: at least 3 characters isValid = newValue.count >= 3 } // Visual feedback HStack { Image(systemName: isValid ? "checkmark.circle" : "xmark.circle") Text("Minimum 3 characters") } .font(.caption) .foregroundColor(isValid ? .green : .red) } } } ``` Cuando el usuario escribe en el TextField, `username` se modifica a traves del binding. La vista padre detecta este cambio y puede utilizarlo. Es comunicacion bidireccional limpia. > **Warning** > > Utilizar `@State` unicamente para estado simple y local. Para datos compartidos entre multiples pantallas o logica compleja, es preferible usar `@Observable` (iOS 17+) o patrones MVVM. ## Visualizacion de Listas Dinamicas Las listas estan presentes en practicamente toda aplicacion movil. SwiftUI proporciona `List` para mostrar colecciones de datos con estilos nativos de iOS (separadores, acciones de deslizamiento, etc.). ### Creacion de una Lista Simple Para mostrar una lista se necesitan dos cosas: datos y una forma de identificarlos. El protocolo `Identifiable` le permite a SwiftUI saber que vista corresponde a que dato. Primero, la definicion del modelo de datos: ```swift // Models/Interview.swift // Identifiable lets SwiftUI track each element struct Interview: Identifiable { let id = UUID() // Auto-generated unique identifier let technology: String let difficulty: String let questionCount: Int } ``` Ahora la lista. La idea es iterar sobre los datos con `ForEach` y crear una fila para cada elemento: ```swift // InterviewListView.swift struct InterviewListView: View { // Data to display (in reality, this would come from an API) @State private var interviews = [ Interview(technology: "iOS", difficulty: "Intermediate", questionCount: 25), Interview(technology: "Android", difficulty: "Advanced", questionCount: 30), Interview(technology: "React", difficulty: "Beginner", questionCount: 20) ] var body: some View { // NavigationStack enables the navigation bar NavigationStack { List { // ForEach iterates over each interview // Thanks to Identifiable, no need to specify id: ForEach(interviews) { interview in InterviewRow(interview: interview) } } .navigationTitle("My Interviews") } } } ``` Y la vista para cada fila, extraida en su propio componente por claridad: ```swift // InterviewRow.swift struct InterviewRow: View { let interview: Interview var body: some View { HStack { // Left column: title and subtitle VStack(alignment: .leading, spacing: 4) { Text(interview.technology) .font(.headline) Text(interview.difficulty) .font(.subheadline) .foregroundColor(.secondary) } Spacer() // Badge with question count Text("\(interview.questionCount) Q") .font(.caption) .fontWeight(.medium) .padding(.horizontal, 8) .padding(.vertical, 4) .background(Color.blue.opacity(0.1)) .cornerRadius(8) } .padding(.vertical, 4) } } ``` Extraer `InterviewRow` en su propio struct hace que el codigo sea mas legible y reutilizable. Esta es una buena practica habitual en SwiftUI. ### Acciones: Eliminar y Reordenar Las listas en iOS soportan de forma nativa la eliminacion (deslizar a la izquierda) y el reordenamiento (arrastrar y soltar). SwiftUI simplifica esto con `.onDelete` y `.onMove`: ```swift // InterviewListView.swift (enhanced version) struct InterviewListView: View { @State private var interviews = [/* ... data ... */] var body: some View { NavigationStack { List { ForEach(interviews) { interview in InterviewRow(interview: interview) } // Swipe to delete .onDelete(perform: deleteInterview) // Drag and drop to reorder .onMove(perform: moveInterview) } .navigationTitle("My Interviews") .toolbar { // "Edit" button that activates edit mode EditButton() } } } // Deletes elements at specified indices private func deleteInterview(at offsets: IndexSet) { interviews.remove(atOffsets: offsets) } // Moves elements from one position to another private func moveInterview(from source: IndexSet, to destination: Int) { interviews.move(fromOffsets: source, toOffset: destination) } } ``` Con solo 4 lineas adicionales de codigo (`.onDelete`, `.onMove`, `EditButton` y las dos funciones), se obtiene una lista completamente interactiva. Esa es la eficiencia de SwiftUI. ## Animaciones en la Interfaz SwiftUI sobresale en animaciones. A diferencia de UIKit, donde las animaciones requerian gran cantidad de codigo, aqui todo es declarativo: se describe el estado final y SwiftUI anima la transicion. ### Animaciones Implicitas con withAnimation La forma mas sencilla de animar es envolver un cambio de estado en `withAnimation`. SwiftUI detecta que cambia y anima automaticamente las propiedades visuales afectadas. ```swift // AnimatedCard.swift struct AnimatedCard: View { @State private var isExpanded = false var body: some View { VStack { RoundedRectangle(cornerRadius: 20) .fill(.blue) // Dimensions change based on state .frame( width: isExpanded ? 300 : 150, height: isExpanded ? 200 : 100 ) Button(isExpanded ? "Collapse" : "Expand") { // withAnimation animates ALL visual changes // resulting from this state change withAnimation(.spring(response: 0.5, dampingFraction: 0.7)) { isExpanded.toggle() } } .padding(.top) } } } ``` Al tocar el boton, `isExpanded` cambia de estado. Gracias a `withAnimation`, el cambio de tamano del rectangulo se anima con un efecto de resorte. No es necesario especificar que animar: SwiftUI lo determina automaticamente. ### Transiciones: Animando la Aparicion y Desaparicion Las transiciones definen como aparece o desaparece una vista. Por defecto es un desvanecimiento (`opacity`), pero se puede personalizar: ```swift // TransitionDemo.swift struct TransitionDemo: View { @State private var showDetails = false var body: some View { VStack(spacing: 20) { Button("Show Details") { withAnimation(.easeInOut(duration: 0.3)) { showDetails.toggle() } } // This view appears/disappears with a transition if showDetails { DetailCard() // Asymmetric transition: different for entry and exit .transition( .asymmetric( insertion: .scale.combined(with: .opacity), // Entry: zoom + fade removal: .slide // Exit: slide ) ) } } } } struct DetailCard: View { var body: some View { VStack(alignment: .leading, spacing: 12) { Text("Interview Details") .font(.headline) Text("25 questions • 45 minutes") .foregroundColor(.secondary) } .padding() .background(Color(.systemGray6)) .cornerRadius(16) } } ``` En este ejemplo, la tarjeta aparece con zoom desde el centro (scale + opacity) y desaparece deslizandose hacia un lado (slide). Estas microanimaciones hacen que la interfaz se sienta viva y profesional. > **Performance** > > SwiftUI optimiza las animaciones automaticamente. Se recomienda preferir `.spring()` para una sensacion natural y evitar animaciones excesivamente largas (> 0.5s) que frustran a los usuarios. ## Carga de Datos Asincronos En aplicaciones reales, los datos suelen provenir de una API. Swift Concurrency (async/await) se integra de forma fluida con SwiftUI a traves del modificador `.task`. ### El Patron Loading / Error / Success Este es el patron estandar para mostrar datos de una API. Se manejan tres estados: cargando, error y exito. ```swift // AsyncDataView.swift struct AsyncDataView: View { @State private var questions: [Question] = [] @State private var isLoading = true @State private var errorMessage: String? var body: some View { Group { if isLoading { // State: loading in progress ProgressView("Loading...") } else if let error = errorMessage { // State: error VStack(spacing: 16) { Image(systemName: "exclamationmark.triangle") .font(.largeTitle) .foregroundColor(.orange) Text(error) Button("Retry") { Task { await loadQuestions() } } } } else { // State: success, display data List(questions) { question in Text(question.title) } } } // .task runs automatically when the view appears .task { await loadQuestions() } // Pull-to-refresh .refreshable { await loadQuestions() } } private func loadQuestions() async { isLoading = true errorMessage = nil do { // Async API call questions = try await QuestionService.shared.fetchQuestions() } catch { errorMessage = "Unable to load questions" } isLoading = false } } ``` El modificador `.task` es esencial: lanza una tarea asincrona cuando la vista aparece y la cancela automaticamente cuando desaparece. No hay posibilidad de fugas de memoria. ## Conclusion SwiftUI se ha convertido en la herramienta fundamental para el desarrollo moderno de iOS. Con iOS 18, el framework ha alcanzado una madurez que lo hace perfectamente adecuado para aplicaciones en produccion. ### Puntos Clave - **Paradigma declarativo**: se describe lo que se desea, no como construirlo - **@State y @Binding**: gestion reactiva del estado y propagacion entre vistas - **Stacks**: combinar VStack, HStack y ZStack para layouts flexibles - **List**: mostrar colecciones con codigo minimo e interacciones nativas - **Animaciones**: usar `withAnimation` para transiciones suaves automaticas - **Async/await**: cargar datos con `.task` y manejar estados de carga y error ### Lista de Verificacion - Comprender la diferencia entre imperativo (UIKit) y declarativo (SwiftUI) - Dominar los modificadores y su orden de aplicacion - Saber cuando usar @State vs @Binding vs @Observable - Construir layouts con Stacks - Implementar listas con acciones (eliminar, mover) - Animar cambios de estado con withAnimation SwiftUI abre las puertas a la construccion de aplicaciones elegantes en todo el ecosistema Apple. La mejor forma de aprender es practicando: crear un pequeno proyecto personal y experimentar con cada concepto de este articulo resulta el camino mas efectivo. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/es/blog/ios/swiftui-building-modern-interfaces