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:
- Why does the struct Order still not conform to Codable?
- 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.
- I also get the error message from ContentView: "Generic parameter 'Destination' cannot be inferred" on NavigationLink when I try Challenge 3.
- 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()
}
}