La concurrence en Swift est devenue une composante essentielle de ma pile de développement. J’exploite la puissance des nouvelles fonctionnalités de concurrence en Swift, telles que async/await
et les groupes de tâches, presque partout. Le type Button
de SwiftUI ne prend pas en charge la concurrence en Swift nativement, mais il est suffisamment flexible pour nous permettre de créer un type de bouton prenant en charge la concurrence en Swift.
Exemple de base :
Pour commencer, examinons un exemple simple où un bouton déclenche une tâche asynchrone :
struct ExampleView: View {
@State private var counter = 0
var body: some View {
VStack {
Text("\(counter)")
Button {
Task {
await heavyUpdate()
}
} label: {
Text("Increment")
}
}
}
private func heavyUpdate() async {
do {
print("update started")
try await Task.sleep(nanoseconds: 3 * 1_000_000_000)
counter += 1
print("update finished")
} catch {
print("update cancelled")
}
}
}
Dans cet exemple, la fonction heavyUpdate
simule une tâche de longue durée en suspendant l’exécution pendant quelques secondes. Le bouton déclenche cette tâche en utilisant l’initialisateur Task
, garantissant ainsi que l’interface utilisateur reste réactive.
Désactivation du bouton pendant la tâche asynchrone
Pour éviter plusieurs invocations de la tâche, vous pouvez désactiver le bouton pendant l’exécution de la tâche :
struct ExampleView: View {
@State private var isRunning = false
@State private var counter = 0
var body: some View {
VStack {
Text("\(counter)")
Button {
isRunning = true
Task {
await heavyUpdate()
isRunning = false
}
} label: {
Text("Increment")
}
.disabled(isRunning)
}
}
private func heavyUpdate() async {
do {
print("update started")
try await Task.sleep(nanoseconds: 3 * 1_000_000_000)
counter += 1
print("update finished")
} catch {
print("update cancelled")
}
}
}
Ici, la variable d’état isRunning
est utilisée pour suivre le statut de la tâche, désactivant le bouton lorsque la tâche est en cours d’exécution.
Création d’un bouton asynchrone personnalisé
Pour rendre cette logique réutilisable, vous pouvez l’encapsuler dans une vue AsyncButton
personnalisée :
struct AsyncButton<Label: View>: View {
let action: () async -> Void
let label: Label
@State private var isRunning = false
init(action: @escaping () async -> Void, @ViewBuilder label: () -> Label) {
self.action = action
self.label = label()
}
var body: some View {
Button {
isRunning = true
Task {
await action()
isRunning = false
}
} label: {
ZStack {
label.opacity(isRunning ? 0 : 1)
if isRunning {
ProgressView()
}
}
}
.disabled(isRunning)
}
}
Ce composant AsyncButton
gère l’action asynchrone et affiche une ProgressView
pendant que la tâche est en cours. Le bouton est désactivé pendant l’exécution de la tâche pour éviter les invocations multiples.
Exemple d’utilisation
Vous pouvez utiliser le AsyncButton
dans vos vues de la manière suivante :
struct AsyncButtonExampleView: View {
@State private var counter = 0
var body: some View {
VStack {
Text("\(counter)")
AsyncButton {
do {
try await Task.sleep(nanoseconds: 3 * 1_000_000_000)
counter += 1
} catch {
print("Task cancelled")
}
} label: {
Text("Increment")
}
.controlSize(.large)
.buttonStyle(.borderedProminent)
}
}
}
Dans cet exemple, le AsyncButton
incrémente le compteur après un délai, montrant comment intégrer les opérations asynchrones de manière fluide dans SwiftUI.
Conclusion
Créer un bouton asynchrone en SwiftUI implique l’utilisation des mots-clés async
et await
pour gérer les tâches non-bloquantes. En encapsulant cette logique dans un composant personnalisé AsyncButton
, vous pouvez créer des éléments d’interface utilisateur réutilisables et réactifs qui gèrent les actions asynchrones de manière efficace. Cette approche améliore non seulement la lisibilité du code, mais elle renforce également l’expérience utilisateur en fournissant un retour visuel pendant les opérations longues.