I am trying to build To-Do app such as Microsoft To-Do App.
There is Row Cell View(Checkbox and Text horizontally) and Custom Checkbox Toggle Style View for the List.
I want to delete Item of clicked Cell View but I couldn't organize "sharing Index"
Content View:
struct ContentView: View {
@Environment(\.modelContext) private var context
@Query(sort: \Note.title) var noteList: [Note]
var body: some View {
List {
ForEach(noteList, id: \.id) { item in
NoteCell(myNote: Note(title: item.title, desc: item.desc))
.sheet(isPresented: $showEditNoteSheet, content: {
EditNoteSheet(noteTitle: item.title, noteDesc: item.desc)
})
}
.onDelete(perform: { indexSet in
for index in indexSet {
deleteItem(index: index)
}
})
}
}
func deleteItem(index: Int){
context.delete(noteList[index])
}
}
Note Cell View:
struct NoteCell: View {
@Environment(\.modelContext) private var modelContext
var myNote: Note
@State var isChecked = false
@State var noteSheet = false
var body: some View {
Button {
noteSheet.toggle()
//Delete action here?
} label: {
HStack {
Toggle(isOn: $isChecked) {
}
.toggleStyle(iOSCheckboxToggleStyle())
Text(myNote.title)
}
.foregroundStyle(.black)
}
.sheet(isPresented: $noteSheet, content: {
AddNoteSheet(noteTitle: myNote.title, noteDesc: myNote.desc)
})
}
}
CustmIOSCheckBoxToggleStyle:
struct iOSCheckboxToggleStyle: ToggleStyle {
func makeBody(configuration: Configuration) -> some View {
// 1
Button(action: {
// 2
configuration.isOn.toggle()
// Delete action here?
}, label: {
HStack {
// 3
Image(systemName: configuration.isOn ? "checkmark.square" : "square")
configuration.label
}
})
}
}
If I understand your question correctly, you want to remove items that have been checked?
If so, there is a way for SwiftData to do that for you. Assuming that each Note
has an isChecked
property, you can add a filter so that only unchecked properties are listed:
@Query(
filter: #Predicate { $0.isChecked == false },
sort: \Note.title
) var noteList: [Note]
Then in your NoteCellView
, the quickest way is to use the Note
's `isChecked property in your action:
struct NoteCellView: View {
// mark your note as Bindable
@Bindable var note: Note
var body: some View {
// ...rest of view omitted...
Toggle(isOn: $note.isChecked) {
// ...etc...
}
}
When you check the toggle, the note object updates, and SwiftData should automatically remove it from the Query collection - and that in turns removes the note from the list.
You may want a bit more of a sophisticated UI – for example, in reminders apps it's not uncommon to delay the removal for a while, giving the user a chance to uncheck the box if they checked it in error. In that case, you may want to keep the isChecked
state on the toggle, and use some form of cancelable task that is triggered to run after a certain amount of time (say, 2 seconds). After that delayed start, you'd set note.isChecked = true
in code. If the user unchecks the box in that task, you cancel the task so the underlying Note
isn't marked as complete after all.
In this option, you don't need to make Note
@Bindable because you're only going to change it through code, not by binding it to a view. This would make your NoteCellView
look something like this:
struct NoteCell: View {
@Environment(\.modelContext) private var modelContext
var myNote: Note
@State var isChecked = false
@State var noteSheet = false
@State var markNoteCompleteTask: Task<Void, Error>?
var body: some View {
Button {
noteSheet.toggle()
//Delete action here?
} label: {
HStack {
Toggle(isOn: $isChecked) {
}
.toggleStyle(iOSCheckboxToggleStyle())
Text(myNote.title)
}
.foregroundStyle(.black)
}
.sheet(isPresented: $noteSheet, content: {
AddNoteSheet(noteTitle: myNote.title, noteDesc: myNote.desc)
})
.onChange(of: isChecked) {
if isChecked {
markNotCompleteTask = Task { @MainActor in
try await Task.sleep(for: .seconds(2))
try await Task.checkCancellation() // don't continue if the task was previously cancelled
withAnimation {
note.isChecked = true
}
}
} else {
// if isChecked is now false, cancel the task
markNoteCompleteTask?.cancel()
}
}
}
}
As long as the @Query
in ContentView
still filters to show only notes that are not checked, after 2 seconds the note will smoothly disappear.