0

I am trying to use the swipeActions SwiftUI modifier setup as displayed in the code below but the swipe action gets disabled as seen on this gif:
enter image description here

struct ContentView: View {
@ObservedObject var viewModel: ViewModel

var body: some View {
    if viewModel.items.count > 0 {
        ZStack {
            List {
                ForEach(viewModel.items, id: \.self) { item in
                    Text(item)
                        .swipeActions {
                            Button {
                                viewModel.removeAction(item: item)
                            } label: {
                                Text("Remove")
                            }
                            .tint(.orange)
                        }
                }
            }
            
        }
    } else {
        ProgressView()
            .foregroundColor(.accentColor)
            .scaleEffect(2)
    }
}

In the view model after the first swipe, I would reload the list from the API (the sample code just mocks a delay):

extension ContentView {
class ViewModel: ObservableObject {
    @Published var items: [String]
    
    init(items: [String]) {
        self.items = items
    }
    
    func removeAction(item: String) {
        if let index = items.firstIndex(where: { $0 == item }) {
            items.remove(at: index)
        }
        
        let itemsSaved = items
        items = []
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            self.items = itemsSaved
        }
    }
}

Expected behaviour: the reloaded rows do not have a space view at the beginning of each row, and the rows can be swiped as before.

Actual behaviour: each rows has a space view at the beginning of the row, you cannot swipe the rows as before.

enter image description here

I created a sample project also: code and additional video.

Any idea if there is workaround?

Thanks.

Zsolt
  • 3,648
  • 3
  • 32
  • 47

1 Answers1

0

I am still not sure why your implementation is causing this behavior, other than that you are completely switching between two separate views(Zstack vs. ProgressView). My suspicion is that the change back and forth is simply putting the List into some weird state. The fix, however, is simple; put the conditional inside of the ZStack:

struct ContentView: View {
    @ObservedObject var viewModel: ViewModel

    var body: some View {
        ZStack {
             // Move the conitional inside of the ZStack.
             // Always use .isEmpty for this sort of test. Faster and
             // less resources than count
             if !viewModel.items.isEmpty {
                  List {
                      // I made Item an Identifiable struct. Deleting items
                      // identified as .self can lead to issues in a ForEach
                      ForEach(viewModel.items) { item in
                           Text(item.name)
                               .swipeActions {
                                    Button {
                                        viewModel.removeAction(item: item)
                               } label: {
                                    Text("Remove")
                               }
                                    .tint(.orange)
                           }
                      }
                  }
             } else {
                  Text("Progress View")
             }
         }
    }
}

extension ContentView {
    class ViewModel: ObservableObject {
        @Published var items: [Item]
        
        init(items: [Item]) {
            self.items = items
        }
        
        func removeAction(item: Item) {
            if let index = items.firstIndex(where: { $0 == item }) {
                items.remove(at: index)
            }
            
            let itemsSaved = items
            items = []
            
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                self.items = itemsSaved
            }
        }
    }
}

// I made this as a data model. 
struct Item: Identifiable, Hashable {
    var id = UUID()
    var name: String
}
Yrb
  • 8,103
  • 2
  • 14
  • 44
  • Good point about `empty`. I seem to forget that sometimes. – Zsolt May 11 '22 at 15:47
  • Thanks @Yrb. I played around with it myself, and got to the same conclusion, but I also have other conditionals (if/else) that I cannot move into the `ZStack`. E.g. I might have to show an error from the API. I wonder if there is something that still can trigger the reset of the `List` so that restores the broken state. – Zsolt May 11 '22 at 15:51
  • The other thing I found that fixed it was simply not emptying the data. What you have done here doesn't make a lot of sense. You delete the single item, then delete ALL the items and then reload them. If you didn't delete them all, and just let the update update them, you don't have the issue. I assumed this was an MRE, so I don't question the logic much when trying to answer unless it seems integral tot he answer. – Yrb May 11 '22 at 16:39
  • Yeah, thanks @Yrb. That back and forth doesn't make sense in this sample project, but in the app I am working on it does with the search/API call/remove/add functionality. – Zsolt May 12 '22 at 12:01