SwiftUI: Erweiterbare / reduzierbare Abschnitte in der Listenansicht erstellen



Eine häufige Aufgabe in der iOS-Entwicklung sind die erweiterbaren / faltbaren Abschnitte in einer UITableView. Heute realisieren wir diese Aufgabe mit SwiftUI. Als kleine Wendung fügen wir ein animiertes Dreieck in die Abschnittsüberschrift ein und lassen die Zellen ebenfalls expandieren.

Die Entwicklung erfolgte auf Xcode 11.2 für macOS Catalina 10.15.1

Starten Sie das Projekt


Starten Sie Xcode, File - New Project - Single View App. Geben Sie im Dialogfeld die Swift-Entwicklungssprache an. Die Benutzeroberfläche wird mit SwiftUI erstellt.



Daten


Als Demonstrationsdaten werden wir einige lustige geflügelte Ausdrücke in Latein mit ihrer Übersetzung ins Russische verwenden.

Fügen Sie dem Projekt eine neue Swift-Datei hinzu, nennen Sie sie Data.swift und schreiben Sie dort Folgendes:

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 ist ein Modell eines einzelnen Ausdrucks und wird in Zukunft zum Inhalt jeder einzelnen Zelle. Darin speichern wir den Originaltext des Ausdrucks, seine Übersetzung und das Vorzeichen der "erweiterten" Zelle (standardmäßig ist es "minimiert")

SectionDataModel ist ein Modell jedes einzelnen Abschnitts. Hier speichern wir den "Buchstaben" des Abschnitts, eine Reihe von Anführungszeichen, die mit diesem Buchstaben beginnen, sowie ein Zeichen für den "erweiterten" Abschnitt (standardmäßig ist er auch "reduziert").

Zukünftig werden wir all dies in einer Listenansicht anzeigen, die voraussetzt, dass die Daten dafür mit dem Identifizierbaren Protokoll übereinstimmen . Dazu definieren wir die Eigenschaft id , die für jedes Element in der Liste eindeutig sein muss.

Außerdem bilden wir in derselben Datei Data.swift unsere Daten:

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

Beschäftigen wir uns mit der Schnittstelle


Nun legen wir fest, wie die Abschnittsüberschrift und jede Zelle aussehen.

Wählen Sie im Menü Datei - Neu - Datei - SwiftUI-Ansicht. Nennen Sie die Datei HeaderView.swift und ersetzen Sie ihren Inhalt durch Folgendes:

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



Jetzt wieder File - New - File - SwiftUI View. Benennen Sie die Datei QuoteView.swift und ersetzen Sie ihren Inhalt durch Folgendes:

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



Öffnen Sie nun die Datei ContentView.swift und ändern Sie die Struktur der ContentView wie folgt:

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

Herzlichen Glückwunsch, Sie haben gerade die Liste mit aktuellen Daten gefüllt! Für jedes Element des Latinities- Arrays erstellen wir einen Abschnitt mit einer auf der HeaderView basierenden Kopfzeile und einer leeren Fußzeile. Wenn der Abschnitt „erweitert“ ist, bilden wir für jeden Ausdruck im Anführungszeichen-Array eine Zelle, die auf QuoteView basiert. In unseren Daten sind alle Abschnitte und alle Zellen „reduziert“. Wenn Sie also Canvas sichtbar machen, werden nur Abschnittsüberschriften angezeigt:



Wie Sie verstehen, ist die Anwendung jetzt vollständig "tot" und noch weit von unserem endgültigen Ziel entfernt. Aber bald werden wir es reparieren!

Ändern Sie die Abschnittsüberschrift leicht


Zurück zur HeaderView.swift-Datei. Fügen Sie in der HeaderView-Struktur unmittelbar nach dem Text Folgendes hinzu:

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

Diese Struktur liefert ein gleichseitiges Dreieck. Fügen Sie nun unser Dreieck in die Kopfzeile ein. Fügen Sie im HStack vor dem ersten Spacer Folgendes hinzu:

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



Daten ändern


Zurück zu unseren Daten. Öffnen Sie Data.swift und binden Sie unser Latinities- Array in eine neue UserData-Klasse ein.

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

Denken Sie daran, Latinities auch als @Published zu kennzeichnen .

Was haben wir getan
ObservableObject ist ein spezielles Objekt für unsere Daten, das an bestimmte Ansichten „gebunden“ werden kann. SwiftUI "überwacht" alle Änderungen, die sich auf die Ansicht auswirken können, und ändert nach Änderung der Daten die Ansicht.

Nach dem "Wrapping" von Latinities hatten wir viele Fehler, wir werden sie korrigieren. Öffnen Sie HeaderView.swift und korrigieren Sie die Struktur von HeaderView_Previews wie folgt:

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

Nehmen Sie nun ähnliche Änderungen an QuoteView.swift vor :

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

Öffnen Sie die Datei ContentView.swift und fügen Sie diese vor der Body- Deklaration hinzu

 @ObservedObject var userData = UserData() 

Beleben Sie die Landschaft wieder


Zurück zur Datei ContentView.swift. Fügen Sie in der ContenView-Struktur unmittelbar nach der userData-Definition zwei Funktionen hinzu:

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

Fügen Sie der Abschnittsüberschrift und der Zelle, die Sie erstellen, die Modifizierer onTapGesture hinzu . Endgültige Ansicht des Körperinhalts :

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

Die Funktionen sectionIndex und quoteUndex geben den Index der Abschnitte und Ausdrücke zurück, die an sie übergeben wurden. Nachdem wir diese Indizes erhalten haben, ändern wir die Werte der erweiterten Eigenschaften in unserem Latinitätsfeld , was zur Faltung / Entfaltung des Abschnitts oder Ausdrucks führt.



Fazit


Das fertige Projekt kann hier heruntergeladen werden .

Einige nützliche Links:



Ich hoffe, dass Ihnen die Veröffentlichung weiterhilft!

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


All Articles