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.
