UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Question: Fail : making a variable of array in a class , where all the items in the array are other properties in the same class

Forums > 100 Days of SwiftUI

@boat  

Hell I'm playing with @Stateobject , @ObservedObject

Please see my code, at the last line of my code making the array @Published var choice =..., Xcode warning me Cannot use instance member 'chosenPosition1' within property initializer; property initializers run before 'self' is available 8 times (repeated for 1 through 8)

All the properties has a default value of 1, right ? So I don't need to initialize it, do I ?

What is the problem here ?

(I am making the array in order to use the it as an @Observedobject or a @Stateobject to pass it around in other views later . Don't know if this is relavant info)


import SwiftUI

class Choice: ObservableObject {

    @Published var chosenPosition1 = 1
    @Published var chosenPosition2 = 1
    @Published var chosenPosition3 = 1
    @Published var chosenPosition4 = 1
    @Published var chosenPosition5 = 1
    @Published var chosenPosition6 = 1
    @Published var chosenPosition7 = 1
    @Published var chosenPosition8 = 1

    @Published var choice = [chosenPosition1,chosenPosition2,chosenPosition3,chosenPosition4,chosenPosition5,chosenPosition6,chosenPosition7,chosenPosition8]

}

Thank you in advance

Boat

2      

The problem is this:

Properties of classes and structs in Swift have to be completely initialized before you can use the class or struct. You initialize properties by either a) supplying a default value, or b) assigning a value in an init.

So let's look at what we have here. chosenPosition1 through chosenPosition8 all have default values, so we're good there. With choice, however, you are assigning a default value that is built from the values of other properties. But according to the rule I mentioned above, you cannot use the properties of class Choice until the class has been completely initialized. Since choice has not yet been initialized class Choice is not completely initialized, so you cannot use any of the other properties of the class. See the dilemma?

I'm not sure what exactly you are trying to do here or I would suggest another way of approaching it.

3      

@boat  

Thank you @roosterboy.

I'm trying to

1, create an Array with $binding , where users can input each item in the array. (It is their own hand-picking choices from 1-8, thus integer)

2, in the second view promoted by sheet(), I want to show by ForEach the array value. telling the users: "Hey, here's a quick summary of all your choices from 1 to 8. press button to submit if happy. "

So the second view screen is not for inputing any data but rather showing what they have chosen in the first screen.

That's why I'm playing with @StateObject and @ObservedObject

Boat

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

I'm still not entirely clear what you're doing. Some View code might help.

But here's a simple example of what I think you're trying to do:

import SwiftUI

class PickerArrayViewModel: ObservableObject {
    //initialize our array to 8 default values of 1
    @Published var choices = Array<Int>(repeating: 1, count: 8)
}

struct PickerArrayView: View {

    @StateObject var viewModel = PickerArrayViewModel()

    //pull this into a function so we don't have to repeat ourselves 8 times
    func integerPicker(for itemIndex: Int) -> some View {
        Picker("Choose...", selection: $viewModel.choices[itemIndex]) {
            ForEach(1...8, id: \.self) { 
                Text("\($0)").tag($0) 
            }
        }
    }

    var body: some View {
        VStack(spacing: 20) {
            ForEach(0..<viewModel.choices.count) { idx in
                integerPicker(for: idx)
            }
        }
        .padding(.horizontal)
        //so that we can watch our changes in the debugger...
        .onChange(of: viewModel.choices) { _ in
            print(viewModel.choices)
        }
    }
}

There's no need to store the user's choices as both individual @Published variables and and @Published array.

3      

@boat  

@roosterboy

thank you so much. I tried your code as a second screen and it works.

But I see you declared a class class PickerArrayViewModel, and then made a @StateObject var out of it...

What I wanted was to pass in the value from main screen where the user chose all their options from 1 to 8.

Shouldn't I :

1, Delcare the Class in the main page.

2, make a @StateObject var instance or @ObservedObject var instance on the second page out of the Class which was declared in the main page.

?

Thank you in advance,

Boat

2      

Well, that example was meant to demonstrate the screen where the user selects their choices. If you want a separate sheet to display those choices, you can do something like this:

class PickerArrayViewModel: ObservableObject {
    @Published var choices = Array<Int>(repeating: 1, count: 8)
}

struct PickerArrayView: View {

    @StateObject var viewModel = PickerArrayViewModel()
    @State private var showSheet = false

    func integerPicker(for itemIndex: Int) -> some View {
        Picker("", selection: $viewModel.choices[itemIndex]) {
            ForEach(1...8, id: \.self) { Text("\($0)").tag($0) }
        }
    }

    var body: some View {
        VStack(spacing: 20) {
            ForEach(0..<viewModel.choices.count) { idx in
                integerPicker(for: idx)
            }
            Button("Show choices") {
                showSheet.toggle()
            }
        }
        .padding(.horizontal)
        //so that we can watch our changes in the debugger...
        .onChange(of: viewModel.choices) { _ in
            print(viewModel.choices)
        }
        .sheet(isPresented: $showSheet) {
            UserDataSheet(userChoices: viewModel)
        }
    }
}

struct UserDataSheet: View {
    @ObservedObject var userChoices: PickerArrayViewModel

    var body: some View {
        VStack(spacing: 40) {
            ForEach(userChoices.choices, id: \.self) {
                Text("\($0)")
            }
        }
    }
}

But yes, you are correct that you would declare and initialize your @StateObject in the parent View and pass it to the child as an @ObservedObject, as you can see done in this updated example.

3      

@boat  

@roosterboy, that is awesome . It worked beautifully in my Xcode and simulator.

But when I tried to "divide bigger chunk into smaller chunks" , meaning, I maded a "Second View file" and copied the Struct UserDataSheet into that file , for the purpose of always having a separate file for a separate screen(view).

Just Like Paul demonstrated : https://www.hackingwithswift.com/books/ios-swiftui/sharing-an-observed-object-with-a-new-view

To make it compile, I will need to modify the Previews , and pass in a value for usersChoices.

I modifed the last line of the Preview code into UserDataSheet (userChoices: PickerArrayViewModel)

But Xcode warns me: Cannot convert value of type 'PickerArrayViewModel.Type' to expected argument type 'PickerArrayViewModel'

 import SwiftUI

struct UserDataSheet: View {
    @ObservedObject var userChoices: PickerArrayViewModel

    var body: some View {
        VStack(spacing: 40) {
            ForEach(userChoices.choices, id: \.self) {
                Text("\($0)")
            }
        }
    }
}

struct UserDataSheet_Previews: PreviewProvider {
    static var previews: some View {
        UserDataSheet(userChoices: PickerArrayViewModel)
    }
}

2      

In the preview, you need to create an instance of PickerArrayViewModel because it's not being passed in from anywhere else.

struct UserDataSheet_Previews: PreviewProvider {
    static var previews: some View {
        UserDataSheet(userChoices: PickerArrayViewModel())
    }
}

3      

@boat  

@roosterboy, yay !

The reason my code in the preview didn't work was I missed a pair of () after PickerArrayViewModel

I haven't learned .tag . What does it mean in your code { Text("\($0)").tag($0) } ?

Thank you

Boat

2      

In order for Picker to work, the variable you use to track the selection has to match the type of the tag of each item inside the Picker. Tags can either be implicitly assigned based on the id used to loop through a ForEach used to populate the Picker or they can be explicitly assigned with a .tag(_:) modifier.

In this particular case, we're looping through a ClosedRange<Int> and using \.self to identify the id of each item, which means the implicit tag of each item is an Int. So they match up and the explicit .tag(_:) isn't strictly needed.

3      

@boat  

@roosterboy, thanks for your patience, this is really really help me a lot.

I need to go back and study on how the twice ForEach you used worked, but mine didn't.

There's still lots to learn and reflect from this thread.

Really appreciate your effort and help,

Have a great week Patrick !

Boat

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.