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.

Categorized in:

SwiftUI,