SwiftUI: création de sections extensibles / réductibles en mode liste



Une tâche courante dans le développement iOS est les sections extensibles / pliantes dans UITableView. Aujourd'hui, nous réalisons cette tâche en utilisant SwiftUI. En guise de petite torsion, ajoutez un triangle animé dans l'en-tête de la section et agrandissez également les cellules.

Le développement a eu lieu sur Xcode 11.2 pour macOS Catalina 10.15.1

Lancer le projet


Lancez Xcode, File - New Project - Single View App. Dans la boîte de dialogue, spécifiez le langage de développement Swift, nous formerons l'interface utilisateur à l'aide de SwiftUI.



Les données


Comme données de démonstration, nous utiliserons plusieurs expressions amusantes ailées en latin avec leur traduction en russe.

Ajoutez un nouveau fichier Swift au projet, appelez-le Data.swift et écrivez ce qui suit:

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 est un modèle d'une expression unique, à l'avenir, il deviendra le contenu de chaque cellule individuelle. Nous y stockons le texte original de l'expression, sa traduction et le signe de la cellule «développée» (par défaut, il est «minimisé»)

SectionDataModel est un modèle de chaque section distincte, ici nous stockons la "lettre" de la section, un tableau de citations commençant par cette lettre et également un signe de la section "développée" (par défaut également "réduite")

À l'avenir, nous afficherons tout cela dans une vue de liste, ce qui nécessite que les données correspondant soient conformes au protocole identifiable . Pour ce faire, nous définissons la propriété id , qui doit être unique pour chaque élément de la liste.

De plus, dans le même fichier Data.swift, nous formons nos données:

 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: "  .")])] 

Traitons de l'interface


Nous allons maintenant déterminer à quoi ressemblera l'en-tête de section et chaque cellule.

Choisissez Fichier - Nouveau - Fichier - Vue SwiftUI dans le menu. Nommez le fichier HeaderView.swift et remplacez son contenu par ce qui suit:

 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]) } } 



Maintenant à nouveau Fichier - Nouveau - Fichier - Vue SwiftUI. Nommez le fichier QuoteView.swift et remplacez son contenu par ce qui suit:

 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]) } } 



Ouvrez maintenant le fichier ContentView.swift et modifiez la structure de ContentView comme suit:

 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()) } } 

Félicitations, vous venez de remplir la liste avec des données à jour! Pour chaque élément du tableau des latinités, nous créons une section avec un en-tête basé sur HeaderView et un pied de page vide. Si la section est «développée», alors pour chaque expression dans le tableau des citations, nous formons une cellule basée sur QuoteView . Dans nos données, toutes les sections et toutes les cellules sont «réduites», donc si vous rendez le canevas visible, vous ne verrez que les en-têtes de section:



Comme vous le comprenez, l'application est maintenant complètement «morte» et encore loin de notre objectif final. Mais bientôt nous allons le réparer!

Modifier légèrement l'en-tête de section


Revenez au fichier HeaderView.swift. À l'intérieur de la structure HeaderView, immédiatement après le corps, ajoutez ceci:

 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 } } 

Cette structure renvoie un triangle équilatéral. Ajoutez maintenant notre triangle à l'en-tête. À l'intérieur du HStack , avant le premier Spacer, ajoutez ceci:

 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)) 



Modifier les données


Retour à nos données. Ouvrez Data.swift et enveloppez notre tableau de latinités dans une nouvelle classe UserData, comme ceci:

 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: "  .")])] } 

N'oubliez pas de marquer également les latinités comme @Published .

Qu'avons-nous fait?
ObservableObject est un objet spécial pour nos données qui peut être «lié» à une vue. SwiftUI "surveille" toutes les modifications susceptibles d'affecter la vue et, une fois les données modifiées, modifie la vue.

Après le "wrapping" des latinités, nous avons eu beaucoup d'erreurs, nous allons les corriger. Ouvrez HeaderView.swift et corrigez la structure de HeaderView_Previews comme suit:

 struct HeaderView_Previews: PreviewProvider { static var previews: some View { HeaderView(section: UserData().latinities[0]) } } 

Apportez maintenant des modifications similaires à QuoteView.swift :

 struct QuoteView_Previews: PreviewProvider { static var previews: some View { QuoteView(quote: UserData().latinities[0].quotes[0]) } } 

Ouvrez le fichier ContentView.swift et ajoutez-le avant la déclaration de corps

 @ObservedObject var userData = UserData() 

Ravivez le paysage


Retour au fichier ContentView.swift. À l'intérieur de la structure ContenView, immédiatement après la définition userData, ajoutez deux fonctions:

 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})! } 

Ajoutez des modificateurs onTapGesture à l'en-tête de section et à la cellule que nous créons . Vue finale du contenu du corps :

 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()) } 

Les fonctions sectionIndex et quoteUndex renvoient l'index des sections et des expressions qui leur sont transmises. Après avoir reçu ces index, nous modifions les valeurs des propriétés étendues dans notre tableau de latinités , ce qui conduit au pliage / dépliage de la section ou de l'expression.



Conclusion


Le projet terminé peut être téléchargé ici .

Quelques liens utiles:



J'espère que la publication vous sera utile!

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


All Articles