Home > Mobile >  SwiftUI cannot set value to @State property in init()
SwiftUI cannot set value to @State property in init()

Time:02-22

I'm probably not understanding the basics and confused, but I couldn't figure out the reason of things below. Ok, what I want to do is pass Int64 to sub view and show Toggles based on bits and set the bits back to parent view like this:

struct ContentView: View {
    @State var bits: Int64 = 5

    var body: some View {
        Form {
            Section {
                SectionView(title: "Section", withBits: $bits)
            } header: {
                Text("Toggle")
            }
        }
    }
}

struct SectionView: View {
    let title: String
    @Binding var bits: Int64

    @State private var first: Bool
    @State private var second: Bool
    @State private var third: Bool

    init(title: String, withBits: Binding<Int64>) {
        self.title = title
        self._bits = withBits
        self.first = self.bits & 1 == 1
        self.second = self.bits & 2 == 2
        self.third = self.bits & 4 == 4
    }

    var body: some View {
        VStack {
            HStack {
                Text(self.title)
            }
            HStack {
                Toggle("Toggle 1", isOn: self.$first)
                    .onChange(of: self.first) { newValue in
                        if newValue {
                            self.bits |= 1
                        }
                        else {
                            self.bits &= ~1
                        }
                    }
            }
            HStack {
                Toggle("Toggle 2", isOn: self.$second)
                    .onChange(of: self.second) { newValue in
                        if newValue {
                            self.bits |= 2
                        }
                        else {
                            self.bits &= ~2
                        }
                    }
            }
            HStack {
                Toggle("Toggle 3", isOn: self.$third)
                    .onChange(of: self.third) { newValue in
                        if newValue {
                            self.bits |= 4
                        }
                        else {
                            self.bits &= ~4
                        }
                    }
            }
        }
        .onChange(of: self.bits) { newValue in
            print("bits: \(bits)")
        }
    }
}

But it won't compile. Saying "'self' used before all stored properties are initialized" error.

So I set default values to all Bool vars like this:

    @State private var first: Bool = false
    @State private var second: Bool = false
    @State private var third: Bool = false

It compiles now but cannot overwrite them in int(), below lines are completely ignored.

        self.first = self.bits & 1 == 1
        self.second = self.bits & 2 == 2
        self.third = self.bits & 4 == 4

Moving above three lines to onAppear() worked. But still I want to know why they won't be overwritten in init() and cleanest way.

CodePudding user response:

Avoid using in your initialiser the variables being initialised, if you can. The Binding has no value in the beginning, that's why you need to wait for your View to appear.

Try replacing the initialisation for your 3 variables with the following code (using init() and not .onAppear()):

self.first = withBits.wrappedValue & 1 == 1      // Why using an always true condition? OK if this is just a placeholder
self.second = withBits.wrappedValue & 2 == 2
self.third = withBits.wrappedValue & 4 == 4

CodePudding user response:

Ok things are clear now. To make a long story short, we can't initialize @State property in init() by doing like this:

@State var bla: Int = 7
init() {
    self.bla = 0
}

because "bla" is just a referece to storage and so setting 0 to it won't reach to real storage in init(), it will be replaced with newly allocated real property with initial value 7. Whole discussion is here: https://forums.swift.org/t/assignment-to-state-var-in-init-doesnt-do-anything-but-the-compiler-gened-one-works/35235/37

So the answer what we should do here to achieve to set initial value to @State real property in init() is like this:

self._first = State(wrappedValue: withBits.wrappedValue & 1 == 1)
self._second = State(wrappedValue: withBits.wrappedValue & 2 == 2)
self._third = State(wrappedValue: withBits.wrappedValue & 4 == 4)

By doing this, we can also have initial values.

  • Related