SwiftUI: Cómo implementar un inicio personalizado con @Binding variables

95

Estoy trabajando en una pantalla de entrada de dinero y necesito implementar una costumbre initpara establecer una variable de estado basada en la cantidad inicializada.

Pensé que esto funcionaría, pero recibo un error del compilador 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
    }
    ...
}
keegan3d
fuente

Respuestas:

152

¡Argh! Estabas tan cerca. Así es como lo haces. Se perdió un signo de dólar (beta 3) o un guión bajo (beta 4), y usted mismo frente a su propiedad de monto, o .value después del parámetro de monto. Todas estas opciones funcionan:

Verá que @Stateeliminé el includeDecimal, verifique la explicación al final.

Esto está usando la propiedad (poner self delante de ella):

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

o usando .value después (pero sin self, porque está usando el parámetro pasado, no la propiedad de la estructura):

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

Esto es lo mismo, pero usamos diferentes nombres para el parámetro (withAmount) y la propiedad (amount), por lo que puede ver claramente cuándo está usando cada uno.

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

Tenga en cuenta que .value no es necesario con la propiedad, gracias al contenedor de propiedades (@Binding), que crea los descriptores de acceso que hacen innecesario el .value. Sin embargo, con el parámetro, no existe tal cosa y hay que hacerlo explícitamente. Si desea obtener más información sobre los envoltorios de propiedades, consulte la sesión 415 de la WWDC: Diseño de API Swift moderno y pase a las 23:12.

Como descubrió, la modificación de la variable @State desde el initilizer arrojará el siguiente error: Subproceso 1: Error fatal: Accediendo al Estado fuera de View.body . Para evitarlo, debe eliminar el @State. Lo cual tiene sentido porque includeDecimal no es una fuente de verdad. Su valor se deriva de la cantidad. Sin embargo, al eliminar @State, includeDecimalno se actualizará si cambia la cantidad. Para lograrlo, la mejor opción es definir su includeDecimal como una propiedad calculada, de modo que su valor se derive de la fuente de verdad (cantidad). De esta manera, siempre que cambie la cantidad, su includeDecimal también lo hará. Si su vista depende de includeDecimal, debería actualizarse cuando cambie:

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

Como lo indica rob mayoff , también puede usar $$varName(beta 3) o _varName(beta4) para inicializar una variable de estado:

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

// Beta 4:
_includeDecimal = State(initialValue: (round(amount.value) - amount.value) != 0)
Kon Tiki
fuente
¡Gracias! ¡Esto ayudó mucho! Recibo un error de tiempo de ejecución el self.includeDecimal = round(self.amount)-self.amount > 0deThread 1: Fatal error: Accessing State<Bool> outside View.body
keegan3d
Bueno, tiene sentido. @Statelas variables deben representar una fuente de verdad. Pero en su caso, está duplicando esa verdad, porque el valor de includeDecimal puede derivarse de su fuente real de verdad que es la cantidad. Tiene dos opciones: 1. Hace que includeDecimal sea una variable privada (sin @State), o incluso mejor 2. Lo convierte en una propiedad calculada que deriva su valor amount. De esta manera, si la cantidad cambia, includeDecimaltambién lo hace. Debería declararlo así: private var includeDecimal: Bool { return round(amount)-amount > 0 }y eliminar elself.includeDecimal = ...
kontiki
Hmm, necesito poder cambiar, includeDecimalasí que lo necesito como una variable @State en la vista. Realmente solo quiero inicializarlo con un valor inicial
keegan3d
1
@ Let's_Create los observaba totalmente sola vez, pero gracias a Dios por la reenvía botón ;-)
kontiki
1
Muy buena explicación, gracias. Creo que ahora que .valueha sido reemplazado por .wrappedValue, sería bueno actualizar la respuesta y eliminar las opciones beta.
user1046037
11

Dijiste (en un comentario) "Necesito poder cambiar includeDecimal". ¿Qué significa cambiar includeDecimal? Aparentemente, desea inicializarlo en función de si amount(en el momento de la inicialización) es un número entero. Bueno. Entonces, ¿qué sucede si includeDecimales falsey luego lo cambia a true? ¿Vas a forzar de alguna manera amounta ser no entero?

De todos modos, no puedes modificar includeDecimalen init. Pero puedes inicializarlo init, así:

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

(Tenga en cuenta que en algún momento$$includeDecimal se cambiará la sintaxis _includeDecimal).

Rob Mayoff
fuente
¡Oh, increíble, el doble $$ era lo que necesitaba para esta parte!
keegan3d
2

Ya que es mediados de 2020, recapitulemos:

En cuanto a @Binding amount

  1. _amountsolo se recomienda su uso durante la inicialización. Y nunca asigne de esta manera self.$amount = xxxdurante la inicialización

  2. amount.wrappedValuey amount.projectedValueno se utilizan con frecuencia, pero puede ver casos como

@Environment(\.presentationMode) var presentationMode

self.presentationMode.wrappedValue.dismiss()
  1. Un caso de uso común de @binding es:
@Binding var showFavorited: Bool

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