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

SOLVED: Project 17 Deleting Rows Issue

Forums > 100 Days of SwiftUI

Hi All! I noticed something in project 17 (Flashzilla) that I was 1. wondering if anyone else noticed and 2. can't figure out a solution to.

In this project, there's an EditCards view that appears when the Plus button in ContentView is pressed. This is the screen where you can add and delete individual index cards to and from the array of index cards that you're working with. Deleting cards works as expected if you use swipe to delete and immediately press the delete button, but if you swipe to reveal the delete button and don't immediately press it then the delete button disappears in about half a second. The same goes for if you use an EditButton. In other words, it feels pretty glitchy and not user friendly.

I downloaded and ran Paul's code for this project, and this issue is also present in his code, so it seems like a bug he may have missed before putting the project together.

Here's my code for the EditCards view:

struct EditCards: View {
    @Environment(\.dismiss) var dismiss
    // Custom struct for using the documents directory
    @State private var cards = DataManager.load()
    @State private var newCardPrompt = ""
    @State private var newCardAnswer = ""

    var body: some View {
        NavigationView {
            List {
                Section("Add New Card") {
                    TextField("Question", text: $newCardPrompt)
                    TextField("Answer", text: $newCardAnswer)
                    Button("Add Card", action: addCard)
                }

                // This is where you can see created cards and delete them
                Section {
                    // I tried using ForEach(cards) instead of ForEach(0..<cards.count)
                    // But the issue still persists
                    ForEach(0..<cards.count, id: \.self) { index in
                        VStack(alignment: .leading) {
                            Text(cards[index].prompt)
                                .font(.headline)

                            Text(cards[index].answer)
                                .foregroundColor(.secondary)
                        }
                    }
                    .onDelete(perform: removeCard)
                }
            }
            .navigationTitle("Edit Cards")
            .toolbar {
                Button("Done", action: done)
            }
            .listStyle(.grouped)
        }
    }

    func addCard() {
        let trimmedPrompt = newCardPrompt.trimmingCharacters(in: .whitespaces)
        let trimmedAnswer = newCardAnswer.trimmingCharacters(in: .whitespaces)
        guard trimmedPrompt.isEmpty == false && trimmedAnswer.isEmpty == false else { return }

        let newCard = Card(prompt: trimmedPrompt, answer: trimmedAnswer)
        cards.insert(newCard, at: 0)
        DataManager.save(cards)

        newCardPrompt = ""
        newCardAnswer = ""
    }

    // Method for removing cards
    func removeCard(at offsets: IndexSet) {
        cards.remove(atOffsets: offsets)
        DataManager.save(cards)
    }

    func done() {
        dismiss()
    }
}

My Card type:

struct Card: Codable, Identifiable, Hashable {
    var id = UUID()
    let prompt: String
    let answer: String

    static let example = Card(prompt: "Who played the 13th Doctor in Doctor Who?", answer: "Jodie Whittaker")
}

2      

I replaced this line in your code above:

// @State private var cards = DataManager.load()
// replace above with this
@State private var cards = [Card.example, Card.example, Card.example]  // dummy array of cards.

I also commented out the calls to DataManager.save()

// Comment these to test.
// DataManager.save(cards)

Then I ran your code in the simulator. Works fine! Swipe to delete will delete the fake row with a full swipe. A partial swipe shows the delete button. But it stays put and doesn't exhibit the behavior you mention.

So, if this debugging session provides you with any comfort, it's that the EditCards view may not be the culprit. In your FlashCardApp view change your code to start with the EditCards() view. Test the results for yourself.

@main
struct FlashCardApp: App {
    var body: some Scene {
        WindowGroup {
            EditCards()  // <------ Change this to focus on EditCards() view
        }
    }
}

Let us know what you find!

2      

Thanks a lot for the response!

Like you suggested, I tried making EditCards to be the main view that appears within the FlashCardApp file and it worked! However, I also tried commenting out the call to the DataManager.save() method and adding in a Dummy array of cards like you suggested without adding EditCards to the FlashCardApp file and then the issue still persisted. In other words, it seems I don't have to follow all of your suggestions to solve the problem, I only need to change up the FlashCardApp app file in the way that you described.

Would you mind explaining why this works? I'm glad this solution solves the problem, but it doesn't seem like a very ideal fix, especially since it means I don't get any control over the view that's shown when the app starts up. I just don't understand how changing the order in which a view presents itself could affect whether or not deleting rows within that view works properly. I'm guessing there's more at play here than just changing the order that the view shows up, but regardless it seems quite strange.

2      

It is due to the timer being published after saving a set of cards, even when in the edit mode, and interferring with the delete. It should be cancelled during editing.

Using Paul's solution as the baseline.

in ContentView

change the timer definition to be

@State private var timer = Timer.publish(every: 1, on: .main, in: .common)

Add this function above the var body:

private func pauseTimerWhilstEditing() {
    self.timer.connect().cancel()
    showingEditScreen = true
}

Change the operation for the add button to be

Button {
    pauseTimerWhilstEditing()
} label: {

Add the following lines to the end of resetCards()

self.timer = Timer.publish(every: 1, on: .main, in: .common)
_ = self.timer.connect()

2      

I wasn't really trying to solve your problem. I was just isolating a portion of your solution and testing that one piece.

Since I didn't have access to your data, or your JSON loading code, I didn't have the DataManager object. Instead, i cheated. I created a dummy array of Card objects using Card.example from the Card struct.

Additionally, since I didn't have the DataManager object, I couldn't call the DateManager.save() function. I wasn't really interested in saving data to device anyway. I was trying to replicate your interface issue. So, I commented out the save function. Adding and deleting from the array still works. The isolated code just doesn't save it to device.

I isolated the EditCard view from the rest of your application. My observation is the EditCard view seems to work fine! I had hoped you'd try isolating it and verifying the results for yourself.

If you eliminate EditCard as the source of your interface issues, then you'll need to look for the problem in other views.

Update:

@greenamber found the issue in another part of your code. I was just sharing a technique for isolating and testing the view where you thought you had the problem.

2      

@Obelix That makes a lot of sense! I hadn't thought to troubleshoot that way (still pretty new here!) so it really didn't occur to me that that's what you were trying to get me to do, sorry for the confusion there!

@Greenamberred Thanks a lot for taking the time! I'm gonna give this a shot

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.