Module 2: Challenge 8

I decided to try out Challenge 8 and I have ran into errors with the coding. Type ‘()’ cannot conform to ‘TableRowContent’ and Cannot assign to property: ‘pizza’ is a ‘let’ constant both of which are displaying on screen. I have tried to switch to a class feature for the structure pizza. Any advice on how I can fix this?

Pizza view

//
//  ContentView.swift
//  Module2Challenge8
//
//  Created by Eric Beecroft on 10/28/21.
//

import SwiftUI

struct ContentView: View {
    var pmodel = PizzaModel()
    var body: some View {
        VStack{
            ForEach(pmodel.pizzas, id: \.name){ pizza in
                Spacer()
                Text(pizza.name)
                HStack{Spacer()
                Text(pizza.topping1)
                    Spacer()
                Text(pizza.topping2)
                    Spacer()
                Text(pizza.topping3)
                    Spacer()
                }
                Spacer()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Pizza model which is now showing weird symbols next to the variables again.

//

// Pizza.swift

// Module2Challenge8

//

// Created by Eric Beecroft on 10/28/21.

//

**import** Foundation

**struct** Pizza{

**var** name = ""

**var** topping1 = ""

**var** topping2 = ""

**var** topping3 = ""

}

Pizza Model

//
//  PizzaModel.swift
//  Module2Challenge8
//
//  Created by Eric Beecroft on 10/28/21.
//

import Foundation
import SwiftUI

class PizzaModel{
    var pizzas = [Pizza]()
    
    init(){
        pizzas.append(Pizza(name: "Original", topping1: "Pepperoni", topping2: "Black Olives", topping3: "Mushrooms"))
        pizzas.append(Pizza(name: "Zaparoni", topping1: "Pepperoni", topping2: "Jalpinos", topping3: "Lightning"))
        pizzas.append(Pizza(name: "AquaSpecial", topping1: "Seacumbers", topping2: "Shrimp", topping3: "Anchovies"))
    }
    
    func addPineapple(){
        ForEach(pizzas){ pizza in
            pizza.topping1 = "Pineapple"
        }
    }
}

You can only use ForEach inside a SwiftUI View; in regular code you have to use either one of these constructions:

for pizza in pizzas {
    //do whatever
}

//or

pizzas.forEach {
    //do whatever
}

As for this specific example, should the addPineapple method add pineapple to every pizza or just to a specific pizza?

If every pizza, try this instead:

for var pizza in pizzas {
    pizza.topping1 = “pineapple”
}

If pineapple should be added to only one pizza, then you would need a parameter to indicate which pizza and then in the func body add “pineapple” to just that pizza.

Also, note that the code you have doesn’t add pineapple to one or more pizzas, it replaces the first topping with pineapple. So, for example, a pepperoni, black olives and mushroom pizza becomes a pinneapple, black olives and mushroom pizza. This may not be what you actually want.

That is something that kind of confused me is the use of for each and for statements as the two are kind of similar. I changed it to a simple for statement but now it is saying that it can’t assign to property ‘pizza’ is a ‘let’ constant.

//
//  PizzaModel.swift
//  Module2Challenge8
//
//  Created by Eric Beecroft on 10/28/21.
//

import Foundation
import SwiftUI

class PizzaModel{
    var pizzas = [Pizza]()
    
    init(){
        pizzas.append(Pizza(name: "Original", topping1: "Pepperoni", topping2: "Black Olives", topping3: "Mushrooms"))
        pizzas.append(Pizza(name: "Zaparoni", topping1: "Pepperoni", topping2: "Jalpinos", topping3: "Lightning"))
        pizzas.append(Pizza(name: "AquaSpecial", topping1: "Seacumbers", topping2: "Shrimp", topping3: "Anchovies"))
    }
    
    func addPineapple(){
        for pizza in pizzas{
            pizza.topping1 = "Pineapple"
        }
    }
}

Think about what this means. In other code, if you can’t assign to a let constant, what do you have to change so that you can assign to it?

And if you need more help, the fix for this has been posted in this thread already.

I added the var command to the for loop and added the published command. I am currently getting the following error message: ‘wrappedValue’ is unavailable: @Published is only available on properties of classes. Yet the viewmodel is a class and not a structure.

Pizza ViewModel

//
//  PizzaModel.swift
//  Module2Challenge8
//
//  Created by Eric Beecroft on 10/28/21.
//

import Foundation
import SwiftUI

class PizzaModel{
    var pizzas = [Pizza]()
    
    init(){
        pizzas.append(Pizza(name: "Original", topping1: "Pepperoni", topping2: "Black Olives", topping3: "Mushrooms"))
        pizzas.append(Pizza(name: "Zaparoni", topping1: "Pepperoni", topping2: "Jalpinos", topping3: "Lightning"))
        pizzas.append(Pizza(name: "AquaSpecial", topping1: "Seacumbers", topping2: "Shrimp", topping3: "Anchovies"))
    }
    
    func addPineapple(){
        for var pizza in pizzas{
            pizza.topping1 = "Pineapple"
        }
    }
    /*
     The pineapple method does get called but there is not any code to update the view,
     and so the old toppings continue to be displayed instead of the one with pineapple added.
     */
}

Getting the weird syntax again from copying and pasting the pizza structure but the class appears to work fine.

Model

//

// Pizza.swift

// Module2Challenge8

//

// Created by Eric Beecroft on 10/28/21.

//

**import** Foundation

**struct** Pizza{

**var** name = ""

**var** topping1 = ""

**var** topping2 = ""

**var** topping3 = ""

}

View

//
//  ContentView.swift
//  Module2Challenge8
//
//  Created by Eric Beecroft on 10/28/21.
//

import SwiftUI

struct ContentView: View {
    @Published var pmodel = PizzaModel()
    var body: some View {
        VStack{
            ForEach(pmodel.pizzas, id: \.name){ pizza in
                Spacer()
                Text(pizza.name)
                HStack{Spacer()
                Text(pizza.topping1)
                    Spacer()
                Text(pizza.topping2)
                    Spacer()
                Text(pizza.topping3)
                    Spacer()
                }
                Spacer()
            }
            Button("Add Pineapple"){
                pmodel.addPineapple()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

@Published doesn’t go in the View, it goes in the view model on the properties you want to observe. You need to use @StateObject or @ObservableObject (@StateObject inn this case) inside the View to watch a @Published property.

I fixed up the code to have the observable objects and published flags. Right now I am getting the error message: Unknown attribute ‘ObservableObject’ to display on the view.

Current view model.

//
//  PizzaModel.swift
//  Module2Challenge8
//
//  Created by Eric Beecroft on 10/28/21.
//

import Foundation
import SwiftUI

class PizzaModel{
    @Published var pizzas = [Pizza]()
    
    init(){
        pizzas.append(Pizza(name: "Original", topping1: "Pepperoni", topping2: "Black Olives", topping3: "Mushrooms"))
        pizzas.append(Pizza(name: "Zaparoni", topping1: "Pepperoni", topping2: "Jalpinos", topping3: "Lightning"))
        pizzas.append(Pizza(name: "AquaSpecial", topping1: "Seacumbers", topping2: "Shrimp", topping3: "Anchovies"))
    }
    
    func addPineapple(){
        for var pizza in pizzas{
            pizza.topping1 = "Pineapple"
        }
    }
    /*
     The pineapple method does get called but there is not any code to update the view,
     and so the old toppings continue to be displayed instead of the one with pineapple added.
     */
}

Current view

//
//  ContentView.swift
//  Module2Challenge8
//
//  Created by Eric Beecroft on 10/28/21.
//

import SwiftUI

struct ContentView: View {
    @ObservableObject var pmodel = PizzaModel()
    var body: some View {
        VStack{
            ForEach(pmodel.pizzas, id: \.name){ pizza in
                Spacer()
                Text(pizza.name)
                HStack{Spacer()
                Text(pizza.topping1)
                    Spacer()
                Text(pizza.topping2)
                    Spacer()
                Text(pizza.topping3)
                    Spacer()
                }
                Spacer()
            }
            Button("Add Pineapple"){
                pmodel.addPineapple()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Sorry, my bad. It’s @ObservedObject, not @ObservableObject

But, as I mentioned earlier, it really should be @StateObject in this case, since you are creating your PizzaModel in this View rather than passing it in from outside the View.

And you need to make your PizzaModel conform to ObservableObject

It is not allowing me to add implements to the pizza model. I am getting several errors cropping up upon changing the pizza model view to an observed object format. Expressions are not allowed at the top level.

//
//  PizzaModel.swift
//  Module2Challenge8
//
//  Created by Eric Beecroft on 10/28/21.
//

import Foundation
import SwiftUI

class PizzaModel implements ObservedObject{
    @Published var pizzas = [Pizza]()
    
    init(){
        pizzas.append(Pizza(name: "Original", topping1: "Pepperoni", topping2: "Black Olives", topping3: "Mushrooms"))
        pizzas.append(Pizza(name: "Zaparoni", topping1: "Pepperoni", topping2: "Jalpinos", topping3: "Lightning"))
        pizzas.append(Pizza(name: "AquaSpecial", topping1: "Seacumbers", topping2: "Shrimp", topping3: "Anchovies"))
    }
    
    func addPineapple(){
        for var pizza in pizzas{
            pizza.topping1 = "Pineapple"
        }
    }
    /*
     The pineapple method does get called but there is not any code to update the view,
     and so the old toppings continue to be displayed instead of the one with pineapple added.
     */
}
//
//  ContentView.swift
//  Module2Challenge8
//
//  Created by Eric Beecroft on 10/28/21.
//

import SwiftUI

struct ContentView: View {
    @ObservedObject var pmodel = PizzaModel()
    var body: some View {
        VStack{
            ForEach(pmodel.pizzas, id: \.name){ pizza in
                Spacer()
                Text(pizza.name)
                HStack{Spacer()
                Text(pizza.topping1)
                    Spacer()
                Text(pizza.topping2)
                    Spacer()
                Text(pizza.topping3)
                    Spacer()
                }
                Spacer()
            }
            Button("Add Pineapple"){
                pmodel.addPineapple()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

That code should be

class PizzaModel: ObservableObject {

What that is saying is that PizzaModel conforms to the protocol ObservableObject.

I changed the class code to PizzaModel: ObservedObject. Now I am getting the error message reference to generic type ‘ObservedObject’ requires arguments.

//
//  PizzaModel.swift
//  Module2Challenge8
//
//  Created by Eric Beecroft on 10/28/21.
//

import Foundation
import SwiftUI

class PizzaModel: ObservedObject{
    @Published var pizzas = [Pizza]()
    
    init(){
        pizzas.append(Pizza(name: "Original", topping1: "Pepperoni", topping2: "Black Olives", topping3: "Mushrooms"))
        pizzas.append(Pizza(name: "Zaparoni", topping1: "Pepperoni", topping2: "Jalpinos", topping3: "Lightning"))
        pizzas.append(Pizza(name: "AquaSpecial", topping1: "Seacumbers", topping2: "Shrimp", topping3: "Anchovies"))
    }
    
    func addPineapple(){
        for var pizza in pizzas{
            pizza.topping1 = "Pineapple"
        }
    }
    /*
     The pineapple method does get called but there is not any code to update the view,
     and so the old toppings continue to be displayed instead of the one with pineapple added.
     */
}

Your PizzaModel is an ObservableObject.

The property wrapper to reference it inside a SwiftUI View is @ObservedObject or @StateObject. In this case, where you are not passing in a PizzaModel from outside your View, you shoul dbe using @StateObject.

So…

class PizzaModel: ObservableObject {
    //...blah blah blah...
}

struct ContentView: View {
    @StateObject var pmodel = PizzaModel()
    //...blah blah blah...
}

So after changing it cleared off the error, and I have the published parameter set in it still does not update the first element. I have looked at the solution but still don’t get why it works and mine doesn’t.

import SwiftUI

struct ContentView: View {
    
    @ObservedObject var model = ViewModel()
    
    var body: some View {
        
        VStack {
            
            List (model.pizzas) { pizza in
                
                VStack(alignment: .leading) {
                    
                    Text(pizza.name)
                        .font(.headline)
                        
                    HStack {
                        Text(pizza.topping1)
                        Text(pizza.topping2)
                        Text(pizza.topping3)
                    }
                }
            }
            
            Button("Add Pineapple") {
                model.addPineapple()
            }
        }
        
        
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
import Foundation

class ViewModel: ObservableObject {
    
    // Add the Published property wrapper to broadcast changes
    @Published var pizzas = [Pizza]()
    
    init() {
        
        // Create and add pizza 1 to the array
        var pizza1 = Pizza()
        pizza1.name = "Meat Lovers"
        pizza1.topping1 = "Pepperoni"
        pizza1.topping2 = "Bacon"
        pizza1.topping3 = "Sausage"
        
        pizzas.append(pizza1)
        
        // Create and add pizza 2 to the array
        var pizza2 = Pizza()
        pizza2.name = "Deluxe"
        pizza2.topping1 = "Pepperoni"
        pizza2.topping2 = "Mushroom"
        pizza2.topping3 = "Green Peppers"
        
        pizzas.append(pizza2)
        
        // Create and add pizza 3 to the array
        var pizza3 = Pizza()
        pizza3.name = "Hawaiian"
        pizza3.topping1 = "Ham"
        pizza3.topping2 = "Bacon"
        pizza3.topping3 = "Pineapple"
        
        pizzas.append(pizza3)
        
    }
    
    func addPineapple() {
        
        // Changing the property value of an object won't trigger a "change" in the pizzas array so our SwiftUI List won't be notified and thus the UI won't update.
        // One "hack" could be to simply remove the last element and re-append it
        // If you do this, the array is changing so it will broadcast a change
        // let lastPizza = pizzas.removeLast()
        // pizzas.append(lastPizza)
        
        // Other things you could do:
        // call pizzas.shuffle() but this would change the order
        
        // Change the Pizza class to a struct which is what I did for this solution.
    
        // That's why we're using an index based For loop.
        // If you used a loop like this "for p in pizzas { }", the struct instance would be copied into "p" for each iteration of the loop and you won't be able to modify the copy in "p".
        for i in 0..<pizzas.count {
            
            // Change the topping1 property to pineapple
            pizzas[i].topping1 = "Pineapple"
        }
        
        // In the upcoming modules, we'll look at other data flow techniques so we won't have to rely on things like the above.
    }
    
}
**import** Foundation

**struct** Pizza: Identifiable {

**var** id = UUID()

**var** name:String = ""

**var** topping1:String = ""

**var** topping2:String = ""

**var** topping3:String = ""

}