Para uma coleção personalizada de coluna única no seu app para Mac, use a List do SwiftUI com um vínculo de seleção baseado em um conjunto de IDs estáveis dos itens. Personalize as linhas e deixe o controle nativo gerenciar a seleção de intervalos e a seleção com Command-clique.
Este tutorial cria uma coleção de rótulos sintéticos, de Tile 01 a Tile 12. A tarefa do leitor é selecionar itens adjacentes, adicionar ou remover itens individuais e manter a seleção compreensível ao filtrar ou inverter a ordem de exibição. Uma seção posterior explica a abordagem com AppKit para uma coleção que precisa de uma grade ou outro layout personalizado.
O que a seleção de intervalos e com Command-clique deve fazer?
O Manual do Usuário do Mac da Apple descreve a interação familiar: clique no primeiro item e, com Shift pressionado, clique no último para selecionar itens adjacentes, incluindo os que estão entre eles. Com Command pressionado, clique para selecionar itens individuais que não precisam ser adjacentes; clicar com Command em um item selecionado o remove de uma seleção múltipla. Essas são as interações básicas a preservar nesta coleção. Selecionar itens na tela do Mac.
Para a implementação inicial, use uma única lista ordenada. Assim, “entre” tem um significado inequívoco: as linhas entre dois extremos na ordem de exibição atual. Uma coleção espacial introduz outra decisão — se um intervalo segue a sequência dos dados ou uma região do layout — que merece testes separados.
Escolha o controle de acordo com o que o usuário seleciona:
| Tarefa do leitor | Ponto de partida adequado | Representação da seleção |
|---|---|---|
| Selecionar linhas personalizadas inteiras | List do SwiftUI |
Conjunto de IDs dos itens |
| Selecionar blocos inteiros em um layout personalizado | NSCollectionView do AppKit |
Caminhos de índice da seleção, mapeados para IDs do modelo |
| Selecionar caracteres dentro de um rótulo | Seleção de texto do SwiftUI | Seleção de texto fornecida pela visualização de texto |
| Selecionar intervalos em um documento de texto | Sistema de texto do AppKit | Intervalos de caracteres ou de texto |
A Apple documenta List como um contêiner de coluna única com seleção opcional, e NSCollectionView como uma coleção ordenada com layout personalizável. Esses são os pontos de partida compatíveis para seleção de itens. List, NSCollectionView.
Crie o exemplo completo em SwiftUI
Crie um projeto de app para macOS no Xcode usando SwiftUI. Defina o destino de implantação como macOS 14 ou posterior para este exemplo: o fechamento de dois parâmetros onChange usado abaixo requer macOS 14. Substitua a declaração de app gerada por este código, deixando exatamente um ponto de entrada @main no destino. onChange(of:initial:_:), App.
O código a seguir é um pequeno app completo, incluindo seus dados sintéticos e janela. Ele não usa recursos externos. Foi conferido com base nas APIs documentadas, mas não foi compilado nem executado no macOS aqui.
import Foundation
import SwiftUI
struct Tile: Identifiable {
let id: Int
let title: String
}
@main
struct CollectionSelectionDemoApp: App {
var body: some Scene {
WindowGroup {
TileCollectionView()
}
}
}
struct TileCollectionView: View {
private let tiles: [Tile] = (1...12).map {
Tile(id: $0, title: String(format: "Tile %02d", $0))
}
@State private var selectedIDs: Set<Int> = []
@State private var query = ""
@State private var reversed = false
private var visibleTiles: [Tile] {
let filtered = tiles.filter {
query.isEmpty ||
$0.title.localizedCaseInsensitiveContains(query)
}
return reversed ? Array(filtered.reversed()) : filtered
}
private var selectionDescription: String {
let names = visibleTiles
.filter { selectedIDs.contains($0.id) }
.map(\.title)
return names.isEmpty ? "None selected" : names.joined(separator: ", ")
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
TextField("Filter tiles", text: $query)
.textFieldStyle(.roundedBorder)
HStack {
Button("Reverse order") {
reversed.toggle()
}
Button("Clear selection") {
selectedIDs.removeAll()
}
.disabled(selectedIDs.isEmpty)
}
List(visibleTiles, selection: $selectedIDs) { tile in
HStack(spacing: 12) {
RoundedRectangle(cornerRadius: 4)
.fill(Color.gray.opacity(0.2))
.frame(width: 32, height: 32)
VStack(alignment: .leading) {
Text(tile.title)
Text("Sample item \(tile.id)")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
}
.padding(.vertical, 4)
.tag(tile.id)
}
Text("\(selectedIDs.count) selected")
Text(selectionDescription)
.font(.caption)
}
.padding()
.frame(minWidth: 440, minHeight: 480)
.onChange(of: query) { _, _ in
let visibleIDs = Set(visibleTiles.map(\.id))
selectedIDs.formIntersection(visibleIDs)
}
}
}
O tipo do elemento no vínculo de seleção corresponde a Tile.ID: ambos usam Int. A Apple documenta que vincular a seleção de uma lista a um conjunto do tipo de ID dos dados permite a seleção múltipla. No Mac, a seleção múltipla não exige entrar no modo de edição. Compatibilidade com seleção em listas.
A linha continua personalizável: ela contém uma forma neutra, um título e um rótulo secundário. Não há um manipulador de clique no nível da linha nem uma operação de alternância separada. Deixar a lista controlar a seleção evita que duas partes independentes do código atualizem a seleção para uma mesma interação.
Mantenha a identidade dos itens separada da ordem de exibição
O exemplo separa deliberadamente três conceitos: um array ordenado de itens, um conjunto de IDs selecionados e uma apresentação filtrada desse array. O Swift define arrays como coleções ordenadas e conjuntos como coleções de valores únicos sem uma ordem definida. Isso torna um conjunto adequado para controlar a associação, enquanto um array fornece a ordem usada na apresentação. Tipos de coleção.
Os IDs inteiros são fixos para este conjunto de dados sintéticos. Inverter o array muda a posição de um item sem alterar sua identidade. Em um modelo real, atribua a cada item um identificador estável ao criá-lo ou carregá-lo, e mantenha esse identificador nas atualizações posteriores da visualização. Evite gerar uma nova identidade cada vez que uma linha é avaliada.
O resumo da seleção segue visibleTiles e verifica a associação em selectedIDs. Essa é uma decisão de implementação baseada nessas propriedades das coleções: ela produz um resumo na ordem de exibição, sem depender da ordem de iteração de um conjunto.
A filtragem exige uma decisão explícita de produto. Este exemplo adota uma política que considera apenas os itens visíveis: sempre que o filtro muda, a seleção é intersectada com os IDs ainda visíveis. Itens ocultos perdem a seleção, e limpar o filtro não restaura a seleção anterior. Os rótulos e o array de origem permanecem inalterados.
Por exemplo, comece com Tile 02, Tile 03 e Tile 04 selecionados. Digite 04 em Filter tiles. De acordo com a política deste exemplo, apenas Tile 04 continua selecionado. Remova o filtro e os outros dois itens reaparecerão sem seleção. Esta é uma demonstração ilustrativa do código, não um resultado de execução relatado.
Outro app pode precisar manter a seleção entre filtros. Nesse caso, transfira a propriedade da seleção para o modelo, defina como as seleções ocultas aparecem na contagem e no resumo e teste a sincronização com o controle exibido. Remover apenas a interseção não estabelece uma política completa para seleções ocultas.
Quando você deve usar uma visualização de coleção do AppKit?
Use NSCollectionView quando a coleção precisar de blocos em várias colunas ou de um layout que vá além de uma única lista. A Apple separa a fonte de dados da coleção, os controladores de visualização de itens, o delegado e o objeto de layout. Isso permite personalizar a aparência e o posicionamento e, ao mesmo tempo, manter um controle de coleção responsável pela interação. NSCollectionView.
Configure a implementação com AppKit nesta ordem:
- Crie a visualização de coleção e atribua um layout moderno, como
NSCollectionViewFlowLayout. - Forneça sua fonte de dados e registre a classe ou o nib do item usado para criar as células.
- Defina
isSelectablecomotrueeallowsMultipleSelectioncomotrue. - Defina
allowsEmptySelectionde acordo com a possibilidade de a tarefa permitir que nada seja selecionado. - Leia
selectionIndexPathse, em seguida, mapeie cada caminho de índice válido para o ID correspondente no modelo. - Mostre a seleção na visualização do item usando o estado
isSelected.
A seleção múltipla fica desativada por padrão. Portanto, ativá-la é uma etapa explícita de configuração, não algo implícito ao desenhar blocos que parecem selecionáveis. A propriedade isSelected do item indica se ele está selecionado. allowsMultipleSelection, isSelected.
Trate os caminhos de índice como posições na fonte de dados atual. A Apple documenta que selectionIndexPaths é atualizado durante a seleção interativa e também pode ser atribuído programaticamente. Atribuir um caminho de índice que não identifique um item válido da fonte de dados gera uma exceção. Após reordenar ou filtrar, reconstrua as posições da seleção a partir de IDs estáveis, em vez de reutilizar posições obsoletas. selectionIndexPaths.
Respeite o limite documentado: allowsMultipleSelection permite selecionar mais de um item; a documentação não especifica um algoritmo completo de âncora de intervalo para todos os layouts personalizados ou combinações de modificadores. Verifique o comportamento de Shift-clique no layout real e nas versões do macOS compatíveis. Se a tarefa exigir um intervalo linear preciso, a implementação de coluna única acima é o ponto de partida mais claro.
Para um app SwiftUI que precisa do controle do AppKit, incorpore-o por meio de NSViewRepresentable. Crie a hierarquia do AppKit em makeNSView, sincronize as mudanças no modelo em updateNSView e use um coordenador para encaminhar eventos do delegado para o estado do SwiftUI. O SwiftUI controla o quadro e os limites da visualização representada. NSViewRepresentable.
As atualizações programáticas também precisam de um caminho explícito de sincronização. A Apple afirma que collectionView(_:shouldSelectItemsAt:) e collectionView(_:didSelectItemsAt:) são ignorados quando a seleção é definida programaticamente. Mantenha as atualizações do modelo em uma operação compartilhada, em vez de depender da execução desses retornos de chamada após cada atribuição. collectionView(_:shouldSelectItemsAt:), collectionView(_:didSelectItemsAt:).
A seleção de texto oferece o mesmo recurso?
textSelection(.enabled) do SwiftUI controla a seleção de texto dentro das visualizações. Aplicá-lo a um contêiner torna selecionável cada visualização de texto contida nele. A documentação não descreve um vínculo de seleção de itens da coleção nem um modelo compartilhado de seleção de intervalos entre cartões arbitrários. Use-o quando a tarefa for selecionar caracteres de um rótulo. textSelection(_:).
Para uma interface de documento de texto, o AppKit expõe as seleções de caracteres por meio de NSTextView.selectedRanges. Seus intervalos são ordenados, não sobrepostos e não contíguos. NSTextSelection.textRanges, do TextKit, também representa intervalos de texto em ordem lógica e está disponível desde o macOS 12. Essas APIs descrevem posições no texto, portanto traduzi-las em IDs de blocos seria uma decisão específica do app. selectedRanges, textRanges.
Decida qual interação o rótulo de uma linha deve oferecer antes de ativar a seleção de texto dentro dele. Para a tarefa deste tutorial, clicar em um rótulo deve participar da seleção da linha inteira, por isso o exemplo não configura a seleção de texto.
Verifique a interação com uma pequena matriz de seleção
Execute estes testes na própria janela do app. Os quatro primeiros verificam as convenções básicas de seleção do Mac; os demais exercitam as decisões do exemplo sobre identidade e filtragem. A interação documentada pela Apple é a referência, enquanto os rótulos sintéticos facilitam a inspeção dos itens selecionados. Selecionar itens na tela do Mac.
| Ação | Resultado esperado |
|---|---|
Clique em Tile 02 |
Um item selecionado |
Com Shift pressionado, clique em Tile 05 |
Tile 02 a Tile 05 selecionados |
Com Command pressionado, clique em Tile 08 |
Tile 08 é adicionado à seleção |
Com Command pressionado, clique no Tile 03 selecionado |
Tile 03 é removido da seleção |
Clique em Clear selection |
Seleção vazia e resumo None selected |
Selecione Tile 04 e, em seguida, inverta a ordem |
O ID selecionado ainda identifica Tile 04 |
Selecione várias linhas e filtre por 04 |
Apenas um item selecionado anteriormente que ainda esteja visível permanece selecionado |
| Limpe o filtro | As linhas ocultas voltam sem restaurar as seleções descartadas |
Verifique também a navegação por teclado depois de clicar na lista, role para que linhas selecionadas saiam da tela e voltem, e inicie um novo intervalo depois de filtrar. Para uma grade do AppKit, acrescente testes que cruzem o limite entre linhas. Se o resultado não corresponder à tarefa pretendida, reduza primeiro a interface a um controle nativo simples e, depois, reintroduza o conteúdo personalizado das linhas e os manipuladores de gestos, um de cada vez.
Deixe o controle nativo responsável pela interação, os IDs estáveis responsáveis pela associação e o array exibido responsável pela ordem. Com essas responsabilidades explícitas, o estilo personalizado das linhas, a filtragem e a ordenação podem dar suporte à mesma tarefa de seleção compreensível.
Fontes
- Selecionar itens na tela do Mac — Suporte da Apple.
- List — Documentação para Desenvolvedores da Apple.
- App — Documentação para Desenvolvedores da Apple.
- onChange(of:initial:_:) — Documentação para Desenvolvedores da Apple.
- Tipos de coleção — A linguagem de programação Swift, projeto Swift.
- NSCollectionView — Documentação para Desenvolvedores da Apple.
- allowsMultipleSelection — Documentação para Desenvolvedores da Apple.
- selectionIndexPaths — Documentação para Desenvolvedores da Apple.
- isSelected — Documentação para Desenvolvedores da Apple.
- NSViewRepresentable — Documentação para Desenvolvedores da Apple.
- collectionView(_:shouldSelectItemsAt:) — Documentação para Desenvolvedores da Apple.
- collectionView(_:didSelectItemsAt:) — Documentação para Desenvolvedores da Apple.
- textSelection(_:) — Documentação para Desenvolvedores da Apple.
- selectedRanges — Documentação para Desenvolvedores da Apple.
- textRanges — Documentação para Desenvolvedores da Apple.