SwiftUI: Criando seções expansíveis / recolhíveis na exibição de lista



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ê!

Source: https://habr.com/ru/post/pt475270/


All Articles