SwiftUI, appel d'une func si changement d'une propriété via @Binding

Bonjour,

Je refais l’exercice du chapitre 5 du cours SwiftUI sur Core Data, avec le gestionnaire de tâches.
Je suis bloqué sur un bête truc (bien avant l’implémentation de Core Data).
Dans ma vue principale j’ai déplacé le choix du nom de la nouvelle Task dans un composant.
La seule responsabilité du composant est de modifier un String contenant le nom de la nouvelle Task.
J’utilise donc un $ et un @Binding pour y échanger le nom de ma nouvelle Task entre ma vue principale et le composant.
Mon but est de créer la Task et de l’ajouter à la liste quand le nom de ma nouvelle Task est « setté » par le composant. Cette création est, dans un premier temps, de la responsabilité de la vue principale (avant de le faire dans le gestionnaire du jeu bien-sûr).

Dans mon composant TaskNameEditorView :

// MARK:- Binding
@Binding var taskName: String

// MARK:- Private var
@State private var localTaskName = ""

var body: some View {
    HStack {
        TextField("New task", text: $localTaskName).textFieldStyle(RoundedBorderTextFieldStyle())
        Image(systemName: "pencil.tip.crop.circle.badge.plus").onTapGesture {

            taskName = localTaskName // Je change la valeur de la propriété "bindée"

        }
    }
}

Dans ma vue principale :

@State var taskName: String = "" {
    didSet {
        // Ici je devrais créer ma Task et l'ajouter à la liste de Task, PAS OK
    }
}

var body: some View {
    VStack {
        TaskNameEditorView(taskName: $taskName)
        
        Text(taskName) // CA, OK

Ce qui marche, c’est le setting de taskName, taskName est correctement affiché lors du Text(taskName). Donc le @Binding est ok.
Ce qui ne marche pas, c’est qu’on ne passe pas par le didSet (ni un willSet). Donc je ne peux pas exécuter mon code de création de la Task ni l’ajout à la liste.
J’ai supprimé le composant et fait tout dans la vue principale, et là, ça à marché, on passe bien par le didSet. Mais ce n’est pas une solution acceptable si on veut utiliser des composants View.

Il y’a donc un truc ou une autre manière de faire, connaissez-vous lesquels ?

D’avance merci,

Pascal Micha

Bonjour,

Je ne vois pas pourquoi tu as besoin du didSet.
La déclaration suivante ne te suffit pas ?

Ta view s’affiche avec un champ vide; tu commences à taper et au moment où tu cliques sur le bouton, ta var taskName dans ta vue principale aura la valeur choisie.

Qu’est-ce qui ne va pas dans ce fonctionnement ?

Cordialement,
Nicolas

Bonjour,

Quand taskName est « setté » par le user, je veux créer un object Task (qui contient un UUID, le taskName et un Bool isDone), qui est ajouté à un tableau [Task].
Le tableau [Task] est mis à jour est affiché dans la vue principale (je n’ai pas mis ce code dans mon exemple).
Il faut donc exécuter du code plus complexe que simplement afficher le taskName (qui marche très bien comme tu le dis). Dans l’ancien monde, j’aurai agis par delegation, le composant demandant à la vue principale l’exécution d’un func pour faire cette création de Task et l’ajout dans [Task].

Pour faire « quelque chose » après le set d’une propriété, on peut utiliser didSet, mais ici sur la propriété « blindée » le didSet n’est pas appelé, je ne peux donc pas créer mon objet Task, ni l’ajouter à mon tableau [Task].
Je ne veux pas créer l’objet Task dans mon composant graphique, ce qui sera possible en lui envoyant le tableau à adapter. Cette création sera localisée dans le gameManager en dehors des View. Mais dans un premier temps je le fait dans la vue principale.

J’espère être plus clair.

Bien à toi

Pascal

Bonjour,

Je pense qu’il faut alors que tu regardes si un ObservableObject ne fera pas l’affaire.
Pas le temps d’en dire plus, je redémarre une réunion.
À plus tard sans doute !

Cordialement,
Nicolas

En complément:

    struct TaskNameEditorView: View {
    @ObservedObject var taskManager: TaskManager

    // MARK:- Private var
    @State private var localTaskName = ""

    var body: some View {
        HStack {
            TextField("New task", text: $taskManager.currentTaskName).textFieldStyle(RoundedBorderTextFieldStyle())
            Image(systemName: "pencil.tip.crop.circle.badge.plus").onTapGesture {
                taskManager.addTask(libelle: localTaskName)
                taskManager.currentTaskName = "" // Je remet à zéro la valeur de la propriété "bindée"
            }
        }
    }
}

Pour illustrer l’ObservableObject:

struct Task {
    var id = UUID()
    var taskName: String
    var isDone: Bool?
}

class TaskManager: ObservableObject {
    @Published var tasks: [Task] = []
    @Published var currentTaskName = ""
    
    func addTask(libelle: String) {
        let newTask = Task(taskName: libelle)
newTask.isDone = true
        tasks.append(newTask)
    }
}

Et la vue principale:

struct ContentView: View {
    @ObservedObject var taskManager = TaskManager()
    
    var body: some View {
        VStack {
            Text("Il y a \(taskManager.tasks.count) tâches")
            TaskNameEditorView(taskManager: taskManager)
            Text(taskManager.currentTaskName) // CA, OK
        }
    }
}

Cordialement
Nicolas

Merci Nicolas,

Bien à toi,

Pascal