0
votes

I need to use init with SwiftUI, but I am just learning about initializers. I have found many complex examples, but I cannot understand the basics.

How to initialize different values inside init() based on value of some other var, for example based on true/false from a toggle?

EXAMPLE CODE

I was trying something like this:

import SwiftUI

struct ToggleView: View {
    
    @State var toggleValue: Bool = true
    
    var testValue: String
    
    init() {
        if toggleValue {
            self.testValue = "true"
        } else {
            self.testValue = "false"
        }
    }
    
    var body: some View {
        
        VStack {
            Toggle(isOn: $toggleValue) {
                Text("True or false?")
            }.padding()
            
            Text("Selected value:")
            Text("\(testValue)")
        }
    }
}

struct ToggleView_Previews: PreviewProvider {
    static var previews: some View {
        ToggleView()
    }
}

But on line with if toggleValue { I am getting an error:

'self' used before all stored properties are initialized

I have tried changing:

var testValue: String

init() {

to:

@Binding var testValue: String

init(testValue: Binding<String>) {

But it doesn't work either. How to bind this testValue with toggle, so after taping on toggle my app would print: "Selected value: false"?

1
You don't need a testValue at all. Just do Text("\(toggleValue)") instead of Text("\(testValue)"). In fact, you shouldn't have a second thing that is binded to the toggle, because SwiftUI likes a single source of truth.Sweeper
Thanks. But I need to learn about init on this (or another simple example). I have a problem with init with fetchrequest and dynamic filters in one app I am working on. And I cannot move forward without understanding inits tied with Booleans. And optional bools in the next stepmallow

1 Answers

2
votes

TLDR; You can never initialise a stored property with another stored property. If you want to initialise two properties with the same value, that value has to come from a parameter in your initialiser or some constant.


The compiler actually gives a very useful error. All stored properties need to be initialised before you can use self. In your example you have two stored properties: toggleValue and testValue. toggleValue is initialised, but testValue is not. Since reading a stored property is an example of using self, you cannot use a stored property to initialise another stored property. You can initialise properties with parameters in init().

For example:

init(initialValue: Bool) {
    testValue = initialValue ? "true" : "false"
}

Or you can make testValue a computed property that depends on another property:

var testValue: String {
    toggleValue ? "true" : "false"
}

If you want to keep testValue and toggleValue in sync and be able to modify testValue you can use a binding:

var testValue: Binding<String> { 
    Binding<String>(
        get: { toggleValue ? "true" : "false" },
        set: { toggleValue = $0 == "true" }
    )
}

If you only want to initialise testValue in sync with toggleValue you can use a static const:

static let initialValue = true
@State var toggleValue = ToggleView.initialValue
var testValue = ToggleView.initialValue ? "true" : "false"

Or use a constant in the initialiser:

init() {
    let initialValue = true
    toggleValue = initialValue
    testValue = initialValue ? "true" : "false"
    // Note that in this case the compiler will inform you the 'false'
    // branch will never be executed (which is correct). To prevent this
    // you can either depend on a static constant, create your own
    // extension on bool to provide a string, or just use:
    // testValue = "\(initialValue)"
}

Note that structs have an implicit initialiser with it stored properties listed in order of appearance. With initialised properties as optional parameters. This means you can initialise ToggleView in the following ways (without specifying an initialiser):

ToggleView(toggleValue: false, testValue: true)
ToggleView(testValue: true)