SwiftUI: Cara mengimplementasikan init khusus dengan variabel @Binding

95

Saya sedang mengerjakan layar masukan uang dan perlu menerapkan kebiasaan inituntuk menyetel variabel negara berdasarkan jumlah yang diinisialisasi.

Saya pikir ini akan berhasil, tetapi saya mendapatkan kesalahan kompiler:

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
sumber

Jawaban:

152

Argh! Kamu sangat dekat. Beginilah cara Anda melakukannya. Anda melewatkan tanda dolar (beta 3) atau garis bawah (beta 4), dan diri sendiri di depan properti jumlah Anda, atau .value setelah parameter jumlah. Semua opsi ini berfungsi:

Anda akan melihat bahwa saya menghapus @Statein includeDecimal, periksa penjelasannya di bagian akhir.

Ini menggunakan properti (letakkan diri di depannya):

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

atau menggunakan .value after (tetapi tanpa self, karena Anda menggunakan parameter yang diteruskan, bukan properti struct):

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

Ini sama, tetapi kami menggunakan nama yang berbeda untuk parameter (withAmount) dan properti (jumlah), jadi Anda dapat melihat dengan jelas kapan Anda menggunakan masing-masing.

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

Perhatikan bahwa .value tidak diperlukan dengan properti, berkat pembungkus properti (@Binding), yang membuat pengakses yang membuat .value tidak diperlukan. Namun, dengan parameter, tidak ada hal seperti itu dan Anda harus melakukannya secara eksplisit. Jika Anda ingin mempelajari lebih lanjut tentang pembungkus properti, periksa sesi WWDC 415 - Desain API Swift Modern dan lompat ke 23:12.

Seperti yang Anda temukan, mengubah variabel @State dari initilizer akan memunculkan kesalahan berikut: Thread 1: Kesalahan fatal: Mengakses Status di luar View.body . Untuk menghindarinya, Anda harus menghapus @State. Yang masuk akal karena includeDecimal bukanlah sumber kebenaran. Nilainya berasal dari jumlah. Dengan menghapus @State, bagaimanapun, includeDecimaltidak akan diperbarui jika jumlahnya berubah. Untuk mencapainya, opsi terbaik adalah dengan mendefinisikan includeDecimal Anda sebagai properti yang dihitung, sehingga nilainya diturunkan dari sumber kebenaran (jumlah). Dengan cara ini, setiap kali jumlahnya berubah, includeDecimal Anda juga. Jika tampilan Anda bergantung pada includeDecimal, itu harus diperbarui ketika berubah:

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

Seperti yang ditunjukkan oleh rob mayoff , Anda juga dapat menggunakan $$varName(beta 3), atau _varName(beta4) untuk menginisialisasi variabel Status :

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

// Beta 4:
_includeDecimal = State(initialValue: (round(amount.value) - amount.value) != 0)
kontiki
sumber
Terima kasih! Ini sangat membantu! Saya mendapatkan error pada self.includeDecimal = round(self.amount)-self.amount > 0dariThread 1: Fatal error: Accessing State<Bool> outside View.body
keegan3d
Yah, itu masuk akal. @Statevariabel harus mewakili sumber kebenaran. Tetapi dalam kasus Anda, Anda menduplikasi kebenaran itu, karena nilai includeDecimal dapat diturunkan dari sumber kebenaran Anda yang sebenarnya yaitu jumlah. Anda memiliki dua opsi: 1. Anda menjadikan includeDecimal var pribadi (tanpa @State), atau bahkan lebih baik 2. Anda menjadikannya properti terhitung yang memperoleh nilainya amount. Dengan cara ini, jika jumlahnya berubah, includeDecimaljuga demikian. Anda harus menyatakannya seperti ini: private var includeDecimal: Bool { return round(amount)-amount > 0 }dan menghapusself.includeDecimal = ...
kontiki
Hmm, saya harus bisa mengubahnya includeDecimaljadi saya membutuhkannya sebagai variabel @State dalam tampilan. Saya benar-benar hanya ingin memulainya dengan nilai awal
keegan3d
1
@ Let's_Create Saya menonton sepenuhnya hanya sekali, tapi terima kasih Tuhan untuk tombol maju ;-)
kontiki
1
Penjelasan yang sangat bagus, terima kasih. Saya pikir sekarang .valuetelah diganti dengan .wrappedValue, alangkah baiknya memperbarui jawaban dan menghapus opsi beta.
pengguna1046037
11

Anda berkata (dalam komentar) "Saya harus bisa berubah includeDecimal". Apa artinya berubah includeDecimal? Anda tampaknya ingin menginisialisasi berdasarkan apakah amount(pada waktu inisialisasi) adalah bilangan bulat. Baik. Jadi apa yang terjadi jika includeDecimalyang falsedan kemudian Anda mengubahnya ke true? Apakah Anda entah bagaimana akan memaksa amountmenjadi non-integer?

Lagi pula, Anda tidak dapat memodifikasi includeDecimaldi init. Tapi Anda bisa memulainya init, seperti ini:

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

(Perhatikan bahwa di beberapa titik yang $$includeDecimalsintaks akan berubah menjadi _includeDecimal.)

merampok mayoff
sumber
Oh luar biasa, $$ ganda adalah apa yang saya butuhkan untuk bagian ini!
keegan3d
2

Karena ini pertengahan tahun 2020, mari kita rekap:

Mengenai @Binding amount

  1. _amounthanya disarankan untuk digunakan selama inisialisasi. Dan jangan pernah menetapkan seperti ini self.$amount = xxxselama inisialisasi

  2. amount.wrappedValuedan amount.projectedValuetidak sering digunakan, tetapi Anda dapat melihat kasus seperti

@Environment(\.presentationMode) var presentationMode

self.presentationMode.wrappedValue.dismiss()
  1. Kasus penggunaan umum @binding adalah:
@Binding var showFavorited: Bool

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