SwiftUI: So implementieren Sie einen benutzerdefinierten Init mit @ Bindungsvariablen

95

Ich arbeite an einem Geldeingabebildschirm und muss eine benutzerdefinierte implementieren init , um eine Statusvariable basierend auf dem initialisierten Betrag festzulegen.

Ich dachte, das würde funktionieren, aber ich erhalte den folgenden Compilerfehler:

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
    }
    ...
}
keegan3d
quelle

Antworten:

152

Argh! Du warst so nah. Das ist wie man es macht. Sie haben ein Dollarzeichen (Beta 3) oder einen Unterstrich (Beta 4) verpasst und entweder sich selbst vor Ihrer Betragseigenschaft oder einen Wert nach dem Betragsparameter. Alle diese Optionen funktionieren:

Sie werden sehen, dass ich das @Statein includeDecimal entfernt habe. Überprüfen Sie die Erklärung am Ende.

Dies nutzt die Eigenschaft (stellen Sie sich davor):

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
    }
}

oder mit .value after (aber ohne self, da Sie den übergebenen Parameter verwenden, nicht die Eigenschaft der Struktur):

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
    }
}

Dies ist das gleiche, aber wir verwenden unterschiedliche Namen für den Parameter (withAmount) und die Eigenschaft (Betrag), sodass Sie deutlich sehen, wann Sie jeden verwenden.

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
    }
}

Beachten Sie, dass der Wert für die Eigenschaft dank des Eigenschafts-Wrappers (@Binding) nicht erforderlich ist, der die Accessoren erstellt, die den Wert unnötig machen. Mit dem Parameter gibt es so etwas jedoch nicht und Sie müssen es explizit tun. Wenn Sie mehr über Property Wrapper erfahren möchten, lesen Sie die WWDC-Sitzung 415 - Modern Swift API Design und springen Sie zu 23:12.

Wie Sie festgestellt haben, wird beim Ändern der Variablen @State im Initilizer der folgende Fehler ausgegeben: Thread 1: Schwerwiegender Fehler: Zugriff auf den Status außerhalb von View.body . Um dies zu vermeiden, sollten Sie entweder den @State entfernen. Was Sinn macht, weil includeDecimal keine Quelle der Wahrheit ist. Sein Wert wird aus der Menge abgeleitet. Durch Entfernen von @State wird jedoch includeDecimalnicht aktualisiert, wenn sich der Betrag ändert. Um dies zu erreichen, ist es am besten, Ihr includeDecimal als berechnete Eigenschaft zu definieren, damit sein Wert von der Quelle der Wahrheit (Menge) abgeleitet wird. Auf diese Weise funktioniert auch includeDecimal, wenn sich der Betrag ändert. Wenn Ihre Ansicht von includeDecimal abhängt, sollte sie aktualisiert werden, wenn sie sich ändert:

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 { ... }
}

Wie von rob mayoff angegeben , können Sie auch $$varName(Beta 3) oder _varName(Beta4) verwenden, um eine Statusvariable zu initialisieren:

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

// Beta 4:
_includeDecimal = State(initialValue: (round(amount.value) - amount.value) != 0)
Kon Tiki
quelle
Vielen Dank! Das hat sehr geholfen! Ich bin ein Laufzeitfehler immer auf self.includeDecimal = round(self.amount)-self.amount > 0derThread 1: Fatal error: Accessing State<Bool> outside View.body
keegan3d
Nun, es macht irgendwie Sinn. @StateVariablen sollten eine Quelle der Wahrheit darstellen. Aber in Ihrem Fall duplizieren Sie diese Wahrheit, weil der Wert von includeDecimal aus Ihrer tatsächlichen Wahrheitsquelle abgeleitet werden kann, nämlich der Menge. Sie haben zwei Möglichkeiten: 1. Sie machen includeDecimal zu einer privaten Variable (kein @State) oder noch besser. 2. Sie machen es zu einer berechneten Eigenschaft, von der der Wert abgeleitet wird amount. Auf diese Weise includeDecimalfunktioniert es auch , wenn sich der Betrag ändert . Sie sollten es so deklarieren: private var includeDecimal: Bool { return round(amount)-amount > 0 }und entfernen Sie dieself.includeDecimal = ...
kontiki
Hmm, ich muss in der Lage sein zu ändern, includeDecimalalso brauche ich es als @ State Variable in der Ansicht. Ich möchte es wirklich nur mit einem Startwert initialisieren
keegan3d
1
@ Let's_Create Ich habe sie nur einmal vollständig gesehen, aber Gott sei Dank für den Vorwärts- Button ;-)
kontiki
1
Wirklich schöne Erklärung, danke. Ich denke jetzt wurde das .valuedurch ersetzt .wrappedValue, wäre schön die Antwort zu aktualisieren und Beta-Optionen zu entfernen.
user1046037
11

Sie sagten (in einem Kommentar): "Ich muss mich ändern können. includeDecimal" Was bedeutet es zu ändern includeDecimal? Sie möchten es anscheinend basierend darauf initialisieren, ob amount(zur Initialisierungszeit) eine Ganzzahl ist. In Ordnung. So was passiert , wenn includeDecimalist falseund dann später Sie es ändern true? Wirst du irgendwie zwingen amount, dann nicht ganzzahlig zu sein?

Wie auch immer, Sie können nicht ändern includeDecimalin init. Sie können es jedoch folgendermaßen initialisieren init:

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

(Beachten Sie, dass an einem gewissen Punkt die $$includeDecimalSyntax geändert werden _includeDecimal.)

Rob Mayoff
quelle
Oh, großartig, das doppelte Geld war das, was ich für diesen Teil brauchte!
Keegan3d
2

Da es Mitte 2020 ist, lassen Sie uns noch einmal zusammenfassen:

Bezüglich @Binding amount

  1. _amountwird nur zur Verwendung während der Initialisierung empfohlen. Und niemals self.$amount = xxxwährend der Initialisierung so zuweisen

  2. amount.wrappedValueund amount.projectedValuewerden nicht häufig verwendet, aber Sie können Fälle wie sehen

@Environment(\.presentationMode) var presentationMode

self.presentationMode.wrappedValue.dismiss()
  1. Ein häufiger Anwendungsfall von @binding ist:
@Binding var showFavorited: Bool

Toggle(isOn: $showFavorited) {
    Text("Change filter")
}
LiangWang
quelle