0

Is the following the best way to get a List to work properly with .swipeActions? It appears to work (at least until Apple comes up with a better way to handle this) but I'm not sure that it's a sound idea.

So, I'm using a List that refers to a struct in my model and I wanted to be able to use swipeActions to delete a client (swipe left) with a confirmationDialog and to also use swipeActions to edit a client (swipe right) using a .sheet. The only way that I could get this to work so that it would delete or edit the correct "row" in the list was to use a temporary variable to store the "row" and to also have a separate view that contains my NavigationLink, .swipeActions, .sheet, etc. For reference, I used a combination of ideas from @Esera and @ScottM from post SwiftUI `swipeActions` and `confirmationDialog` delete the wrong item

I have more testing to do but this seems to work for iPhone. If anyone has a better way to do this, I would definitely be interested in hearing about it... Also, if you see any issues with the way I'm handling this or find any bugs, please let me know.

Here is the relevant code... I took out some formatting stuff for the sake of simplicity so it's not going to match the video clip exactly.

ClientList structure ->

struct ClientList: Codable, Identifiable {
@DocumentID var id: String?
var firstName: String
var lastName: String
var notes: String
var owner: String
var clientDocPath: String

}

Main ClientListView ->

struct ClientListView: View {
@ObservedObject var model: MovieModel

@State var isPresentedTrailing = false
@State var isPresentedLeading = false
@State var isPresentedAbout = false
@State var oldFavorite = false

var body: some View {
    ZStack {
        Color("maroon")
        VStack {
            Text("Client List")
            Text("Swipe right to edit item... Swipe left to delete an item... ")
            List($model.myClientList) { $client in
                //..separate view to get swipeActions to work right
                ClientNavView(model: model, client: $client)
            }  
        }
        .listStyle(.plain)
        .sheet(isPresented: $isPresentedTrailing, onDismiss: {
            //
        }, content: {
            AddClient(model: model, count: model.myClientList.count)
        })
        .sheet(isPresented: $isPresentedAbout, onDismiss: {
            //
        }, content: {
            SupportView()
        })
    }
    .accentColor(.black)
    .navigationBarTitle(Text("Clients"), displayMode: .inline)
    .navigationBarItems(
        leading:  AboutLogoutOptions(model: model, isPresentedLeading: $isPresentedLeading, isPresentedAbout: $isPresentedAbout),
        trailing:  Button(action: { self.isPresentedTrailing.toggle() }) {
            Text("Add")
        }
            .buttonStyle(.bordered)
            .tint(Color("ocean"))
    )
    .onAppear {
        model.fetchClients()
     }
    .navigationViewStyle(.stack)
}

Separate ClientNavView (referenced inside of List in ClientListView) ->

struct ClientNavView: View {
@ObservedObject var model: MovieModel

@Binding var client: ClientList

@State var oldFavorite = false
@State var editToggle = false
@State var deleteToggle = false
@State var tempClientToDelete: ClientList? = nil
@State var tempClientToEdit: ClientList? = nil

var body: some View {
    NavigationLink {
        ContentView2(model: model, clientDocPath: client.clientDocPath, clientFirstName: client.firstName, clientLastName: client.lastName)
    } label: {
        ClientListRowView(model: model, client: $client)
    }
    .swipeActions(edge: .trailing) {
        Button {
            print("client to delete = \(client.firstName)\(client.lastName)")
            tempClientToDelete = client
            deleteToggle = true
        } label: {
            Image(systemName: "trash.fill")
        }.tint(.red)
    }
    .confirmationDialog("Delete???", isPresented: $deleteToggle){
        Button("Delete", role: .destructive){
            if let deleteClient = tempClientToDelete {
                print("Actually deleting: \(deleteClient)")
                deleteToggle = false
                tempClientToDelete = nil
                model.deleteClient(clientDocPath: deleteClient.clientDocPath, clientToDelete: deleteClient)
            }
        }
        Button("Cancel", role: .cancel){}
    }message: {
        Text("""
         Are you sure you want to delete this client? Deleting this client will delete ALL other data including Contacts and Items. This action CANNOT be undone.
        """
        )
    }
    .swipeActions(edge: .leading) {
        Button {
            print("client to edit = \(client.firstName)\(client.lastName)")
            tempClientToEdit = client
            editToggle = true
        } label: {
            Image(systemName: "pencil")
        }.tint(.cyan)
    }
    .sheet(isPresented: $editToggle, onDismiss: {
        editToggle = false
        tempClientToEdit = nil
    }, content: {
        if let editClient = tempClientToEdit {
            EditClient(model: model, client: editClient)
        }
    })
    .onAppear{
        tempClientToEdit = client
    }
    
}

"Row" - ClientListRowView (referenced in ClientNavView) ->

struct ClientListRowView: View {

@ObservedObject var model: MovieModel
@State var editToggle = false
@Binding var client: ClientList

var body: some View {
    VStack(alignment: .leading, spacing: 10){
        HStack {
            Text("\(client.firstName )")
            Text("\(client.lastName)")
            Spacer()
        }
        Text("\(client.notes)")
            .italic()
            .font(Font.system(.body))
            .foregroundColor(.gray)
            .padding(.top, 2)
            .padding(.leading, 80)
        Spacer()
    }
    .padding(.leading, 2)
    .foregroundColor(.indigo)
    .font(Font.custom("ChalkboardSE-Bold", size: 18))
}

}

Delete 1 Delete 2 Delete 3 Edit 1

Video Clip Demo

KatM
  • 57
  • 11
  • Wrong usage, next should be helpful https://stackoverflow.com/a/63217450/12299030. – Asperi Aug 06 '22 at 18:35
  • @Asperi - Sorry, but I'm not using a LazyVGrid. I'm using a List. So, I'm not really understanding what you're getting at by saying "wrong usage". Can you elaborate more? Are you saying that I should be using a LazyVGrid instead of a List??? – KatM Aug 06 '22 at 19:11
  • sheet can be put on each row of ForEach if using item: instead of isPresented. For confirmationDialog there is not this possibility, that is why you need to save the current item and have the confirmation dialog set on ForEach not on row. – Ptit Xav Aug 07 '22 at 07:55
  • @PtitXav I thought of that also initially but I don't think I can use a ForEach in my particular case because I'm using a struct for my "client" data and then making Firestore calls to retrieve/update/delete data. I didn't see it in the Firestore doc but can I somehow use a ForEach in this way with a struct? I'm pretty new to SwiftUI... So, I'm just wondering... – KatM Aug 07 '22 at 15:10
  • It only depends on how your model is defined. SwiftUI may reload views if an item of a published array in the model is added/removed/moved. As you didn’t show the model it is difficult to help more. – Ptit Xav Aug 07 '22 at 15:41
  • @PtitXav I do have a Published var that points to this struct/array. In my code above, I'm using "List($model.myClientList) { $client in". I did this based on examples that I saw for Firestore. I guess my question (and I obviously need to do more research) is how to use ForEach instead or in conjunction with "List($model.myClientList) { $client in". Do you have any links to any examples that might help? – KatM Aug 07 '22 at 16:55
  • @PtitXav Thanks for your help btw. At least I know I'm on the right track with some of my thinking :) – KatM Aug 07 '22 at 16:56
  • « List { ForEach () {… » Basically List is also a view that hold its contents in a vertical list. It can contains a ForEach, section, text,…. You fo not have to do «List(…) {» . It is just one possibility. – Ptit Xav Aug 07 '22 at 18:08
  • @PtitXav So, I tried changing some things around to eliminate the ClientNavView and to attempt to use a ForEach. While the UI still looked the same, it would not allow me to swipe and "edit" the correct row. The only way I can get this to work is to use the example that I posted above initially. I don't fully understand why it works but until Apple comes up with a better way to handle swipes for things like pulling up a new sheet, I think I'm stuck with this... unless someone has a better way. I'll do more testing and post back if I discover something new. – KatM Aug 08 '22 at 19:56

0 Answers0