SwiftUI Alert dismisses itself due to Code Execution order

529 views Asked by At

I have a view, where i can add a new entry to CoreData. The name for that entry cannot be null, which can be seen in the ViewModel. If someone tries to add a new entry without a name, they are presented with an error. Now, every time the error pops up, it dismisses itself.

The View:

struct AddProductPopover: View {

@Environment(\.presentationMode) var presentationMode
@StateObject var prodPopViewModel = AddProductPopoverViewModel()

var body: some View {
    NavigationView {
        List {
            HStack {
                Label("", systemImage: K.ProductIcons.name)
                    .foregroundColor(.black)
                Spacer().frame(maxWidth: .infinity)
                TextField("Add Name", text: $prodPopViewModel.newProductName)
                    .keyboardType(.default)
            }
        }
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button("Save") {
                    prodPopViewModel.saveProduct()
                    
                    // if saving fails due to an empty name, the dismissal is still called before the error is displayed
                    presentationMode.wrappedValue.dismiss()
                }
                .alert(isPresented: $prodPopViewModel.showAlert) {
                    Alert(
                        title: Text("Product Name cannot be empty!"),
                        message: Text("Please specify a name for your new Product.")
                    )
                }
            }
        }
    }
}

The ViewModel:

class AddProductPopoverViewModel: ObservableObject {

var managedObjectContext = PersistenceController.shared.container.viewContext

@Published var newProductName: String = ""
@Published var newProductVendor: String = ""
@Published var newProductCategory: String = ""
@Published var newProductStoredQuantity: Int = 0
@Published var showAlert = false

func saveProduct() {
    
    // if name is not nil saves the new product to CoreData
    if !newProductName.isEmpty {
        let newProduct = ProductEntity(context: managedObjectContext)
        newProduct.productName = newProductName
        newProduct.id = UUID()
        newProduct.productVendor = newProductVendor
        newProduct.productCategory = newProductCategory
        newProduct.productStoredQuantity = Int32(newProductStoredQuantity)
        
        PersistenceController.shared.save()
        
    } else {
        showAlert = true
    }
}

I have figured out, that issue lies in the View in the Button Save action. Whenever the check in the ViewModel fails, it sets the boolean required for the alert to true. However, after setting that boolean to true, it returns to the view first and completes the next step in the Button Action, which is dismissing the current view before it then finally triggers the Alert. This execution order results in the Alert to be dismissed. However, the alert should not be dismissed. Dismissing should only happen if saving to CoreData has been successfull.

Button("Save") {
   prodPopViewModel.saveProduct()
   presentationMode.wrappedValue.dismiss()
}

What changes would I need to make to skip the dismissing line in case the boolean is set to true? I thought of including the dismissal in the ViewModel. However, that would violate the MVVM concept I'm trying to follow.

1

There are 1 answers

0
mani On BEST ANSWER

Replace save button with:

Button("Save") {
  prodPopViewModel.saveProduct() // save the product

  if (!prodPopViewModel.showAlert) { // don't dismiss if need to show alert
    presentationMode.wrappedValue.dismiss()
  }
}
.alert("Product Name cannot be empty!", // alert title
  // decide to show alert i.e. save failed
  isPresented: $prodPopViewModel.showAlert) { 
  Button("Ok"){
    // hide alert on button press
    prodPopViewModel.showAlert = false
  }
} message: {
  Text("Please specify a name for your new Product.")
}