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

SOLVED: Day 52 Project 10 Cupcake Corner - 2nd Challenge - Help Please!

Forums > 100 Days of SwiftUI

This was to be my solution to displaying more meaningful error information in an Alert for Checkout View. When, however, I disable wi-fi, the Alert is not displayed. It calls my error handler (which I added because I understand that you can't throw inside the completion handler) and it places the correct error message into the errorMessage property and it toggles my $showingErrorAlert to true. I can't understand why the Alert is not displayed?

Here is my Checkout.View !

//
//  CheckoutView.swift
//  CupCake Corner
//
//  Created by Lee Beddington-March on 30/05/2021.
//

import SwiftUI

struct CheckoutView: View {
    /* reference to order model object created here */
    @ObservedObject var order: Order

    /* message and visible status for alert to display 'echoed' JSON order */
    @State private var confirmationMesssage = ""
    @State private var showingConfirmation = false

    @State private var showingErrorAlert = false
    @State private var errorMessage = ""
    enum orderError: Error {
        case encodingError /* failed to encode order */

    }

    func myErrorHandler(error: String) {
        self.errorMessage = error
        self.showingErrorAlert = true
    }

    // function to place an order (send it)
    func placeOrder() throws {

        /* convert order to JSON */
        guard let encoded = try? JSONEncoder().encode(order) else {
            throw orderError.encodingError
        }
        /* create & configure URLRequest */

        let url = URL(string: "https://reqres.in/api/cupcakes")!
        /* 'reqres' lets us send data and sends it back */

        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        request.httpBody = encoded

        /* send URL data request */

        URLSession.shared.dataTask(with: request) {
            /* throwing not allowed in .dataTask completion handler */
            data, response, error in

            guard let data = data else {
                self.myErrorHandler(error: (error?.localizedDescription ?? "Unknown Error"))
                return
            }
                if let decodedOrder = try? JSONDecoder().decode(Order.self, from: data) {
                    self.confirmationMesssage = "Your order for \(decodedOrder.quantity) x \(Order.types[decodedOrder.type].lowercased()) cupcakes is on it's way!"
                    self.showingConfirmation = true
                } else {
                        myErrorHandler(error: "response error")

                }

        }.resume()
    }

    var body: some View {
        GeometryReader { geo in
            ScrollView {
                VStack {
                    Image("cupcakes")
                        .resizable()
                        .scaledToFit()
                        .frame(width: geo.size.width)
                    Text("Your order is $\(self.order.cost, specifier: "%.2f")")
                        .font(.title)
                    Button("Place Order") {
                        do {
                            try self.placeOrder()
                        } catch {
                            self.errorMessage = error.localizedDescription
                            self.showingErrorAlert = true
                        }
                    }
                    .padding()
                }
            }
        }.alert(isPresented: $showingErrorAlert, content: {
            Alert(title: Text("Error placing order!"), message: Text("Error \(self.errorMessage)"), dismissButton: .default(Text("Ok")))
            })
        .alert(isPresented: $showingConfirmation, content: {
            Alert(title: Text("Thank you!"), message: Text(confirmationMesssage), dismissButton: .default(Text("Ok")))
        })
        .navigationBarTitle("Check out", displayMode: .inline)
    }
}

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

3      

Try attaching one of those alert modifiers to, say, the VStack or the ScrollView. SwiftUI has a problem with attaching multiple alerts like you've done here, though IIRC it's been fixed in more recent releases.

3      

I had the same problem while doing this project, and read somewhere online that it is not possible to use two .alert modifiers on a single view.

This makes sense if you think about other modifiers that you can add to views. For example, if you try to add a .font modifier to a Text view, and then add another .font modifier immediately after it, only the second modifier you added will be in effect.

In this project, I ran into the problem where only one of the .alert modifiers was in effect, even when I added one .alert modifier to the Button itself, and the other .alert modifier to my GeometryReader that the Button was contained in.

I think it has something to do with the alert(isPresented: ) condition for both of the alerts being triggered by the same button, so there isn't a good way to separate them.

The only solution I was able to find was to kind of multi-purpose the @State variables for both situations. Either way, when you press the button, an alert is shown, but the title and message of the alert would just be different depending on whether there was an error or the order was completed successfully.

@State private var orderCompletionTitle = ""
@State private var orderCompletionMessage = ""
@State private var  showingOrderCompletion = false
URLSession.shared.dataTask(with: request) { data, response, error in
            guard let data = data else {
                self.orderCompletionTitle = "Failed to place order!"
                self.orderCompletionMessage = "No data in response: \(error?.localizedDescription ?? "Unknown error.")"
                self.showingOrderCompletion = true
                return
            }

            if let decodedOrder = try? JSONDecoder().decode(Order.self, from: data) {
                self.orderCompletionTitle = "Your order has been placed!"
                self.orderCompletionMessage = "Your order for \(decodedOrder.quantity) \(Order.types[decodedOrder.type].lowercased()) cupcakes is on its way."
                self.showingOrderCompletion = true
            } else {
                self.orderCompletionTitle = "Your order may not have been placed correctly!"
                self.orderCompletionMessage = "Invalid response from server."
                self.showingOrderCompletion = true
            }

        }.resume()

I admit, this doesn't seem like a very professional solution, but it gets the job done I guess.

4      

One neat way to handle multiple Alerts is to use the .alert(item:content:) modifier instead of .alert(isPresented:content:). An example of how that would work:

import SwiftUI

struct AlertData: Identifiable {
    let id = UUID()
    let title: Text
    let message: Text
    let button: Alert.Button

    static let firstAlert = AlertData(title: Text("First Alert"),
                                      message: Text("This is the first alert"),
                                      button: .default(Text("OK")))

    static let secondAlert = AlertData(title: Text("Second Alert"),
                                       message: Text("This is the second alert"),
                                       button: .default(Text("OK")))
}

struct MultiAlertView: View {
    @State private var alert: AlertData?

    var body: some View {
        VStack(spacing: 20) {
            Button { alert = AlertData.firstAlert } label: { Text("Show first alert") }
            Button { alert = AlertData.secondAlert } label: { Text("Show second alert") }
        }
        .alert(item: $alert) { a in
            Alert(title: a.title, message: a.message, dismissButton: a.button)
        }
    }
}

4      

Hmm... interesting.

So is item: just a binding to any type of optional then?

2      

To an optional Identifiable something.

(Although in looking at the changes and new stuff in SwiftUI 3, it looks like this initializer has been deprecated going forward. I haven't really explored the new stuff much, so I'm not sure if there's an equivalent. That would suck if there isn't, IMO, because it's so useful.)

2      

Thank you @FlyOstrich and @roosterboy for your help. I decided to adopt the .alert(item:modifier:) approach suggested by roosterboy.

import SwiftUI

struct AlertDataX: Identifiable {
        let id = UUID() /* 'item' in .alert(item:content:) call to .alert constructor must conform to identifiable */
        let title: Text
        let message: Text
        let button: Alert.Button

    static let alertInvalidResponse = AlertDataX(title: Text("Your order may not have been placed correctly!"), message: Text("Invalid response from server!"), button: .default(Text("OK")))

    }

struct CheckoutView: View {

    /* reference to order model object created here */
    @ObservedObject var order: Order

    @State private var alertX: AlertDataX?

    // function to place an order (send it)
    func placeOrder() {

        /* convert order to JSON */
        guard let encoded = try? JSONEncoder().encode(order) else {
            alertX = AlertDataX(title: Text("Failed to place order!"), message: Text("Encoding Error"), button: .default(Text("OK")))
    return
        }
        /* create & configure URLRequest */

        let url = URL(string: "https://reqres.in/api/cupcakes")!
            /* 'reqres' lets us send data and sends it back */

        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        request.httpBody = encoded

        /* send URL data request */

        URLSession.shared.dataTask(with: request) {
            /* throwing not allowed in .dataTask completion handler */
            data, response, error in

            guard let data = data else {

                        alertX = AlertDataX(title: Text("Failed to place order!"), message: Text("No data in response:\(error?.localizedDescription ?? "Unknown Error")"), button: .default(Text("OK")))
                return
            }
                if let decodedOrder = try? JSONDecoder().decode(Order.self, from: data) {
                    alertX = AlertDataX(title: Text("Your order has been placed!"), message: Text("Your order for  \(decodedOrder.quantity) \(Order.types[decodedOrder.type].lowercased())  cupcakes is on the way"), button: .default(Text("OK")))
                } else {
                    alertX = AlertDataX.alertInvalidResponse

                }

        }.resume()
    }

    var body: some View {
        GeometryReader { geo in
            ScrollView {
                VStack {
                    Image("cupcakes")
                        .resizable()
                        .scaledToFit()
                        .frame(width: geo.size.width)
                    Text("Your order is $\(self.order.cost, specifier: "%.2f")")
                        .font(.title)
                    Button("Place Order") {
                             self.placeOrder()
                    }
                    .padding()
                } /* end of VStack */

            } /* end of ScrollView */

        } /* end of Geometry Reader */

        .alert(item: $alertX) { detail in
            Alert(title: detail.title, message: detail.message, dismissButton: detail.button)
        }
        .navigationBarTitle("Check out", displayMode: .inline)
    }
}

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

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

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.