web-dev-qa-db-fra.com

SwiftUI: Comment implémenter un init personnalisé avec des variables @Binding

Je travaille sur un écran de saisie d'argent et j'ai besoin d'implémenter un init personnalisé pour définir une variable d'état basée sur le montant initialisé.

Je pensais que cela fonctionnerait, mais j'obtiens une erreur de compilation de:

Cannot assign value of type 'Binding<Double>' to type 'Double'

struct AmountView : View {
    @Binding var amount: Double

    @State var includeDecimal = false

    init(amount: Binding<Double>) {
        self.amount = amount
        self.includeDecimal = round(amount)-amount > 0
    }
    ...
}
46
keegan3d

Argh! Tu étais si proche. Voilà comment vous le faites. Vous avez manqué un signe dollar (bêta 3) ou un trait de soulignement (bêta 4), et vous vous êtes placé devant votre propriété de montant, ou .valeur après le paramètre de montant. Toutes ces options fonctionnent:

Vous verrez que j'ai supprimé le @Statein includeDecimal, vérifiez l'explication à la fin.

Ceci utilise la propriété (mettez-vous devant elle):

struct AmountView : View {
    @Binding var amount: Double

    private var includeDecimal = false

    init(amount: Binding<Double>) {

        // self.$amount = amount // beta 3
        self._amount = amount // beta 4

        self.includeDecimal = round(self.amount)-self.amount > 0
    }
}

ou en utilisant .value après (mais sans self, car vous utilisez le paramètre passé, pas la propriété de la structure):

struct AmountView : View {
    @Binding var amount: Double

    private var includeDecimal = false

    init(amount: Binding<Double>) {
        // self.$amount = amount // beta 3
        self._amount = amount // beta 4

        self.includeDecimal = round(amount.value)-amount.value > 0
    }
}

C'est la même chose, mais nous utilisons des noms différents pour le paramètre (withAmount) et la propriété (amount), donc vous voyez clairement quand vous les utilisez. =

struct AmountView : View {
    @Binding var amount: Double

    private var includeDecimal = false

    init(withAmount: Binding<Double>) {
        // self.$amount = withAmount // beta 3
        self._amount = withAmount // beta 4

        self.includeDecimal = round(self.amount)-self.amount > 0
    }
}
struct AmountView : View {
    @Binding var amount: Double

    private var includeDecimal = false

    init(withAmount: Binding<Double>) {
        // self.$amount = withAmount // beta 3
        self._amount = withAmount // beta 4

        self.includeDecimal = round(withAmount.value)-withAmount.value > 0
    }
}

Notez que .value n'est pas nécessaire avec la propriété, grâce au wrapper de propriété (@Binding), qui crée les accesseurs qui rendent la .value inutile. Cependant, avec le paramètre, il n'y a rien de tel et vous devez le faire explicitement. Si vous souhaitez en savoir plus sur les wrappers de propriétés, consultez WWDC session 415 - Modern Swift API Design et passez à 23:12.

Comme vous l'avez découvert, la modification de la variable @State à partir de l'initiateur générera l'erreur suivante: Thread 1: erreur fatale: accès à l'état en dehors de View.body. Pour l'éviter, vous devez soit supprimer @State. Ce qui est logique car includeDecimal n'est pas une source de vérité. Sa valeur est dérivée du montant. Cependant, en supprimant @State, includeDecimal ne sera pas mis à jour si le montant change. Pour y parvenir, la meilleure option consiste à définir votre includeDecimal en tant que propriété calculée, de sorte que sa valeur soit dérivée de la source de vérité (quantité). De cette façon, chaque fois que le montant change, votre includeDecimal le fait aussi. Si votre vue dépend de includeDecimal, elle devrait se mettre à jour lorsqu'elle change:

struct AmountView : View {
    @Binding var amount: Double

    private var includeDecimal: Bool {
        return round(amount)-amount > 0
    }

    init(withAmount: Binding<Double>) {
        self.$amount = withAmount
    }

    var body: some View { ... }
}

Comme indiqué par rob mayoff, vous pouvez également utiliser $$varName (beta 3) ou _varName (beta4) pour initialiser une variable d'état:

// Beta 3:
$$includeDecimal = State(initialValue: (round(amount.value) - amount.value) != 0)

// Beta 4:
_includeDecimal = State(initialValue: (round(amount.value) - amount.value) != 0)
63
kontiki

Vous avez dit (dans un commentaire) "Je dois pouvoir changer includeDecimal". Que signifie changer includeDecimal? Vous voulez apparemment l'initialiser selon que amount (au moment de l'initialisation) est un entier. D'accord. Que se passe-t-il donc si includeDecimal est false et que vous le changez ensuite en true? Allez-vous forcer d'une manière ou d'une autre amount à être non entier?

Quoi qu'il en soit, vous ne pouvez pas modifier includeDecimal dans init. Mais vous pouvez l'initialiser dans init, comme ceci:

struct ContentView : View {
    @Binding var amount: Double

    init(amount: Binding<Double>) {
        $amount = amount
        $$includeDecimal = State(initialValue: (round(amount.value) - amount.value) != 0)
    }

    @State private var includeDecimal: Bool

(Notez que à un moment donné le $$includeDecimal la syntaxe sera remplacée par _includeDecimal.)

4
rob mayoff