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

คำตอบ:


152

อ๊าก! คุณอยู่ใกล้มาก นี่คือวิธีที่คุณทำ คุณพลาดเครื่องหมายดอลลาร์ (เบต้า 3) หรือขีดล่าง (เบต้า 4) และตัวเองอยู่หน้าคุณสมบัติจำนวนเงินของคุณหรือ. ค่าหลังพารามิเตอร์จำนวน ตัวเลือกเหล่านี้ใช้ได้ผล:

คุณจะเห็นว่าฉันลบ@Stateใน includeDecimal ตรวจสอบคำอธิบายในตอนท้าย

นี่คือการใช้คุณสมบัติ (ใส่ตัวเองไว้ข้างหน้า):

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 หลัง (แต่ไม่มีตัวเองเนื่องจากคุณกำลังใช้พารามิเตอร์ที่ผ่านไม่ใช่คุณสมบัติของโครงสร้าง)

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 เนื่องจาก property wrapper (@Binding) ซึ่งสร้างตัวเข้าถึงที่ทำให้. value ไม่จำเป็น อย่างไรก็ตามด้วยพารามิเตอร์ไม่มีสิ่งนั้นและคุณต้องทำอย่างชัดเจน หากคุณต้องการเรียนรู้เพิ่มเติมเกี่ยวกับตัวปิดคุณสมบัติให้ตรวจสอบเซสชัน WWDC 415 - Modern Swift API Designแล้วข้ามไปที่ 23:12

ในขณะที่คุณค้นพบการปรับเปลี่ยนตัวแปร @State จาก initilizer จะโยนความผิดพลาดต่อไปนี้: กระทู้ที่ 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)

ขอบคุณ! สิ่งนี้ช่วยได้มาก! ฉันได้รับข้อผิดพลาด runtime บนself.includeDecimal = round(self.amount)-self.amount > 0ของThread 1: Fatal error: Accessing State<Bool> outside View.body
keegan3d

มันก็สมเหตุสมผลดี @Stateตัวแปรควรแสดงถึงแหล่งที่มาของความจริง แต่ในกรณีของคุณคุณกำลังทำซ้ำความจริงนั้นเนื่องจากค่าของ includeDecimal สามารถได้มาจากแหล่งที่มาของความจริงที่แท้จริงของคุณนั่นคือจำนวน คุณมีสองตัวเลือก: 1. คุณทำให้ includeDecimal var เอกชน (ไม่มี @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")
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.