SwiftUI: Construindo Interfaces Modernas para iOS

Guia completo para criar interfaces modernas com SwiftUI: sintaxe declarativa, componentes, animacoes e boas praticas para iOS 18.

Guia de SwiftUI para construir interfaces modernas no iOS

SwiftUI transformou o desenvolvimento de interfaces nas plataformas Apple. Com sua sintaxe declarativa e integracao nativa, o framework permite construir aplicativos elegantes com menos linhas de codigo do que nunca. O iOS 18 traz melhorias expressivas em desempenho e novas funcionalidades.

Why SwiftUI in 2026?

O SwiftUI atingiu maturidade com o iOS 18, oferecendo renderizacao otimizada, melhor gerenciamento de memoria e integracao simplificada com UIKit. Tornou-se o padrao para novos aplicativos iOS.

Entendendo o Paradigma Declarativo

Antes de escrever codigo, vale compreender o que torna o SwiftUI diferente. Com o UIKit (o framework anterior), era necessario dizer ao iOS como construir a interface passo a passo: "crie um label, posicione aqui, mude a cor quando o usuario tocar". Esse e o paradigma imperativo.

O SwiftUI funciona de forma diferente: descreve-se o que deve ser exibido e o framework cuida do resto. E como a diferenca entre dar instrucoes de GPS curva a curva (imperativo) e simplesmente informar o destino (declarativo).

A Primeira View

No SwiftUI, cada elemento de interface e uma View. Uma View e uma struct que descreve o que deve aparecer na tela. A seguir, uma primeira tela com titulo, subtitulo e botao:

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
    }
}

A estrutura e direta: declaram-se os elementos desejados (textos, botao), empilham-se verticalmente (VStack) e aplicam-se estilos por meio de modificadores (.font(), .padding()).

Ponto-chave: No SwiftUI, nao se "criam" objetos de UI -- descreve-se a interface desejada. O SwiftUI cuida de criar, atualizar e destruir os elementos reais.

Modificadores: Transformando as Views

Modificadores sao metodos encadeados apos uma view para transforma-la. Funcionam como filtros do Instagram aplicados um apos o outro. A ordem importa, pois cada modificador cria uma nova view que envolve a anterior.

O exemplo a seguir ilustra por que a ordem e 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

A diferenca: no primeiro caso, o padding fica "dentro" do fundo. No segundo, fica "fora". Sutil, mas essencial para dominar layouts.

Organizando Interfaces com Stacks

O SwiftUI oferece tres containers principais para organizar views. Podem ser pensados como caixas que organizam seu conteudo de formas diferentes.

VStack, HStack e ZStack

  • VStack (Vertical Stack): empilha elementos de cima para baixo
  • HStack (Horizontal Stack): alinha elementos da esquerda para a direita
  • ZStack (Z-axis Stack): sobrepoe elementos um sobre o outro

O proximo exemplo constroi um cartao de perfil de usuario combinando os tres tipos de stack. O objetivo: exibir uma foto com selo de verificacao, seguida do nome e funcao do 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)
    }
}

O truque esta no Spacer(): e um elemento invisivel que ocupa todo o espaco disponivel. Sem ele, os elementos ficariam centralizados. Com ele, sao empurrados para a esquerda e o chevron permanece fixo a direita.

Xcode Tip

Usar command + click em qualquer view no Xcode abre o inspetor visual. E possivel adicionar modificadores sem digitar codigo.

Gerenciamento de Estado com @State e @Binding

O gerenciamento de estado e o coracao do SwiftUI. Estado e qualquer dado que pode mudar e que deve atualizar a interface. Quando o estado muda, o SwiftUI recalcula automaticamente as views afetadas.

@State: O Estado Local de uma View

@State e um property wrapper que diz ao SwiftUI: "observe esta variavel e atualize a view quando ela mudar". E ideal para o estado local de uma unica view.

O contador interativo a seguir permite entender o 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)
                }
            }
        }
    }
}

Ao tocar em um botao, count muda. O SwiftUI detecta essa mudanca e executa novamente body para atualizar a tela. Nao e preciso atualizar manualmente o label -- tudo e automatico.

@Binding: Compartilhando Estado entre Views

As vezes, uma view filha precisa modificar o estado da view pai. E ai que entra o @Binding: ele cria uma conexao bidirecional com um @State existente.

Um exemplo concreto: um campo de nome de usuario com validacao em tempo 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()
    }
}

A view filha recebe bindings e pode modifica-los:

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)
        }
    }
}

Quando o usuario digita no TextField, username e modificado pelo binding. A view pai enxerga essa alteracao e pode utiliza-la. Comunicacao bidirecional limpa.

Warning

Utilizar @State somente para estado simples e local. Para dados compartilhados entre varias telas ou logica complexa, o recomendado e usar @Observable (iOS 17+) ou padroes MVVM.

Pronto para mandar bem nas entrevistas de iOS?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Exibindo Listas Dinamicas

Listas estao presentes em praticamente todo aplicativo movel. O SwiftUI disponibiliza List para exibir colecoes de dados com estilo nativo do iOS (separadores, acoes de swipe, etc.).

Criando uma Lista Simples

Para exibir uma lista sao necessarias duas coisas: dados e uma forma de identifica-los. O protocolo Identifiable permite ao SwiftUI saber qual view corresponde a qual dado.

Primeiro, a definicao do modelo de dados:

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
}

Agora a lista. A ideia e iterar sobre os dados com ForEach e criar uma linha 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")
        }
    }
}

E a view para cada linha, extraida em seu proprio componente para clareza:

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)
    }
}

Extrair InterviewRow em uma struct propria torna o codigo mais legivel e reutilizavel. Essa e uma boa pratica consolidada no SwiftUI.

Acoes: Excluir e Reordenar

As listas do iOS suportam nativamente exclusao (deslizar para a esquerda) e reordenamento (arrastar e soltar). O SwiftUI simplifica isso com .onDelete e .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)
    }
}

Com apenas 4 linhas extras de codigo (.onDelete, .onMove, EditButton e as duas funcoes), obtem-se uma lista totalmente interativa. Essa e a eficiencia do SwiftUI.

Animando as Interfaces

O SwiftUI se destaca em animacoes. Diferentemente do UIKit, onde animacoes exigiam bastante codigo, aqui tudo e declarativo: descreve-se o estado final e o SwiftUI anima a transicao.

Animacoes Implicitas com withAnimation

A forma mais simples de animar e envolver uma mudanca de estado em withAnimation. O SwiftUI detecta o que muda e anima automaticamente as propriedades visuais afetadas.

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)
        }
    }
}

Ao tocar o botao, isExpanded alterna. Gracas ao withAnimation, a mudanca de tamanho do retangulo e animada com efeito de mola. Nao e necessario especificar o que animar -- o SwiftUI resolve sozinho.

Transicoes: Animando o Surgimento e Desaparecimento

Transicoes definem como uma view aparece ou desaparece. Por padrao e um fade (opacity), mas e possivel 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)
    }
}

Nesse exemplo, o cartao aparece com zoom a partir do centro (scale + opacity) e desaparece deslizando para o lado (slide). Essas microanimacoes fazem a interface parecer viva e profissional.

Performance

O SwiftUI otimiza animacoes automaticamente. Recomenda-se preferir .spring() para uma sensacao natural e evitar animacoes longas demais (> 0.5s) que frustram os usuarios.

Carregando Dados Assincronos

Em aplicativos reais, os dados frequentemente vem de uma API. O Swift Concurrency (async/await) se integra perfeitamente ao SwiftUI pelo modificador .task.

O Padrao Loading / Error / Success

Este e o padrao habitual para exibir dados de uma API. Tres estados sao gerenciados: carregando, erro e sucesso.

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
    }
}

O modificador .task e essencial: ele inicia uma tarefa assincrona quando a view aparece e a cancela automaticamente quando desaparece. Sem possibilidade de vazamentos de memoria.

Conclusao

O SwiftUI se consolidou como ferramenta fundamental para o desenvolvimento moderno de iOS. Com o iOS 18, o framework atingiu uma maturidade que o torna perfeitamente adequado para aplicativos em producao.

Pontos-Chave

  • Paradigma declarativo: descrever o que se deseja, nao como construir
  • @State e @Binding: gerenciar estado de forma reativa e propaga-lo entre views
  • Stacks: combinar VStack, HStack e ZStack para layouts flexiveis
  • List: exibir colecoes com codigo minimo e interacoes nativas
  • Animacoes: usar withAnimation para transicoes suaves automaticas
  • Async/await: carregar dados com .task e gerenciar estados de carregamento e erro

Checklist

  • Compreender a diferenca entre imperativo (UIKit) e declarativo (SwiftUI)
  • Dominar modificadores e sua ordem de aplicacao
  • Saber quando usar @State vs @Binding vs @Observable
  • Construir layouts com Stacks
  • Implementar listas com acoes (excluir, mover)
  • Animar mudancas de estado com withAnimation

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

O SwiftUI abre portas para a construcao de aplicativos elegantes em todo o ecossistema Apple. O melhor caminho para aprender e praticando: criar um projeto pessoal pequeno e experimentar cada conceito deste artigo e o metodo mais eficaz.

Tags

#swiftui
#ios
#swift
#ui
#apple

Compartilhar

Artigos relacionados