2

I am trying to use an ActionSheet to manipulate items of a List. How can I call a function (in this example deleteItem) that is part of the data model, using an ActionSheet and manipulte the selected item, similar to what .onDelete does?

My view presents items from a model using the following code:

struct ItemManager: View {
    @ObservedObject var model: ItemModel

    var body: some View {
        List {
            ForEach(model.items) { item in
                ItemCell(item: item)
            }
            .onDelete { self.model.deleteItem(at: $0) }
        }
    }
}

struct ItemCell: View {
    var item: Item
    @State private var isActionSheetVisible = false
    private var actionSheet: ActionSheet {
        let button1 = ActionSheet.Button.default(Text("Delete")){
            self.isActionSheetVisible = false
        }
        let button2 = ActionSheet.Button.cancel(){
            self.isActionSheetVisible = false
        }
        let buttons = [button1, button2]
        return ActionSheet(title: Text("Actions"), buttons: buttons)
    }

    var body: some View {
        VStack(alignment: .leading) {
            Button(action: {
                self.isActionSheetVisible = true
            }) {
                Text(item.title).font(.headline)
            }.actionSheet(isPresented: self.$isActionSheetVisible) {
                self.actionSheet
            }
        }
    }
}

My model has some simple properties and a function that deletes items from the collection:

struct Item: Identifiable, Equatable  {
    let title: String
    var id: String {
        title
    }
}

class ItemModel: ObservableObject {
    @Published var items: [Item] = [Item(title: "temp.1"), Item(title: "temp.2")]
    public func deleteItem(at indices: IndexSet) {
        indices.forEach { items.remove(at: $0) }
    }
}

extension Item {
    static let previewItem = Item(title: "temp.3")
}

Update: Added Equatable in the Item declaration to comform.

jiko
  • 146
  • 1
  • 9

1 Answers1

1

You could try passing the ItemModel to the ForEach() like so:

ForEach(model.items) { item in
    ItemCell(item: item, model: self.model)
}

Then in your ItemCell you can:

struct ItemCell: View {
    var item: Item
    var model: ItemModel // Add the model variable

    @State private var isActionSheetVisible = false

    private var actionSheet: ActionSheet {
        let button1 = ActionSheet.Button.default(Text("Delete")) {
            // Get the index
            if let index = self.model.items.firstIndex(of: self.item) {
                // Delete the item based on the index
                self.model.items.remove(at: index)

                // Dismiss the ActionSheet
                self.isActionSheetVisible = false
            } else {
                print("Could not find item!")
                print(self.item)
            }
        }
    }
}
gotnull
  • 26,454
  • 22
  • 137
  • 203
  • Thank you very much. It worked like a charm! I had to add ```Equatable``` in the ```Item``` definition to make it work. I find it quite surprising that the sender is not transferred to children's items and we have to go through all this to find the item. On a separate note, there is an issue though with the ActionSheet which does not dismiss automatically after the parent item is deleted. I guess that since we remove the item from the list the ```$isActionSheetVisible``` is not updated. Any ideas on how to fix this? – jiko Dec 10 '19 at 15:57
  • @jiko The dismiss of the `ActionSheet` seems to be working fine for me. One thing you could do (I've updated the answer) is to dismiss the `ActionSheet` just after the removal of the item. – gotnull Dec 10 '19 at 22:53