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.

Guia de SwiftUI para construir interfaces modernas en iOS

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:

ContentView.swiftswift
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:

ModifiersOrder.swiftswift
// 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.

ProfileCard.swiftswift
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:

CounterView.swiftswift
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.

UsernameValidation.swiftswift
// 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.

¿Listo para aprobar tus entrevistas de iOS?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

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:

Models/Interview.swiftswift
// 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:

InterviewListView.swiftswift
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:

InterviewRow.swiftswift
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:

InterviewListView.swift (enhanced version)swift
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.

AnimatedCard.swiftswift
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:

TransitionDemo.swiftswift
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.

AsyncDataView.swiftswift
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

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

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.

Etiquetas

#swiftui
#ios
#swift
#ui
#apple

Compartir

Artículos relacionados