Buttons in Listenzellen einbinden
Herzlich Willkommen zu dieser neuen Artikelreihe hier auf dem Let’s Code-Blog! Mit SwiftUI in der Praxis möchte ich euch einige Best Practices zu SwiftUI mit auf den Weg geben, die mir bei der täglichen Arbeit mit Apples neuem UI-Framework begegnet sind. Dabei handelt es sich sowohl um diverse Tipps und Tricks als auch Möglichkeiten zur Erkennung und Behebung diverser Fehler (denn so schön SwiftUI auch ist, von Perfektion ist es leider noch ein ziemliches Stück entfernt).
In diesem ersten Artikel möchte ich euch eine Möglichkeit präsentieren, wie ihr Buttons (und entsprechend zugehörige Actions) in Zellen einer SwiftUI-Liste implementieren und nutzen könnt. Der Standardweg greift hier nämlich in SwiftUI nicht.
Das Problem
Gegeben ist das folgende Listing. Darin erfolgt die Erstellung zweier Views. ContentView
stellt eine Liste mit insgesamt zehn Zellen dar. Sie ist Teil eines Navigation-Stacks, was dazu führt, dass bei Auswahl einer Zelle eine Detail-View gepusht wird, die nochmals den Text der gewählten Zelle darstellt.
Die Zellen wiederum sind innerhalb von CellView
definiert. Sie bestehen aus zwei Informationen: Einem anzuzeigenden Text und einem Binding (presentsAlert
). Das Binding ist wichtig im Zusammenspiel mit einem Button, der sich auf jeder Zelle befindet. Wählt man diesen Button aus, soll ein Alert eingeblendet werden, der einen statischen Text ausgibt (ich weiß, das mutet nicht sehr originell an, dafür erlaubt es uns dieser überschaubare Code aber, dass wir uns auf das eigentliche Problem konzentrieren können). Das Binding steuert somit die Sichtbarkeit des Alerts. Die zugehörige Source of Truth für das Binding ist als State
-Property in ContentView
definiert und steht per Default auf false
.
struct ContentView: View {
@State private var presentsAlert = false
var body: some View {
NavigationView {
List(0 ..< 10) { value in
NavigationLink(destination: Text("Zeile \(value)")) {
CellView(text: "Zeile \(value)", presentsAlert: self.$presentsAlert)
}
}
.navigationBarTitle(Text("Liste"))
}
}
}
struct CellView: View {
let text: String
@Binding var presentsAlert: Bool
var body: some View {
HStack {
Text(text)
Spacer()
Button(action: {
self.presentsAlert.toggle()
}) {
Image(systemName: "info.circle.fill")
}
}
.alert(isPresented: $presentsAlert, content: {
Alert(title: Text("It works!"))
})
}
}
Führt man diesen Code aus, wird die Liste korrekt erzeugt und angezeigt. Auch lassen sich die einzelnen Zellen auswählen, um so zur Detailansicht zu gelangen. Doch ein Betätigen des Buttons ist nicht möglich. Stattdessen führt diese Aktion ebenfalls zur Auswahl der Zelle und so zu einem Push auf dem Navigation-Stack, nicht aber zur gewünschten Anzeige des Alerts.
Die Lösung
Um einen Button in einer Zellenliste einzubinden, braucht es einen kleinen Kniff. Wir dürfen nämlich nicht direkt eine Button
-Instanz innerhalb einer Zelle verwenden. Stattdessen rufen wir auf der View, die als Schaltfläche dienen soll (in unserem Fall das Image), den onTapGesture(_:)
-Modifier auf.
Innerhalb des Closure-Parameters dieses Modifiers führt man dann alle Befehle auf, die bei Betätigen des Buttons durchgeführt werden sollen (es handelt sich dabei also um jene Implementierung, die bisher im action
-Parameter der Button
-Instanz von CellView
steckte).
Da die Basis für unseren Button das Info-Image ist, muss der Code wie folgt aktualisiert werden (tatsächlich betrifft die Änderung nur die CellView
-Structure):
struct CellView: View {
let text: String
@Binding var presentsAlert: Bool
var body: some View {
HStack {
Text(text)
Spacer()
Image(systemName: "info.circle.fill")
.onTapGesture {
self.presentsAlert.toggle()
}
}
.alert(isPresented: $presentsAlert, content: {
Alert(title: Text("It works!"))
})
}
}
Die Button
-Instanz verschwindet, und stattdessen weist man dem Image den onTapGesture
-Modifier zu. Die darin implementierte Logik bleibt dieselbe. Diese kleine Änderung sorgt dafür, dass sich der Button auswählen lässt und der Alert erscheint. Gleichzeitig lassen sich die Zellen selbst ebenfalls wie gewohnt auswählen, um die zugehörige Detail-View einzublenden.
Fazit
Zwar finde ich es persönlich etwas schade, dass sich Button
-Instanzen nicht direkt als auswählbare Views innerhalb einer Listenzelle verwenden lassen. Dafür gibt es mit dem onTapGesture
-Modifier einen praktischen und simplen Workaround, der die Funktionsweise von Button
nachbildet und Zellen so deutlich mehr technische Möglichkeiten zur Verfügung stellt.
Euer Thomas
Schreibe einen Kommentar