1. ホーム
  2. swift

[解決済み] SwiftUIです。バインド変数でカスタムinitを実装する方法

2022-04-16 08:56:57

質問

お金の入力画面を制作しているのですが、カスタムで 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
    }
    ...
}

解決方法は?

アーッ! あと少しだったのに こうやってやるんだ。ドル記号(ベータ3)かアンダースコア(ベータ4)、そして金額プロパティの前にself、または金額パラメータの後に.valueが抜けていますね。これらのオプションはすべて動作します。

を削除したのがわかると思います。 @StateincludeDecimal ということで、最後の説明をご覧ください。

これはプロパティ(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
    }
}

プロパティラッパー(@Binding)のおかげで、.valueは必要ありません。しかし、パラメータでは、そのようなものはなく、明示的に行う必要があります。プロパティ・ラッパーについてもっと知りたい場合は、以下のサイトを参照してください。 WWDCセッション415 - モダンなSwift APIデザイン をクリックし、23:12にジャンプしてください。

あなたが発見したように、initilizerから@State変数を変更すると、次のようなエラーが発生します。 スレッド 1: Fatal error: View.body の外側で State にアクセスしています . これを避けるには、@Stateを削除するか、@Stateを削除する必要があります。これは、includeDecimalが真実の源ではないので、理にかなっています。その値は量から導かれます。しかし、@Stateを削除することで includeDecimal は、金額が変更されても更新されません。これを実現するための最良の方法は、includeDecimalをcomputedプロパティとして定義し、その値が真実の源(金額)から派生するようにすることです。この方法では、金額が変更されるたびに、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 (ベータ3)、または _varName (β4)で状態変数を初期化します。

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

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