Search code examples
swiftxcodeswiftuics193p

SwiftUI CS193P - Cannot use instance member 'card' within property initializer; property initializers run before 'self' is available


I am following Stanfords' CS193p Developing Apps for iOS online course. I am using Xcode 11.5. (I didn't update because that's the version the course instructor (Paul Heagarty) is using.)

I'm trying to do the Assignment 3 (Set Game). I know my code is probably poorly written in many places, but I am going to refine it later, right now I am just trying to make it work with no errors. The error I currently can not fix is this:

var shading: SetGameModel.Card.Shading = card.shading //Error: Cannot use instance member 'card' within property initializer; property initializers run before 'self' is available

And here is full View code: If you also need ViewModel, Model or some other files to help me fix it, let me know :-)

Please try to help me fix it in some way, that I will understand as a beginner 🙏

//
//  SetGameView.swift
//  TheSetGame
//

import SwiftUI

struct SetGameView: View {
    
    @ObservedObject var viewModel: SetGameViewModel
    
    var body: some View {
            Grid(viewModel.cards) { card in
                CardView(card: card, shading: .solid).onTapGesture {
                    self.viewModel.choose(card: card)
                }
            }
        .padding()
        .foregroundColor(Color.orange)
    }
}

struct CardView: View {
    var card: SetGameModel.Card
    // Shape features (numberOfShapes, shape, shading, color):
    var numberOfShapes: Int {
        switch card.numberOfShapes {
        case .one:
            return 1
        case .two:
            return 2
        case .three:
            return 3
        }
    }
    
    var shading: SetGameModel.Card.Shading = card.shading //Error: Cannot use instance member 'card' within property initializer; property initializers run before 'self' is available
    var isFilled: Bool { shading == .solid ? true : false }
    var isStriped: Bool { shading == .striped ? true : false }
    
    var color: Color {
        switch card.color {
        case .green:
            return Color.green
        case .purple:
            return Color.purple
        case .red:
            return Color.red
        }
    }
    
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 10.0).fill(Color.white)
            RoundedRectangle(cornerRadius: 10.0)
                .stroke(lineWidth: card.isChosen ? 6 : 3)
                .foregroundColor(card.isChosen ? Color.red : Color.orange)
            VStack {
                ForEach(0..<numberOfShapes) { index in
                    if self.card.shape == .diamond {
                        Diamond()//.scale(0.75)
                    }
                    if self.card.shape == .squiggle {
                        Rectangle()//.scale(0.75)
                    }
                    if self.card.shape == .oval {
                        Ellipse()//.scale(0.75)
                    }
                }
            }
            .foregroundColor(self.color)
            .padding()
        }
        .aspectRatio(2/3, contentMode: .fit)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        SetGameView(viewModel: SetGameViewModel())
    }
}

Solution

  • This is going to sound a bit existential, but the compiler is complaining because you are trying to set shading to a value that does not exist yet. When you first set the values of your variables in the View, they do not actually exist. You are telling the compiler what they are going to be, but the compiler insists that the thing that you have said the variable will be actually exists.

    There are ways around this, such as George E. suggesting a lazy var. I don't want to go down that route as that is beyond a beginner. For your purposes just know, when you define a variable, it must be give an actual value. The compiler won't complain about var x = 1 because it knows what 1 is, and it already exists.

    Your exact error says you can't use the instance card because it has not been initialized at the time you are trying to use it. In other words, it has no value when you are trying to assign it, as card.shading to shading.

    Now, that being said, why do you need shading at all? Isn't the shading you want always going to be card.shading? Rather than make a separate variable, just use what you have. Learn to love dot notation and keep it simple.

    The last thing to consider, outside of your original question, is that you have a view without any State variables. Your compiler will complain at runtime if you try to change a variable that is not marked @State. What you have created is a fixed view, not one that can change or update.