
Uma tarefa comum no desenvolvimento do iOS são as seções expansíveis / dobráveis em um UITableView. Hoje estamos realizando esta tarefa usando o SwiftUI. Como um pequeno toque, adicione um triângulo animado no cabeçalho da seção e faça com que as células também se expandam.
O desenvolvimento ocorreu no Xcode 11.2 para o macOS Catalina 10.15.1
Iniciar o projeto
Inicie o Xcode, Arquivo - Novo projeto - Aplicativo de exibição única. Na caixa de diálogo, especifique a linguagem de desenvolvimento Swift, formaremos a interface do usuário usando o SwiftUI.

Dados
Como dados de demonstração, usaremos várias expressões aladas engraçadas em latim com sua tradução para o russo.
Adicione um novo arquivo Swift ao projeto, chame-o de
Data.swift e escreva o seguinte:
struct QuoteDataModel : Identifiable { var id: String { return latin } var latin : String var russian : String var expanded = false } struct SectionDataModel : Identifiable { var id: Character { return letter } var letter : Character var quotes : [QuoteDataModel] var expanded = false }
QuoteDataModel é um modelo de uma única expressão; no futuro, ele se tornará o conteúdo de cada célula individual. Nele, armazenamos o texto original da expressão, sua tradução e o sinal da célula "expandida" (por padrão, é "minimizada")
SectionDataModel é um modelo de cada seção separada; aqui armazenamos a "letra" da seção, uma matriz de citações começando com esta letra e também um sinal da seção "expandida" (por padrão, também é "minimizada")
No futuro, exibiremos tudo isso em uma exibição de lista, o que exige que os dados estejam em conformidade com o protocolo
identificável . Para isso, definimos a propriedade
id , que deve ser exclusiva para cada item da lista.
Além disso, no mesmo arquivo Data.swift, formamos nossos dados:
var latinities : [SectionDataModel] = [ SectionDataModel(letter: "C", quotes: [ QuoteDataModel(latin: "Calvitium non est vitium, sed prudentiae indicium.", russian: " , ."), QuoteDataModel(latin: "Conjecturalem artem esse medicinam.", russian: " ."), QuoteDataModel(latin: "Crede firmiter et pecca fortiter!", russian: " !")]), SectionDataModel(letter: "H", quotes: [ QuoteDataModel(latin: "Homo sine religione sicut equus sine freno.", russian: " ."), QuoteDataModel(latin: "Habet et musca splenem.", russian: " .")]), SectionDataModel(letter: "M", quotes: [ QuoteDataModel(latin: "Malum est mulier, sed necessarium malum.", russian: " , ."), QuoteDataModel(latin: "Mulierem ornat silentium.", russian: " .")])]
Vamos lidar com a interface
Agora vamos determinar como será o cabeçalho da seção e cada célula.
Escolha Arquivo - Novo - Arquivo - SwiftUI View no menu. Nomeie o arquivo
HeaderView.swift e substitua seu conteúdo pelo seguinte:
import SwiftUI struct HeaderView : View { var section : SectionDataModel var body: some View { HStack() { Spacer() Text(String(section.letter)) .font(.largeTitle) .foregroundColor(Color.black) Spacer() } .background(Color.yellow) } } struct HeaderView_Previews: PreviewProvider { static var previews: some View { HeaderView(section: latinities[0]) } }

Agora novamente Arquivo - Novo - Arquivo - SwiftUI View. Nomeie o arquivo
QuoteView.swift e substitua seu conteúdo pelo seguinte:
import SwiftUI struct QuoteView: View { var quote : QuoteDataModel var body: some View { VStack(alignment: .leading, spacing: 5) { Text(quote.latin) .font(.title) if quote.expanded { Group() { Divider() Text(quote.russian).font(.body) }.transition(.move(edge: .top)).animation(.default) } } } } struct QuoteView_Previews: PreviewProvider { static var previews: some View { QuoteView(quote: latinities[0].quotes[0]) } }

Agora abra o arquivo ContentView.swift e altere a estrutura do ContentView da seguinte maneira:
struct ContentView: View { var body: some View { List { ForEach(latinities) { section in Section(header: HeaderView(section: section), footer: EmptyView()) { if section.expanded { ForEach(section.quotes) { quote in QuoteView(quote: quote) } } } } } .listStyle(GroupedListStyle()) } }
Parabéns, você acabou de preencher a lista com dados atualizados! Para cada elemento da matriz de
latinidades, criamos uma seção com um cabeçalho baseado no
HeaderView e com um rodapé vazio. Se a seção for "expandida", para cada expressão na matriz de aspas formamos uma célula com base no
QuoteView . Em nossos dados, todas as seções e todas as células são "recolhidas"; portanto, se você tornar o Canvas visível, verá apenas os cabeçalhos das seções:

Como você entende, agora o aplicativo está completamente "morto" e ainda longe do nosso objetivo final. Mas em breve vamos consertar!
Modifique ligeiramente o cabeçalho da seção
Voltar para o arquivo HeaderView.swift. Dentro da estrutura do HeaderView, imediatamente após o corpo, adicione isto:
struct Triangle : Shape { func path(in rect: CGRect) -> Path { var path = Path() path.move(to: CGPoint(x: 0, y: 0)) path.addLine(to: CGPoint(x: 0, y: rect.height - 1)) path.addLine(to: CGPoint(x: sqrt(3)*(rect.height)/2, y: rect.height/2)) path.closeSubpath() return path } }
Essa estrutura retorna um triângulo equilátero. Agora adicione nosso triângulo ao cabeçalho. Dentro do
HStack , antes do primeiro
espaçador, adicione este:
Triangle() .fill(Color.black) .overlay( Triangle() .stroke(Color.red, lineWidth: 5) ) .frame(width : 50, height : 50) .padding() .rotationEffect(.degrees(section.expanded ? 90 : 0), anchor: .init(x: 0.5, y: 0.5)).animation(.default))

Modificar dados
Voltar aos nossos dados. Abra Data.swift e agrupe nossa matriz de
latinidades em uma nova classe UserData, assim:
class UserData : ObservableObject { @Published var latinities : [SectionDataModel] = [ SectionDataModel(letter: "C", quotes: [ QuoteDataModel(latin: "Calvitium non est vitium, sed prudentiae indicium.", russian: " , ."), QuoteDataModel(latin: "Conjecturalem artem esse medicinam.", russian: " ."), QuoteDataModel(latin: "Crede firmiter et pecca fortiter!", russian: " !")]), SectionDataModel(letter: "H", quotes: [ QuoteDataModel(latin: "Homo sine religione sicut equus sine freno.", russian: " ."), QuoteDataModel(latin: "Habet et musca splenem.", russian: " .")]), SectionDataModel(letter: "M", quotes: [ QuoteDataModel(latin: "Malum est mulier, sed necessarium malum.", russian: " , ."), QuoteDataModel(latin: "Mulierem ornat silentium.", russian: " .")])] }
Lembre-se de marcar também as latinidades como
@Published .
O que fizemos?ObservableObject é um objeto especial para nossos dados que pode ser "vinculado" a alguma Visualização. O SwiftUI "monitora" todas as alterações que possam afetar a Visualização e, após a alteração dos dados, altera a Visualização.
Após o "empacotamento" das latinidades, tivemos muitos erros, vamos corrigi-los. Abra
HeaderView.swift e corrija a estrutura do
HeaderView_Previews da seguinte maneira:
struct HeaderView_Previews: PreviewProvider { static var previews: some View { HeaderView(section: UserData().latinities[0]) } }
Agora faça alterações semelhantes ao
QuoteView.swift :
struct QuoteView_Previews: PreviewProvider { static var previews: some View { QuoteView(quote: UserData().latinities[0].quotes[0]) } }
Abra o arquivo ContentView.swift e adicione-o antes da declaração do
corpo @ObservedObject var userData = UserData()
Reviver a paisagem
Voltar para o arquivo ContentView.swift. Dentro da estrutura do ContenView, imediatamente após a definição de userData, adicione duas funções:
func sectionIndex(section : SectionDataModel) -> Int { userData.latinities.firstIndex(where: {$0.letter == section.letter})! } func quoteIndex(section : Int, quote : QuoteDataModel) -> Int { return userData.latinities[section].quotes.firstIndex(where: {$0.latin == quote.latin})! }
Adicione modificadores
onTapGesture ao cabeçalho e à célula da seção que estamos
criando . Visualização final do conteúdo do
corpo :
var body: some View { List { ForEach(userData.latinities) { section in Section(header: HeaderView(section: section) .onTapGesture { self.userData.latinities[self.sectionIndex(section: section)].expanded.toggle() }, footer: EmptyView()) { if section.expanded { ForEach(section.quotes) { quote in QuoteView(quote: quote) .onTapGesture { let sectionIndex = self.sectionIndex(section: section) let quoteIndex = self.quoteIndex(section: sectionIndex, quote: quote) self.userData.latinities[sectionIndex].quotes[quoteIndex].expanded.toggle() } } } } } } .listStyle(GroupedListStyle()) }
As
funções sectionIndex e
quoteUndex retornam o índice das seções e expressões passadas para elas. Após receber esses índices,
alteramos os valores das propriedades
expandidas em nossa matriz de
latinidades , o que leva ao dobramento / desdobramento da seção ou expressão.

Conclusão
O projeto finalizado pode ser
baixado aqui .
Alguns links úteis:
Espero que a publicação seja útil para você!