Introdução às vistas dinâmicas da coleção em SwiftUI

As aplicações iOS modernas exigem interfaces fluidas e adaptativas que respondem graciosamente a diferentes tamanhos de dados, orientações de dispositivos e dimensões da tela. Na UIKit, as vistas de coleção fornecem um mecanismo poderoso, mas verboso, para construir layouts semelhantes aos da grade. A SwiftUI simplifica isso dramaticamente com sua API declarativa, particularmente através de LazyVGrid[ e LazyHGrid[. Estes componentes permitem que os desenvolvedores definam grades dinâmicas e redimensionáveis com código mínimo, facilitando a construção de UIs prontas para a produção que funcionam em iPhones, iPads e até mesmo alvos de Mac Catalyst.

Um layout de coleção dinâmica adapta a sua contagem de colunas, altura da linha, espaçamento ou comportamento de dimensionamento com base na largura do recipiente disponível, no número de itens ou no próprio conteúdo. Este artigo irá guiá- lo através da construção de layouts em SwiftUI, cobrindo tudo, desde a configuração básica da grade até comportamentos adaptativos avançados, ajuste de desempenho e integração com animações. No final, você terá uma base sólida para criar visualizações de coleção flexíveis, envolventes e performantes em seus próprios aplicativos.

Componentes Principais: PreguiçosoVGrid e PreguiçosoHGrid

SwiftUI fornece dois recipientes de grade primária: LazyVGrid (rede de rolagem vertical) e LazyHGrid[ (rede de rolagem horizontal). Ambos carregam lazily seus filhos, o que significa que as vistas são criadas apenas à medida que eles se tornam visíveis na tela – uma característica crucial para o manuseio de grandes conjuntos de dados sem inchaço de memória. Estes recipientes são análogos a no UIKit, menos a placa de caldeira de fonte de dados e protocolos de delegada.

Configuração do GridItem

Para definir a estrutura de uma grade, você cria uma matriz de instâncias GridItem. Cada representa uma única coluna (em ) ou uma única linha (em ]). Você controla três propriedades chave:

  • Tamanho – Um dos três modos de dimensionamento: , , ou .
  • Espaçamento – Espaçamento horizontal entre os itens da mesma linha (para grades verticais) ou espaçamento vertical (para grades horizontais).
  • Alinhamento – Controla como os itens se alinham dentro de uma célula de grade. Raramente alterados de padrões.

A escolha do modo de dimensionamento define a flexibilidade geral do layout:

  • Resolvido – Largura explícita (ou altura) para cada coluna/linha. Útil quando você precisa de dimensionamento exato, por exemplo, um layout de três colunas de células fixas de 100 pontos.
  • Flexível – Cada item da grade pode esticar para preencher espaço disponível, com limites mínimos e máximos opcionais. Esta é a escolha mais comum para colunas dinâmicas, uniformemente espaçadas.
  • Adaptativo – A grade determina automaticamente quantos itens se encaixam por linha de coluna com base em um tamanho mínimo especificado. Esta é a maneira mais simples de fazer um layout dinâmico que responde à largura da tela.

Por exemplo, para criar uma grade que se encaixe automaticamente no maior número possível de colunas, cada uma com uma largura mínima de 100 pontos, você escreveria:

let columns = [
 GridItem(.adaptive(minimum: 100))
]

Esta linha única produz um layout que se adapta de uma coluna em um iPhone estreito para várias colunas em um iPad na paisagem – exatamente o comportamento que queremos para layouts dinâmicos.

Construindo uma Grade Dinâmica Básica

Vamos começar com uma grade simples e totalmente dinâmica usando o dimensionamento . Abaixo está uma visão completa que exibe um array de strings em uma grade responsiva:

struct AdaptiveGridView: View {
 let items = Array(1...20).map { "Item \($0)" }

 var body: some View {
 ScrollView {
 LazyVGrid(
 columns: [GridItem(.adaptive(minimum: 80), spacing: 12)],
 spacing: 12
 ) {
 ForEach(items, id: \.self) { item in
 Text(item)
 .frame(minWidth: 80, maxWidth: .infinity, minHeight: 80)
 .background(Color.blue.opacity(0.2))
 .cornerRadius(8)
 }
 }
 .padding()
 }
 }
}

Aqui, cada item é forçado a pelo menos 80 pontos de largura e altura. O item da grade calcula automaticamente quantos itens podem caber por linha, dada a largura disponível, respeitando a largura mínima. O resultado é uma disposição que reflui perfeitamente quando o dispositivo gira ou a janela é redimensionada (no iPad ou Mac).

Esta abordagem funciona bem para conteúdo simples, mas para mais controle você precisa combinar com itens, ou usar para calcular contagens de colunas personalizadas.

Usando o GeometryReader para contagem precisa de colunas dinâmicas

Enquanto lida com muitos casos, às vezes você quer definir um número exato de colunas com base na largura do recipiente – por exemplo, sempre 2 colunas em um iPhone em retrato, 3 em paisagem e 4 no iPad. A visão GeometriaReader fornece o tamanho atual de seu pai, permitindo que você calcule a contagem ótima de colunas dinamicamente.

Exemplo: Contagem de Colunas Adaptativas

A seguinte visão lê a largura disponível e calcula uma contagem de colunas com base na largura do item alvo:

struct DynamicColumnGridView: View {
 let items = Array(1...30).map { "Item \($0)" }
 let targetItemWidth: CGFloat = 120

 var columns: [GridItem] {
 let count = max(Int(geometryProxy.size.width / targetItemWidth), 1)
 return Array(repeating: GridItem(.flexible(), spacing: 12), count: count)
 }

 var body: some View {
 GeometryReader { geometry in
 ScrollView {
 let width = geometry.size.width
 let count = max(Int(width / targetItemWidth), 1)
 let columns = Array(repeating: GridItem(.flexible(), spacing: 12), count: count)

 LazyVGrid(columns: columns, spacing: 12) {
 ForEach(items, id: \.self) { item in
 Text(item)
 .frame(minWidth: 0, maxWidth: .infinity, minHeight: 80)
 .background(Color.green.opacity(0.3))
 .cornerRadius(8)
 }
 }
 .padding()
 }
 }
 }
}

[[FLT: 0]] Importante: Na prática, você deve armazenar o valor dentro de uma variável ou usá- lo dentro do mesmo fechamento. O exemplo acima usa uma variável auxiliar dentro do fechamento para clareza. Certifique- se sempre de que não envolve o corpo inteiro em desnecessariamente – ele pode quebrar o layout se usado como a visão mais superior.

Esta técnica dá- lhe o controlo completo sobre o número de colunas, mantendo os itens flexíveis para que preencham a largura uniformemente. Você também pode adicionar restrições mínimas e máximas aos itens flexíveis para evitar células extremamente esticadas ou esmagadas.

Adaptando as Alturas das Linhas Dinâmica

Os layouts dinâmicos não são apenas sobre colunas – alturas de linha também podem variar com base no conteúdo. SwiftUI não fornece uma grade direta de dimensionamento "automático" como a de UIKit ] com tamanhos estimados. No entanto, você pode alcançar resultados semelhantes usando o tamanho intrínseco do conteúdo e ou incorporando alturas orientadas por dados.

Por exemplo, você pode fazer com que cada célula da grade se auto- dimensione omitindo alturas explícitas e deixando o conteúdo interno determinar o tamanho. A SwiftUI irá calcular automaticamente a altura da célula com base no item mais alto de cada linha (para grades verticais). Mas se você precisar de alturas de linha verdadeiramente variáveis (como uma disposição de alvenaria), você precisará de uma abordagem personalizada – usando muitas vezes com guias de alinhamento personalizados ou envolvendo a grade em uma ] mas com diferentes alturas por coluna. Isso é mais avançado e além do escopo deste artigo, mas a chave de takeaway é que assume que todas as células em uma linha compartilham a mesma altura (determinada pela célula mais alta). Se o seu desenho requer alturas variáveis, considere usar uma com uma disposição de grade personalizada.

Manuseando grandes conjuntos de dados com carregamento preguiçoso

Uma das principais vantagens de [[FLT: 24]] e [[FLT: 25]] é o seu comportamento de carregamento preguiçoso. Eles só criam visualizações para os itens que estão atualmente visíveis (mais um pequeno buffer). Isso os torna eficientes para conjuntos de dados com centenas ou até milhares de itens. No entanto, existem algumas boas práticas para manter o desempenho suave:

  • Use identificadores estáveis – Sempre forneça um parâmetro único para . Para modelos de dados de estrutura que se conformam com , SwiftUI pode diff eficientemente.
  • Evite cálculos caros dentro do corpo – Mantenha as visualizações de células simples. Se você precisar de cálculos de layout complexos, precompute-os fora da grade.
  • Alavancagem – Se a sua visão celular estiver em conformidade com ou usar o modificador , SwiftUI pode pular as alterações de renderização quando os dados da célula não tiverem mudado.
  • Use dentro de um – Se você aninhar um dentro de outro recipiente preguiçoso, você pode quebrar o carregamento preguiçoso. Normalmente, a grade em si deve ser a criança direta de um (ou ser enrolado dentro de um).

Combinando layouts dinâmicos com animações

As animações no SwiftUI podem fazer com que as alterações de disposição se sintam naturais e polidas. Por exemplo, quando o número de colunas muda (devido à rotação ou ao redimensionamento da janela), você pode animar os itens da grade para as suas novas posições. A maneira mais fácil é envolver a grade em um modificador [[FLT: 36]] ou usar o modificador [[ FLT: 37]] com um valor que desencadeia a mudança.

Alterações na Coluna Animadora

Aqui está uma extensão do exemplo da coluna dinâmica anterior com uma transição animada:

struct AnimatedDynamicGridView: View {
 let items = Array(1...20).map { "Item \($0)" }
 @State private var targetItemWidth: CGFloat = 120

 var body: some View {
 GeometryReader { geometry in
 ScrollView {
 let width = geometry.size.width
 let count = max(Int(width / targetItemWidth), 1)
 let columns = Array(repeating: GridItem(.flexible(), spacing: 12), count: count)

 LazyVGrid(columns: columns, spacing: 12) {
 ForEach(items, id: \.self) { item in
 Text(item)
 .frame(minWidth: 0, maxWidth: .infinity, minHeight: 80)
 .background(Color.orange.opacity(0.4))
 .cornerRadius(8)
 .transition(.scale.combined(with: .opacity))
 }
 }
 .padding()
 .animation(.spring(response: 0.4, dampingFraction: 0.7), value: count)
 }
 }
 }
}

Ao ligar a animação ao valor , o SwiftUI animará a inserção, remoção e rearranjo dos itens quando o número de colunas mudar. Você também pode usar para transições mais avançadas entre layouts.

Exemplos práticos: Layouts Dinâmicos do Mundo Real

Vamos caminhar por dois cenários comuns que se beneficiam de layouts de visualização de coleção dinâmica.

Grelha da Galeria de Fotos

Uma galeria de fotos típica usa uma grade de células quadradas que se adapta à largura da tela. Você quer que as células sejam tão grandes quanto possível, mantendo um número fixo de colunas (por exemplo, 2 no iPhone, 3 no iPhone Plus, 4 no iPad). Usar com uma contagem de colunas condicional baseada em classes de tamanho é simples:

struct PhotoGalleryGrid: View {
 let images: [String]

 @Environment(\.horizontalSizeClass) var sizeClass

 var columns: [GridItem] {
 let count = sizeClass == .compact ? 2 : 4
 return Array(repeating: GridItem(.flexible(), spacing: 2), count: count)
 }

 var body: some View {
 ScrollView {
 LazyVGrid(columns: columns, spacing: 2) {
 ForEach(images, id: \.self) { imageName in
 Image(imageName)
 .resizable()
 .aspectRatio(1, contentMode: .fill)
 .clipped()
 }
 }
 .padding(.horizontal, 2)
 }
 }
}

Painel dinâmico com tamanhos variados de células

Para um painel, você pode ter células de larguras diferentes – por exemplo, um grande gráfico que ocupa duas colunas, estatísticas menores que levam uma coluna cada. Você pode conseguir isso combinando itens com multiplicadores explícitos usando um array personalizado . Por exemplo:

let columns: [GridItem] = [
 GridItem(.flexible(minimum: 80), spacing: 12), // small stat cell
 GridItem(.flexible(minimum: 160), spacing: 12), // wide cell
 GridItem(.flexible(minimum: 80), spacing: 12) // small stat cell
]

Então, na grade , você pode mapear diferentes tipos de dados para diferentes extensões de coluna, embrulhando itens em um recipiente que se expande para preencher a largura da grade. Isso requer coordenação cuidadosa, pois não suporta nativamente spans multi-colunas. Uma solução comum é usar modificador (disponível no iOS 16+) ou indexar manualmente a grade dividindo seus dados em seções que cada um preenche uma linha completa.

Para iOS 16 ou posterior, você pode usar o modificador em uma visualização dentro da grade para especificar quantas colunas essa visualização deve abranger:

LazyVGrid(columns: columns, spacing: 12) {
 ForEach(dashboardItems, id: \.id) { item in
 DashboardCell(item: item)
 .gridCellColumns(item.columnSpan)
 }
}

Esta é a maneira mais limpa de criar um verdadeiro layout dinâmico do painel com tamanhos de células mistas.

Considerações de desempenho para grandes conjuntos de dados

Ao lidar com milhares de itens, mesmo o carregamento preguiçoso pode levar a lags se cada célula for complexa. Aqui estão técnicas avançadas para manter suas grades dinâmicas lisas:

  • Preceder imagens e dados – Utilizar ou para carregar os dados de forma assíncrona, não no corpo da célula.
  • Use para listas extremamente grandes – A SwiftUI’s é mais otimizada para milhares de linhas com dimensionamento dinâmico. No entanto, para um layout de grade, com um layout personalizado é possível, mas mais difícil de conseguir; considere usar via se o desempenho se tornar um gargalo real.
  • Limitar recomputação do layout – Evite colocar lógica de layout dinâmico dentro do corpo da célula. Precomputar geometrias quando possível.
  • Use em toda a grade – Se você alterar completamente a estrutura da grade (por exemplo, alternando de 2 colunas para 3), atribuindo uma nova à grade forçou-a a recarregar completamente, o que pode ser mais rápido do que tentar animar uma mudança complexa.

Recursos externos para aprofundar seu conhecimento

Para explorar ainda mais as vistas dinâmicas de coleta na SwiftUI, consulte estes recursos oficiais e comunitários:

  1. Documentação Preguiçosa da AppleVGrid – A referência oficial para todos os parâmetros e comportamentos. Documentação do Desenvolvedor da Apple: LazyVGrid
  2. Guia de início rápido para GridItem – Abrange todas as três estratégias de dimensionamento com exemplos interativos. Documentação do desenvolvedor da Apple: GridItem
  3. SwiftUI por Exemplo – Layouts de grade – Um tutorial prático de Hacking com Swift que cobre grades adaptativas e flexíveis. Hacking com Swift: Layouts de grade
  4. SwiftUI Lab: Lazy Grids – Mergulhar profundamente em desempenho e personalizações. SwiftUI Lab: Lazy Grids

Resumo das Melhores Práticas

Criar um layout dinâmico de visualização de coleção na SwiftUI requer flexibilidade de equilíbrio com o desempenho. Aqui está uma lista de verificação rápida para grades prontas para produção:

  • Comece com para a contagem de colunas dinâmicas mais simples; use apenas quando você precisar de controle preciso.
  • Fornecer sempre valores únicos ou estar em conformidade com .
  • Use com um valor que muda (como a contagem de colunas) para suavizar transições.
  • Para tamanhos de células mistas, prefira no iOS 16+ ou crie um layout personalizado linha por linha.
  • Perfilize sua grade com Instrumentos para verificar o carregamento preguiçoso está funcionando e as células não estão renderizando conteúdo offscreen.
  • Teste em vários tamanhos e orientações de dispositivos – layouts dinâmicos que parecem ótimos em uma tela podem quebrar em outra.

Dominar visualizações dinâmicas de coleção permite que você crie aplicativos que se sintam nativos e responsivos em todos os dispositivos Apple. Com as ferramentas declarativas da SwiftUI, você pode alcançar o que uma vez exigiu dezenas de linhas de código UIKit em apenas um punhado de componentes expressivos e reutilizáveis.