1

When I'm trying to dismiss/cancel an Add Object Modal, it is creating an empty object instead of just cancelling.

I've tried deleteObject, context.rollback(), and a bunch of other random things. Would love some help and able to answer any questions.

I realize that this isn't an issue by putting the Cancel button in a NavigationBarItem but would like to be able to understand how to make an separate "cancel (or dismiss)" button.

ContentView.swift

import SwiftUI
import CoreData


struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: Game.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Game.gameName, ascending: true)]) var games: FetchedResults<Game>
    @State private var showingAddGame = false


    var body: some View {
        GeometryReader { geometry in
            NavigationView {
                List {
                    ForEach(self.games, id: \.self) { games in
                        NavigationLink(destination: GameGoalsDetail(game: games)) {
                            VStack(alignment: .leading) {
                                Text(games.gameName ?? "Unknown Game")
                                Text(games.gameDescription ?? "Unknown Game Description")
                            }
                        }
                    }
                    .onDelete(perform: self.removeGames)
                    }

                .navigationBarItems(leading:
                    HStack {
                        Button(action: {
                                self.showingAddGame.toggle()
                            }) {
                                Text("Add Game")
                                    .padding(.top, 50)
                                    .foregroundColor(Color.yellow)
                        }.sheet(isPresented: self.$showingAddGame) {
                                AddGameView().environment(\.managedObjectContext, self.moc)
                        }
                        Image("Game Goals App Logo")
                        .resizable()
                        .frame(width: 100, height: 100)
                        .padding(.leading, (geometry.size.width / 2.0) + -160)
                        .padding(.bottom, -50)
                    }, trailing:
                        EditButton()
                            .padding(.top, 50)
                            .foregroundColor(Color.yellow)
                            )
            }
        }
    }

    func removeGames(at offsets: IndexSet) {
        for index in offsets {
            let game = games[index]
            moc.delete(game)
        }
        try? moc.save()
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        let newGame = Game(context: context)
        newGame.gameName = "Apex Legends"
        newGame.gameDescription = "Maybe this will work"
        return ContentView().environment(\.managedObjectContext, context)
    }
}

AddGameView.swift

import SwiftUI
import CoreData


struct AddGameView: View {

    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: Game.entity(), sortDescriptors: []) var games: FetchedResults<Game>
    @Environment(\.presentationMode) var presentationMode

    @State private var gameName = ""
    @State private var gameDescription = ""
    @State private var showingAlert = false

    var body: some View {
        Form {
            Section {
                TextField("Game Name", text: $gameName)
                TextField("Game Description", text: $gameDescription)
            }
            HStack {
                Button("Add Game") {
                    let newGame = Game(context: self.moc)
                    newGame.gameName = self.gameName
                    newGame.gameDescription = self.gameDescription

                    do {
                        try self.moc.save()
                        self.presentationMode.wrappedValue.dismiss()
                    } catch {
                        print("Whoops! \(error.localizedDescription)")
                    }

                }
                Button(action: {
                    self.presentationMode.wrappedValue.dismiss()
                }) {
                    Text("Cancel")
                }
                .padding(10)
                .foregroundColor(Color.white)
                .background(Color.red)
            }
        }
    }
}

struct AddGameView_Previews: PreviewProvider {
    static var previews: some View {
        AddGameView()
    }
}

I've searched all over so if there is something out there that I've missed as far as a stackoverflow post, please link it as I'd like to not only fix this but understand why.

andywalt
  • 109
  • 1
  • 13

1 Answers1

0

Your Cancel button is not creating an empty object. The problem is that the whole row in your form that has Add and Cancel buttons is interactive and triggers actions of your both buttons.

I have found an answer here: https://stackoverflow.com/a/59402642/12315994 To keep your current layout you need to simply add one line to each of your buttons:

.buttonStyle(BorderlessButtonStyle())

After this, only taping on each button will trigger actions. Form's row with the buttons will not be clickable.

There are 2 other solutions. Both are to move your buttons out of Form.

Solution 1 is to move buttons to NavigationBarItems like this:

import SwiftUI import CoreData

struct AddGameView: View {
    
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: Game.entity(), sortDescriptors: []) var games: FetchedResults<Game>
    @Environment(\.presentationMode) var presentationMode
    
    @State private var gameName = ""
    @State private var gameDescription = ""
    @State private var showingAlert = false
    
    var body: some View {
        NavigationView {
            VStack {
                Form {
                    Section {
                        TextField("Game Name", text: $gameName)
                        TextField("Game Description", text: $gameDescription)
                    }
                    
                }
                
            }
            .navigationBarItems(
                leading:
                Button(action: {
                    self.presentationMode.wrappedValue.dismiss()
                }) {
                    Text("Cancel")
                }
                .padding(10)
                .foregroundColor(Color.white)
                .background(Color.red)
                ,
                
                trailing:
                Button(action: {
                    let newGame = Game(context: self.moc)
                    newGame.gameName = self.gameName
                    newGame.gameDescription = self.gameDescription
                    
                    do {
                        try self.moc.save()
                        self.presentationMode.wrappedValue.dismiss()
                    } catch {
                        print("Whoops! \(error.localizedDescription)")
                    }
                }) {
                    Text("Add Game")
                }
            )
        }
        
        
    }
}

Solution 2 Is to move buttons out of Form and move them to the bottom of the screen. Like this:

import SwiftUI
import CoreData


struct AddGameView: View {
    
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: Game.entity(), sortDescriptors: []) var games: FetchedResults<Game>
    @Environment(\.presentationMode) var presentationMode
    
    @State private var gameName = ""
    @State private var gameDescription = ""
    @State private var showingAlert = false
    
    var body: some View {
        VStack {
            Form {
                Section {
                    TextField("Game Name", text: $gameName)
                    TextField("Game Description", text: $gameDescription)
                }
                
            }
            
            HStack {
                Button(action: {
                    let newGame = Game(context: self.moc)
                    newGame.gameName = self.gameName
                    newGame.gameDescription = self.gameDescription
                    
                    do {
                        try self.moc.save()
                        self.presentationMode.wrappedValue.dismiss()
                    } catch {
                        print("Whoops! \(error.localizedDescription)")
                    }
                    
                }) {
                    Text("Add Game")
                }
                
                
                Button(action: {
                    self.presentationMode.wrappedValue.dismiss()
                }) {
                    Text("Cancel")
                }
                .padding(10)
                .foregroundColor(Color.white)
                .background(Color.red)
            }
            
        }
        
        
    }
}

Both options are better than your current layout from the UX point of view, because buttons are now in more standard locations. Especially version 1 is a more standard way of presenting buttons like this in iOS.

mallow
  • 2,368
  • 2
  • 22
  • 63