WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

SOLVED: Day 52 Project 10 Challenge 3: Having Trouble Solving It

Forums > 100 Days of SwiftUI

Hi, I've tried to solve Project 10 Challenge 3 and I saw a couple of posts here, but none of them seemed to work.

Questions:

  1. Why does the struct Order still not conform to Codable?
  2. I also get the message in AddressView that "Generic struct 'ObservedObject' requires that 'Order' conform to 'ObservableObject'". How is that possible? Since ObservableObject applies to a class.
  3. I also get the error message from ContentView: "Generic parameter 'Destination' cannot be inferred" on NavigationLink when I try Challenge 3.
  4. Also this error message in CheckoutView "Value of type 'OrderClass' has no dynamic member 'order' using key path from root type 'Order'". That seems strange since OrderClass does contain a property that is an order struct, which then has a property called cost.

Here is my code:

Order

iimport SwiftUI

class OrderClass : ObservableObject, Codable {
    @Published var order = Order()

    enum CodingKeys : CodingKey {
        case order
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        order = try container.decode(Order.self, forKey: .order)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(order, forKey: .order)
    }
}

struct Order : Codable {

    static let types = ["Vanilla", "Strawberry", "Chocolate", "Rainbow"]

    var type = 0
    var quantity = 3

    var specialRequestEnabled = false {
        didSet {
            if specialRequestEnabled == false {
                extraFrosting = false
                addSprinkles = false
            }
        }
    }

    var extraFrosting = false
    var addSprinkles = false

    var name = ""
    var streetAddress = ""
    var city = ""
    var zip = ""

    var hasValidAddress : Bool {
        if name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || streetAddress.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || city.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || zip.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
            return false
        }
        return true
    }

    var cost : Double {
        var cost = Double(quantity) * 2

        // complicated cakes cost more
        cost += (Double(type) / 2)

        if extraFrosting {
            cost += Double(quantity)
        }

        if addSprinkles {
            cost += Double(quantity) / 2
        }

        return cost
    }
}

CheckOut View

import SwiftUI

struct CheckoutView: View {
    @ObservedObject var order : Order

    @State private var confirmationMessage = ""
    @State private var showingConfirmation = false

    @State private var networkFailureMessage = ""
    @State private var networkingFailure = false

    func placeOrder() async {

        guard let encoded = try? JSONEncoder().encode(order) else {
            print("Failed to encode order")
            return
        }

        let url = URL(string: "https://reqres.in/api/cupcakes")!
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"

        do {
            let (data, _) = try await URLSession.shared.upload(for: request, from: encoded)
            let decodedOrder = try JSONDecoder().decode(Order.self, from: data)
            confirmationMessage = "Your order for \(decodedOrder.quantity)x \(Order.types[decodedOrder.type].lowercased()) cupcakes is on its way !"
            showingConfirmation = true
        }
        catch {
            networkingFailure = true
            networkFailureMessage = "Lost internet connection."
        }

    }

    var body: some View {
        ScrollView {
            VStack {
                AsyncImage(url: URL(string: "https://hws.dev/img/cupcakes@3x.jpg"), scale: 3) { image in
                    image
                        .resizable()
                        .scaledToFit()
                } placeholder: {
                    ProgressView()
                }
                .frame(height: 233)

                Text("Your total is \(order.order.cost, format: .currency(code: "USD"))")
                    .font(.title)

                Button("Place Order") {
                    Task {
                        await placeOrder()
                    }
                }
                .padding()
            }
        }
        .navigationTitle("Check Out")
        .alert("Thank you", isPresented: $showingConfirmation) {
            Button("OK") {  }
        } message: {
            Text(confirmationMessage)
        }
        .alert("Networking Failure", isPresented: $networkingFailure) {
            Button("Continue") {  }
        } message: {
            Text(networkFailureMessage)
        }
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct CheckoutView_Previews: PreviewProvider {
    static var previews: some View {
        CheckoutView(order: Order)
    }
}

AddressView:

import SwiftUI

struct AddressView: View {
    @ObservedObject var order : Order

    var body: some View {
        Form {
            Section {
                TextField("Name", text: $order.name)
                TextField("Street Address", text: $order.streetAddress)
                TextField("City", text: $order.city)
                TextField("Zip Code", text: $order.zip)
            }

            Section {
                NavigationLink {
                    CheckoutView(order: order)
                } label: {
                    Text("Check Out")
                }
            }
            // if this condition satisfies, there won't be a new view leading to Check Out.
            .disabled(order.hasValidAddress == false)
        }
        .navigationTitle("Delivery Details")
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct AddressView_Previews: PreviewProvider {
    static var previews: some View {
        AddressView(order: Order())
    }
}

ContentView:

import SwiftUI

struct ContentView: View {
    // When @Published properties change, StateObject updates them.
    @State var order = Order()

    var body: some View {
        NavigationView {
            Form {
                Section {
                    Picker("Select your cake type", selection: $order.type) {
                        ForEach(Order.types.indices) {
                            Text(Order.types[$0])
                        }
                    }

                    Stepper("Number of cakes: \(order.quantity)", value: $order.quantity, in: 3...20)
                }

                Section {
                    Toggle("Any special requests?", isOn: $order.specialRequestEnabled.animation())

                    if order.specialRequestEnabled {
                        Toggle("Add extra frosting", isOn: $order.extraFrosting)
                        Toggle("Add extra sprinkles", isOn: $order.addSprinkles)
                    }
                }

                Section {
                    NavigationLink {
                        AddressView(order: order)
                    } label: {
                        Text("Delivery Details")
                    }
                }

            }
            .navigationTitle("CupCake Corner")
        }
    }
}

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

   

HINT:

In your CheckOutView struct, how did you declare the variable order ??

All the other errors you list are probably side effects of one error. Once you solve the big problem, the others may very well disappear.

   

@Obelix:

Thank you for the hint. I declared order in Checkout View as @State var order : Order since it is impossible to make order Struct conform to the ObservableObject protocol. A similar change was also made in AddressView.

I do observe that in Challenge 3, with a Struct approach instead of a Class, when I enter the address details and then lick "back", and then go to the address page again, all information is lost, which I believe speaks to the "value type vs. reference type" thing ?

   

Right on!

I love it when the lightbulbs come on. Nice.

I may have ranted a few times about naming objects. If my coding team were to peer-review your code, this would certainly be one of the team's comments.

I think you tripped yourself up by taking shortcuts with your struct and class names. While you're learning, feel free to be overly verbose when naming your structs and classes. For one, it helps you solidify the concepts in your head. Verbose names also help you with your logic.

Compare:

let theOneAndOnlyFaceUpCard = Card?  // Verbose name from Matching card game from CS193p course.
// compare these two game card names
let tappedCard = Card? // Is this the first? or the second tapped card? It's unclear.

Another good reason may have helped in your situation.

You have this:

struct CheckoutView: View {
    @ObservedObject var order : Order
    // snip.......

You used Order to describe a struct, then reused this word as the name of a variable. Yikes! Which are you using? The struct, the class, or the variable. Confusing.

Maybe a better name could be:

struct CheckoutView: View {
    @ObservedObject var customersCupcakeOrder : Order  // <-- Be verbose!
    // snip.......

1      

@Obelix:

I will certainly keep this in mind ! Thank again.

   

Save 50% in my Black Friday sale.

SAVE 50% To celebrate WWDC22, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

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

Reply to this topic…

You need to create an account or log in to reply.

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.