SwiftUI: как реализовать собственный init с переменными @Binding

95

Я работаю над экраном ввода денег, и мне нужно реализовать заказ, initчтобы установить переменную состояния на основе инициализированной суммы.

Я думал, что это сработает, но получаю ошибку компилятора:

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
источник

Ответы:

152

Ах! Вы были так близки. Вот как вы это делаете. Вы пропустили знак доллара (бета 3) или подчеркивание (бета 4) и либо self перед свойством amount, либо .value после параметра amount. Все эти варианты работают:

Вы увидите, что я удалил @Statein includeDecimal, проверьте объяснение в конце.

Это использует свойство (поместите перед ним self):

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

или используя .value после (но без self, потому что вы используете переданный параметр, а не свойство структуры):

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

Это то же самое, но мы используем разные имена для параметра (withAmount) и свойства (amount), поэтому вы четко видите, когда вы их используете.

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

Обратите внимание, что .value не требуется для свойства, благодаря оболочке свойства (@Binding), которая создает средства доступа, которые делают ненужным .value. Однако с параметром такого нет, и вы должны делать это явно. Если вы хотите узнать больше о оболочках свойств, посмотрите сеанс WWDC 415 - Современный дизайн Swift API и перейдите к 23:12.

Как вы обнаружили, изменение переменной @State из инициализатора вызовет следующую ошибку: Поток 1: Неустранимая ошибка: доступ к состоянию вне View.body . Чтобы этого избежать, вам следует либо удалить @State. Это имеет смысл, потому что includeDecimal не является источником истины. Его стоимость определяется суммой. Однако при удалении @State не includeDecimalбудет обновляться, если сумма изменится. Для этого лучше всего определить includeDecimal как вычисляемое свойство, чтобы его значение было получено из источника истины (количества). Таким образом, всякий раз, когда изменяется сумма, ваш includeDecimal тоже. Если ваше представление зависит от includeDecimal, оно должно обновляться при изменении:

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

Как указал Роб Майофф , вы также можете использовать $$varName(beta 3) или _varName(beta4) для инициализации переменной State:

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

// Beta 4:
_includeDecimal = State(initialValue: (round(amount.value) - amount.value) != 0)
контики
источник
Благодарность! Это очень помогло! Я получаю сообщение об ошибке выполнения на self.includeDecimal = round(self.amount)-self.amount > 0изThread 1: Fatal error: Accessing State<Bool> outside View.body
keegan3d
Что ж, в этом есть смысл. @Stateпеременные должны представлять источник истины. Но в вашем случае вы дублируете эту истину, потому что значение includeDecimal может быть получено из вашего фактического источника истины, то есть количества. У вас есть два варианта: 1. Сделать includeDecimal частной переменной (без @State) или даже лучше 2. Вы сделаете ее вычисляемым свойством, из которого выводится его значение amount. Таким образом, если сумма изменится, includeDecimalтоже. Вы должны объявить это так: private var includeDecimal: Bool { return round(amount)-amount > 0 }и удалитьself.includeDecimal = ...
kontiki
Хм, мне нужно иметь возможность изменять, includeDecimalпоэтому он нужен как переменная @State в представлении. Я действительно просто хочу инициализировать его начальным значением
keegan3d
1
@ Let's_Create Я просмотрел их полностью только один раз, но слава богу за кнопку форвардов ;-)
kontiki
1
Действительно хорошее объяснение, спасибо. Я думаю, что теперь .valueбыл заменен на .wrappedValue, было бы неплохо обновить ответ и удалить параметры бета.
user1046037
11

Вы сказали (в комментарии): «Мне нужно иметь возможность измениться includeDecimal». Что значит изменить includeDecimal? Очевидно, вы хотите инициализировать его в зависимости от того, является ли amount(во время инициализации) целым числом. Ладно. Итак, что произойдет, если includeDecimalбудет, falseа потом вы измените его на true? Собираетесь ли вы как-то заставить amountпотом быть нецелым числом?

Во всяком случае, вы не можете изменить includeDecimalв init. Но вы можете инициализировать его 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

(Обратите внимание , что в какой - то момент$$includeDecimal синтаксис будет изменен _includeDecimal.)

Роб Мэйофф
источник
О, круто, двойные $$ - это то, что мне нужно для этой части!
keegan3d
2

Поскольку сейчас середина 2020 года, подведем итоги:

Относительно @Binding amount

  1. _amountрекомендуется использовать только во время инициализации. И никогда не назначайте таким образом self.$amount = xxxво время инициализации

  2. amount.wrappedValueи amount.projectedValueне часто используются, но вы можете увидеть такие случаи, как

@Environment(\.presentationMode) var presentationMode

self.presentationMode.wrappedValue.dismiss()
  1. Типичный вариант использования @binding:
@Binding var showFavorited: Bool

Toggle(isOn: $showFavorited) {
    Text("Change filter")
}
LiangWang
источник