SwiftUI: Як реалізувати власний init зі змінними @Binding


96

Я працюю на екрані введення грошей, і мені потрібно застосувати спеціальний 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
    }
    ...
}

Відповіді:


153

Ага! Ви були так близько. Ось як ви це робите. Ви пропустили знак долара (бета-версія 3) або підкреслення (бета-версія 4), або або перед вашим властивістю суми, або .value після параметра суми. Усі ці варіанти працюють:

Ви побачите, що я видалив @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) та властивості (суми), так що ви чітко бачите, коли ви використовуєте кожен з них.

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 - Сучасний дизайн API Swift і перейдіть до 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 { ... }
}

Як вказує rob mayoff , ви також можете використовувати $$varName(beta 3) або _varName(beta4) для ініціалізації змінної стану:

// 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")
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.