SwiftUI na prática: Views, modificadores e layout para apps iOS

Capítulo 2

Tempo estimado de leitura: 9 minutos

+ Exercício

Como pensar em SwiftUI: composição e árvore de Views

Em SwiftUI, a interface é construída compondo pequenas Views em estruturas maiores. Em vez de “desenhar” a tela manualmente, você descreve o que deve aparecer e como deve se comportar. O resultado é uma árvore de views: contêineres (como VStack) agrupam elementos (como Text e Button), e cada nó pode receber modificadores (como .padding() e .background()).

Uma regra prática: comece com uma estrutura de layout (V/H/ZStack), coloque dentro os componentes básicos (Text, Image, Button etc.) e, por último, aplique modificadores para espaçamento, tamanho e estilo.

Views básicas que você vai usar o tempo todo

  • Text: exibe texto e suporta estilo via modificadores.
  • Image: exibe imagens do sistema (SF Symbols) ou do Asset Catalog.
  • Button: componente interativo com ação e conteúdo (label).
  • Spacer: ocupa espaço flexível para empurrar elementos.
  • Divider: linha divisória, útil para separar seções.

Text: estilo, alinhamento e limites

O Text é simples, mas o comportamento muda bastante conforme você limita largura/altura e define alinhamento. Alguns modificadores comuns:

  • .font(...) e .fontWeight(...)
  • .foregroundStyle(...) (ou .foregroundColor em versões antigas)
  • .multilineTextAlignment(...)
  • .lineLimit(...) e .minimumScaleFactor(...)
Text("Bem-vindo ao SwiftUI")
    .font(.title)
    .fontWeight(.semibold)
    .foregroundStyle(.primary)

Text("Um texto maior que pode quebrar em várias linhas dependendo do espaço disponível.")
    .font(.body)
    .multilineTextAlignment(.leading)
    .lineLimit(3)
    .minimumScaleFactor(0.8)

Passo a passo: um cabeçalho com subtítulo responsivo

Objetivo: criar um cabeçalho que se adapte a telas menores sem “estourar” o layout.

VStack(alignment: .leading, spacing: 8) {
    Text("Minha Conta")
        .font(.largeTitle)
        .fontWeight(.bold)

    Text("Gerencie seus dados e preferências com segurança.")
        .font(.body)
        .foregroundStyle(.secondary)
        .lineLimit(2)
        .minimumScaleFactor(0.85)
}
.padding()

Image: SF Symbols, redimensionamento e recorte

Imagens em SwiftUI podem ser de duas fontes comuns: SF Symbols (Image(systemName:)) e assets do projeto (Image("nome")). Para controlar tamanho e proporção, use .resizable() com .scaledToFit() ou .scaledToFill().

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

Image(systemName: "bell.fill")
    .font(.system(size: 28))
    .foregroundStyle(.blue)

Image("profile")
    .resizable()
    .scaledToFill()
    .frame(width: 72, height: 72)
    .clipShape(Circle())

Dica importante: .resizable() muda o comportamento da imagem para aceitar redimensionamento. Sem ele, .frame pode apenas “recortar” a imagem em vez de escalá-la.

Button: ação, label e área de toque

Um Button recebe uma ação (closure) e um conteúdo visual (label). Você pode usar um texto simples ou compor uma label com ícone + texto.

Button("Continuar") {
    print("Toquei em Continuar")
}

Button {
    print("Abrir configurações")
} label: {
    HStack(spacing: 8) {
        Image(systemName: "gearshape.fill")
        Text("Configurações")
    }
}

Passo a passo: botão com estilo e área de toque consistente

Objetivo: criar um botão “cheio” com cantos arredondados e largura adaptável.

Button {
    print("Comprar")
} label: {
    Text("Comprar")
        .font(.headline)
        .frame(maxWidth: .infinity)
        .padding(.vertical, 14)
}
.background(.blue)
.foregroundStyle(.white)
.clipShape(RoundedRectangle(cornerRadius: 12))
.padding(.horizontal)

Note que .frame(maxWidth: .infinity) faz o conteúdo tentar ocupar toda a largura disponível dentro do contêiner, e o .padding(.horizontal) cria “margens” externas.

Spacer e Divider: controle de espaço e separação

Spacer() ocupa espaço flexível. Em um HStack, ele empurra elementos para as extremidades. Em um VStack, ele empurra para cima/baixo.

HStack {
    Text("Título")
        .font(.headline)
    Spacer()
    Image(systemName: "chevron.right")
        .foregroundStyle(.secondary)
}
.padding()

Divider() desenha uma linha separadora. Ele respeita o layout do contêiner (por exemplo, dentro de um VStack ele é horizontal).

VStack(spacing: 12) {
    Text("Seção A")
    Divider()
    Text("Seção B")
}
.padding()

Contêineres: VStack, HStack e ZStack

VStack: empilhamento vertical

VStack organiza views de cima para baixo. Você controla alinhamento e espaçamento:

VStack(alignment: .leading, spacing: 10) {
    Text("Nome")
    Text("Email")
    Text("Telefone")
}
.padding()

HStack: empilhamento horizontal

HStack organiza views da esquerda para a direita. Combine com Spacer para distribuir espaço:

HStack(spacing: 12) {
    Image(systemName: "person.circle.fill")
        .font(.system(size: 36))
        .foregroundStyle(.blue)

    VStack(alignment: .leading, spacing: 2) {
        Text("Ana Souza").font(.headline)
        Text("Plano Premium").font(.subheadline).foregroundStyle(.secondary)
    }

    Spacer()

    Button("Editar") {
        print("Editar")
    }
}
.padding()

ZStack: sobreposição

ZStack empilha views no eixo Z (uma em cima da outra). Útil para colocar texto sobre uma imagem, ou criar cards com fundo.

ZStack(alignment: .bottomLeading) {
    RoundedRectangle(cornerRadius: 16)
        .fill(.purple.gradient)
        .frame(height: 140)

    VStack(alignment: .leading, spacing: 6) {
        Text("Destaque")
            .font(.headline)
        Text("Oferta válida hoje")
            .font(.subheadline)
            .foregroundStyle(.white.opacity(0.9))
    }
    .foregroundStyle(.white)
    .padding()
}
.padding(.horizontal)

Modificadores: encadeamento e ordem importa

Modificadores retornam uma nova view (uma “view modificada”). Por isso, a ordem pode mudar o resultado. Dois exemplos clássicos:

Exemplo 1: padding antes/depois do background

ObjetivoCódigoResultado esperado
Fundo abraça o textoText("Olá") .background(.yellow) .padding()O fundo fica justo no texto; o padding vira margem externa.
Fundo abraça texto + paddingText("Olá") .padding() .background(.yellow)O fundo inclui o padding (um “badge” maior).

Exemplo 2: frame e background

// Fundo apenas no tamanho do texto
Text("A")
    .background(.red)
    .frame(width: 80, height: 80)

// Fundo no tamanho do frame
Text("A")
    .frame(width: 80, height: 80)
    .background(.red)

No segundo caso, o .background é aplicado depois que a view já tem 80x80, então o fundo ocupa esse tamanho.

Layout no SwiftUI: como o tamanho é calculado

Um modelo mental útil para entender layout:

  • O pai propõe um tamanho (constraints/available space).
  • O filho escolhe um tamanho dentro do que foi proposto (com base no conteúdo e modificadores).
  • O pai posiciona o filho (alinhamento e stacks).

Alguns comportamentos importantes:

  • Text e Image (sem resizable) tendem a usar seu “tamanho ideal” (intrínseco).
  • Spacer expande para ocupar o espaço restante.
  • .frame pode impor tamanho fixo (width/height) ou limites flexíveis (maxWidth/maxHeight).
  • alignment em frame controla onde o conteúdo fica dentro do frame.

Frame e alignment na prática

Text("Alinhado à esquerda")
    .frame(maxWidth: .infinity, alignment: .leading)
    .padding()
    .background(.gray.opacity(0.2))

Aqui o texto fica à esquerda dentro de uma área que ocupa toda a largura disponível.

background e overlay: camadas atrás e na frente

.background desenha atrás da view. .overlay desenha por cima. Ambos respeitam o tamanho atual da view (por isso a ordem com .padding e .frame é relevante).

Badge com overlay

Text("PRO")
    .font(.caption)
    .fontWeight(.bold)
    .padding(.horizontal, 10)
    .padding(.vertical, 6)
    .background(.black)
    .foregroundStyle(.white)
    .clipShape(Capsule())
    .overlay(
        Capsule().stroke(.white.opacity(0.6), lineWidth: 1)
    )

Preview: iterar rápido e comparar tamanhos de iPhone

O Preview permite testar layout sem rodar no simulador a cada mudança. Você pode criar variações para comparar tamanhos e modo claro/escuro.

#Preview("iPhone SE") {
    MinhaTela()
}

#Preview("iPhone 15 Pro") {
    MinhaTela()
}

#Preview("Dark") {
    MinhaTela()
        .preferredColorScheme(.dark)
}

Para comparar lado a lado, você também pode criar uma view “container” só para preview, empilhando versões em um VStack e separando com Divider().

Exercício 1: Card de perfil simples (VStack + HStack + Spacer)

Objetivo: montar um card com avatar, nome, status e um botão, responsivo em largura.

Passo a passo

  • Crie um contêiner com VStack para organizar título e conteúdo.
  • Use HStack para avatar + textos + botão.
  • Use Spacer() para empurrar o botão para a direita.
  • Aplique padding, background e overlay para aparência de card.
struct PerfilCardView: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Perfil")
                .font(.headline)

            HStack(spacing: 12) {
                Image(systemName: "person.crop.circle.fill")
                    .font(.system(size: 44))
                    .foregroundStyle(.blue)

                VStack(alignment: .leading, spacing: 2) {
                    Text("Ana Souza")
                        .font(.headline)
                    Text("Online")
                        .font(.subheadline)
                        .foregroundStyle(.secondary)
                }

                Spacer()

                Button("Editar") {
                    print("Editar")
                }
                .font(.subheadline)
            }
        }
        .padding(16)
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(.white)
        .overlay(
            RoundedRectangle(cornerRadius: 16)
                .stroke(.gray.opacity(0.25), lineWidth: 1)
        )
        .clipShape(RoundedRectangle(cornerRadius: 16))
        .padding()
        .background(.gray.opacity(0.08))
    }
}

Variações para testar no Preview

  • Troque o nome por um texto bem longo e observe se quebra ou empurra o botão.
  • Adicione .lineLimit(1) no nome e .truncationMode(.tail) para evitar quebra.
  • Teste em telas menores e maiores usando múltiplos previews.
#Preview("SE") { PerfilCardView() }
#Preview("Pro Max") { PerfilCardView() }

Exercício 2: Tela de “Login” responsiva (ZStack + overlay + frame)

Objetivo: criar uma tela com fundo em gradiente, um card central e botões, mantendo boa leitura em diferentes tamanhos.

Passo a passo

  • Use ZStack para fundo + conteúdo.
  • Crie um card com VStack e limite a largura com frame(maxWidth:).
  • Use padding e background para separar o card do fundo.
struct LoginSimplesView: View {
    var body: some View {
        ZStack {
            LinearGradient(
                colors: [.blue, .purple],
                startPoint: .topLeading,
                endPoint: .bottomTrailing
            )
            .ignoresSafeArea()

            VStack(spacing: 16) {
                Text("Entrar")
                    .font(.largeTitle)
                    .fontWeight(.bold)
                    .foregroundStyle(.white)

                VStack(alignment: .leading, spacing: 12) {
                    Text("Acesse sua conta")
                        .font(.headline)

                    Divider()

                    Button {
                        print("Entrar com Apple")
                    } label: {
                        HStack {
                            Image(systemName: "applelogo")
                            Text("Continuar com Apple")
                                .fontWeight(.semibold)
                            Spacer()
                        }
                        .frame(maxWidth: .infinity)
                        .padding(.vertical, 12)
                    }
                    .background(.black)
                    .foregroundStyle(.white)
                    .clipShape(RoundedRectangle(cornerRadius: 12))

                    Button {
                        print("Entrar com Email")
                    } label: {
                        HStack {
                            Image(systemName: "envelope.fill")
                            Text("Continuar com Email")
                                .fontWeight(.semibold)
                            Spacer()
                        }
                        .frame(maxWidth: .infinity)
                        .padding(.vertical, 12)
                    }
                    .background(.white)
                    .foregroundStyle(.black)
                    .clipShape(RoundedRectangle(cornerRadius: 12))
                    .overlay(
                        RoundedRectangle(cornerRadius: 12)
                            .stroke(.black.opacity(0.08), lineWidth: 1)
                    )
                }
                .padding(16)
                .frame(maxWidth: 420)
                .background(.thinMaterial)
                .clipShape(RoundedRectangle(cornerRadius: 16))
                .padding(.horizontal)
            }
            .padding(.vertical, 24)
        }
    }
}

Testes de responsividade no Preview

#Preview("SE") {
    LoginSimplesView()
}

#Preview("15 Pro") {
    LoginSimplesView()
}

#Preview("Dark") {
    LoginSimplesView()
        .preferredColorScheme(.dark)
}

Experimente reduzir o espaço vertical (por exemplo, simulando teclado) adicionando mais conteúdo acima e observe como o VStack se comporta. Ajuste spacing, padding e limites de lineLimit para manter a tela legível.

Exercício 3: Comparando modificadores (ordem e efeito visual)

Objetivo: criar uma “tela laboratório” para ver rapidamente como a ordem dos modificadores muda o layout.

struct ModificadoresLabView: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Group {
                Text("A")
                    .padding()
                    .background(.yellow)

                Text("B")
                    .background(.yellow)
                    .padding()
            }

            Divider()

            Group {
                Text("C")
                    .frame(width: 80, height: 80)
                    .background(.red)

                Text("D")
                    .background(.red)
                    .frame(width: 80, height: 80)
            }
        }
        .padding()
        .frame(maxWidth: .infinity, alignment: .leading)
    }
}

Use o Preview para observar: onde o fundo aparece? Qual elemento “cresce”? O que vira margem externa vs. interna? Esse tipo de laboratório acelera muito a compreensão de layout em SwiftUI.

Agora responda o exercício sobre o conteúdo:

Ao estilizar uma view em SwiftUI, por que a ordem dos modificadores pode mudar o resultado visual (por exemplo, com padding e background)?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Em SwiftUI, modificadores criam uma nova view a cada etapa. Como background e overlay usam o tamanho atual da view, mudar a ordem com padding ou frame altera se o fundo inclui o espaçamento/tamanho ou fica apenas no conteúdo.

Próximo capitúlo

iOS com SwiftUI: componentes essenciais e controles de interação

Arrow Right Icon
Capa do Ebook gratuito iOS para Iniciantes com SwiftUI: do zero ao primeiro app na App Store
14%

iOS para Iniciantes com SwiftUI: do zero ao primeiro app na App Store

Novo curso

14 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.