SwiftUI: Cách triển khai init tùy chỉnh với các biến @Binding


96

Tôi đang làm việc trên màn hình nhập tiền và cần triển khai tùy chỉnh initđể đặt biến trạng thái dựa trên số tiền đã khởi tạo.

Tôi nghĩ điều này sẽ hoạt động, nhưng tôi gặp lỗi trình biên dịch:

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

Câu trả lời:


153

Argh! Bạn đã rất gần. Đây là cách bạn làm điều đó. Bạn đã bỏ lỡ ký hiệu đô la (beta 3) hoặc dấu gạch dưới (beta 4) và tự đứng trước thuộc tính số tiền của mình hoặc .value sau thông số số tiền. Tất cả các tùy chọn này hoạt động:

Bạn sẽ thấy rằng tôi đã xóa @Statetrong includeDecimal, hãy kiểm tra phần giải thích ở cuối.

Đây là cách sử dụng thuộc tính (đặt bản thân trước nó):

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

hoặc sử dụng .value sau (nhưng không có tự, vì bạn đang sử dụng tham số được truyền, không phải thuộc tính của 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
    }
}

Điều này giống nhau, nhưng chúng tôi sử dụng các tên khác nhau cho tham số (withAmount) và thuộc tính (số tiền), vì vậy bạn có thể thấy rõ khi nào bạn đang sử dụng mỗi tên.

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

Lưu ý rằng .value là không cần thiết với thuộc tính, nhờ vào trình bao bọc thuộc tính (@Binding), nó tạo ra các trình truy cập làm cho .value không cần thiết. Tuy nhiên, với tham số, không có điều đó và bạn phải làm điều đó một cách rõ ràng. Nếu bạn muốn tìm hiểu thêm về trình bao bọc thuộc tính, hãy xem phiên WWDC 415 - Thiết kế API Swift hiện đại và chuyển đến 23:12.

Như bạn đã phát hiện, việc sửa đổi biến @State từ trình khởi tạo sẽ gây ra lỗi sau: Chủ đề 1: Lỗi nghiêm trọng: Truy cập Trạng thái bên ngoài View.body . Để tránh điều đó, bạn nên xóa @State. Điều này có ý nghĩa vì includeDecimal không phải là nguồn chân lý. Giá trị của nó bắt nguồn từ số lượng. Tuy nhiên, bằng cách xóa @State includeDecimalsẽ không cập nhật nếu số lượng thay đổi. Để đạt được điều đó, tùy chọn tốt nhất là xác định includeDecimal của bạn như một thuộc tính được tính toán, để giá trị của nó được lấy từ nguồn chân lý (số tiền). Bằng cách này, bất cứ khi nào số tiền thay đổi, includeDecimal của bạn cũng vậy. Nếu chế độ xem của bạn phụ thuộc vào includeDecimal, chế độ xem sẽ cập nhật khi nó thay đổi:

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

Như được chỉ ra bởi rob mayoff , bạn cũng có thể sử dụng $$varName(beta 3) hoặc _varName(beta4) để khởi tạo biến Trạng thái:

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

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

Cảm ơn! Điều này đã giúp rất nhiều! Tôi nhận được một lỗi thời gian chạy trên self.includeDecimal = round(self.amount)-self.amount > 0củaThread 1: Fatal error: Accessing State<Bool> outside View.body
keegan3d

Chà, nó cũng có lý. @Statecác biến nên đại diện cho một nguồn chân lý. Nhưng trong trường hợp của bạn, bạn đang sao chép chân lý đó, vì giá trị của includeDecimal có thể được lấy từ nguồn chân lý thực tế của bạn là số tiền. Bạn có hai tùy chọn: 1. Bạn đặt includeDecimal thành var riêng (không có @State), hoặc thậm chí tốt hơn 2. Bạn đặt nó thành một thuộc tính được tính toán lấy giá trị của nó amount. Bằng cách này, nếu số tiền thay đổi, includeDecimalcũng sẽ xảy ra. Bạn nên khai báo nó như thế này: private var includeDecimal: Bool { return round(amount)-amount > 0 }và loại bỏ cácself.includeDecimal = ...
Kontiki

Rất tiếc, tôi cần có thể thay đổi includeDecimalnên cần nó làm biến @State trong chế độ xem. Tôi thực sự chỉ muốn khởi tạo nó với một giá trị bắt đầu
keegan3d

1
@ Let's_Create Tôi nhìn họ hoàn toàn chỉ một lần, nhưng nhờ thần cho chuyển tiếp nút ;-)
Kontiki

1
Lời giải thích thực sự tốt đẹp, cảm ơn. Tôi nghĩ rằng bây giờ .valueđã được thay thế bằng .wrappedValue, sẽ rất vui nếu cập nhật câu trả lời và loại bỏ các tùy chọn beta.
user1046037

11

Bạn đã nói (trong một bình luận) "Tôi cần phải thay đổi includeDecimal". Thay đổi nghĩa là includeDecimalgì? Bạn dường như muốn khởi tạo nó dựa trên việc amount(tại thời điểm khởi tạo) có phải là một số nguyên hay không. Được chứ. Vì vậy, điều gì xảy ra nếu includeDecimalfalsevà sau đó bạn thay đổi nó thành true? Bạn sẽ buộc bằng cách nào đó amountđể sau đó không phải là số nguyên?

Dù sao, bạn không thể sửa đổi includeDecimaltrong init. Nhưng bạn có thể khởi tạo nó init, như sau:

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

(Lưu ý rằng tại một số điểm , $$includeDecimalcú pháp sẽ được thay đổi thành _includeDecimal.)


Thật tuyệt vời, $$ gấp đôi là những gì tôi cần cho phần này!
keegan3d

2

Vì đó là giữa năm 2020, hãy tóm tắt lại:

Giống như là @Binding amount

  1. _amountchỉ được khuyến khích sử dụng trong quá trình khởi tạo. Và không bao giờ chỉ định như vậy self.$amount = xxxtrong quá trình khởi tạo

  2. amount.wrappedValueamount.projectedValuekhông được sử dụng thường xuyên, nhưng bạn có thể thấy các trường hợp như

@Environment(\.presentationMode) var presentationMode

self.presentationMode.wrappedValue.dismiss()
  1. Trường hợp sử dụng phổ biến của @binding là:
@Binding var showFavorited: Bool

Toggle(isOn: $showFavorited) {
    Text("Change filter")
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.