问题描述
我有下面的视图,其中包含包含已完成/未完成部分的任务列表,但在尝试编辑列表中的一项时,始终选择该部分的最后一项,甚至不允许删除。它看起来在测试部分内的所有记录都是一条记录时,因此删除幻灯片不起作用,并且在编辑时获取最后一条记录。在完整代码下方,您可以尝试帮助我确定问题所在。
import SwiftUI
import CoreData
import UserNotifications
struct ListView: View {
@Environment(\.presentationMode) var presentationMode
@Environment(\.managedobjectContext) var viewContext
@FetchRequest(fetchRequest: Task.taskList(),animation: .default) private var items: FetchedResults<Task>
@State var isAddFormPresented: Bool = false
@State var taskToEdit: Task?
init(predicate: nspredicate?,sortDescriptor: NSSortDescriptor) {
let fetchRequest = NSFetchRequest<Task>(entityName: Task.entity().name ?? "Task")
fetchRequest.sortDescriptors = [sortDescriptor]
if let predicate = predicate {
fetchRequest.predicate = predicate
}
_items = FetchRequest(fetchRequest: fetchRequest)
UITableViewCell.appearance().backgroundColor = .white
UITableView.appearance().backgroundColor = .white
}
var body: some View {
let data = groupedEntries(self.items)
vstack(alignment: .center) {
if data.isEmpty {
Spacer()
Image("empty")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 250,height: 250)
.clipShape(Circle())
.overlay(Circle().stroke(Color.pink,linewidth: 2))
Spacer()
} else {
List {
ForEach(data,id: \.self) { (section: [Task]) in
Section(header: Text(section[0].isComplete == false ? "Incomplete" : "Completed")
.font(.body)
.foregroundColor(section[0].isComplete == false ? Color.pink : Color.green)
){
self.completedView(section: section)
}
.sheet(item: $taskToEdit,ondismiss: {
self.taskToEdit = nil
}) { task in
TaskFormView(
taskToEdit: task,name: task.name!,taskDetails: task.taskDetails ?? "",important: TaskType2(rawValue: task.important ?? "") ?? .none,urgent: TaskType(rawValue: task.urgent ?? "") ?? .none,secondaryCategory: Category(rawValue: task.secondaryCategory ?? "") ?? .other,isComplete: task.isComplete,dateAdded: task.dateAdded ?? Date()
)
}
}
}
.listStyle(SidebarListStyle())
HStack {
Spacer()
Button(action: addTapped) {
Image(systemName: "plus.circle.fill")
.resizable()
.frame(width: 30,height: 30)
.shadow(radius: 20)
}
.padding(.trailing,40)
.padding(.bottom,24)
.accentColor(Color(UIColor.systemRed))
}
}
}
.frame(maxWidth: .infinity,maxHeight: .infinity)
.background(Color(UIColor.systemYellow).opacity(0.5))
.edgesIgnoringSafeArea(.all)
.sheet(isPresented: $isAddFormPresented) {
TaskFormView()
.environment(\.managedobjectContext,PersistenceController.shared.container.viewContext)
}
}
func groupedEntries(_ result : FetchedResults<Task>) -> [[Task]] {
return Dictionary(grouping: result) { (element : Task) in
element.isComplete
}
.values.sorted() { $0[0].dateAdded! < $1[0].dateAdded! }
}
func completedView(section: [Task]) -> some View {
ForEach(section,id: \.id) { task in
Button(action: {
taskToEdit = task
}) {
HStack {
CategoryRowView(category: Category(rawValue: task.secondaryCategory!)!,dateAdded: task.dateAdded!)
vstack(alignment: .leading) {
HStack {
if task.isComplete != false {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.pink)
.padding(.top,10)
.padding(.leading,5)
}
Text(task.name!)
.font(.system(size: 20,weight: .regular,design: .default))
.foregroundColor(.primary)
.padding(.top,10)
.padding(.leading,5)
}
Spacer()
HStack {
Image(systemName: "tag.fill")
.foregroundColor(.secondary)
Text(task.important == "none" ? "Not Important" : "Important")
.font(.system(size: 12,design: .default))
.foregroundColor(.secondary)
.padding(.vertical)
Text(task.urgent == "none" ? "Not Urgent" : "Urgent")
.font(.system(size: 12,design: .default))
.foregroundColor(.secondary)
.padding(.vertical)
}
.padding(.leading,5)
}
.padding(.trailing,2)
Spacer()
}
.frame(height: 140)
.background(Color(.systemBackground))
.cornerRadius(10)
.shadow(color: .black,radius: 4,x: 0,y: 0)
.padding(.vertical,5)
}
}
.onDelete { row in
deleteEntry(row: row,in: section)
}
}
func addTapped() {
isAddFormPresented.toggle()
}
private func onReturnTapped() {
self.presentationMode.wrappedValue.dismiss()
}
func deleteEntry(row: IndexSet,in section: [Task]) {
let task = section[row.first!]
UNUserNotificationCenter.current().removeAllDeliverednotifications()
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [task.id!.uuidString])
viewContext.delete(task)
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError),\(nsError.userInfo)")
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "dd MMM"
return formatter
}()
解决方法
这是您的 View
的简化版本,因此您可以了解 NSFetchedResultsController
如何完成这项工作。您需要一个 List
才能滑动要删除的行。
import SwiftUI
import CoreData
class TaskListViewModel: ObservableObject {
let persistenceController = PersistenceController.previewAware()
@Published var fetchedResultsController: NSFetchedResultsController<Task>?
init() {
setupController()
}
func setupController() {
do{
fetchedResultsController = try retrieveFetchedController(sortDescriptors: nil,predicate: nil,sectionNameKeyPath: #keyPath(Task.isComplete))
}catch{
print(error)
}
}
func deleteObject(object: Task) {
persistenceController.container.viewContext.delete(object)
save()
}
func save() {
do {
if persistenceController.container.viewContext.hasChanges{
try persistenceController.container.viewContext.save()
objectWillChange.send()
}else{
}
} catch {
print(error)
}
}
}
//MARK: FetchedResultsController setup
extension TaskListViewModel{
func retrieveFetchedController(sortDescriptors: [NSSortDescriptor]?,predicate: NSPredicate?,sectionNameKeyPath: String) throws -> NSFetchedResultsController<Task> {
return try initFetchedResultsController(sortDescriptors: sortDescriptors,predicate: predicate,sectionNameKeyPath: sectionNameKeyPath)
}
private func initFetchedResultsController(sortDescriptors: [NSSortDescriptor]?,sectionNameKeyPath: String) throws -> NSFetchedResultsController<Task> {
fetchedResultsController = getFetchedResultsController(sortDescriptors: sortDescriptors,sectionNameKeyPath: sectionNameKeyPath)
do {
try fetchedResultsController!.performFetch()
return fetchedResultsController!
} catch {
print( error)
throw error
}
}
func getFetchedResultsController(sortDescriptors: [NSSortDescriptor]?,sectionNameKeyPath: String) -> NSFetchedResultsController<Task> {
return NSFetchedResultsController(fetchRequest: getEntityFetchRequest(sortDescriptors: sortDescriptors,predicate: predicate),managedObjectContext: persistenceController.container.viewContext,sectionNameKeyPath: sectionNameKeyPath,cacheName: nil)
}
private func getEntityFetchRequest(sortDescriptors: [NSSortDescriptor]?,predicate: NSPredicate?) -> NSFetchRequest<Task>
{
let fetchRequest: NSFetchRequest<Task> = Task.fetchRequest()
fetchRequest.includesPendingChanges = false
fetchRequest.fetchBatchSize = 20
if sortDescriptors != nil{
fetchRequest.sortDescriptors = sortDescriptors
}else{
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Task.dateAdded),ascending: false)]
}
if predicate != nil{
fetchRequest.predicate = predicate
}
return fetchRequest
}
}
struct TaskListView: View {
@StateObject var vm: TaskListViewModel = TaskListViewModel()
@State var taskToEdit: Task?
var body: some View {
if vm.fetchedResultsController?.sections != nil{
List{
ForEach(0..<vm.fetchedResultsController!.sections!.count){idx in
let section = vm.fetchedResultsController!.sections![idx]
TaskListSectionView(objects: section.objects as? [Task] ?? [],taskToEdit: $taskToEdit,sectionName: section.name).environmentObject(vm)
}
}.sheet(item: $taskToEdit,onDismiss: {
vm.save()
}){editingTask in
TaskEditView(task: editingTask)
}
}else{
Image(systemName: "empty")
}
}
}
struct TaskEditView: View {
@ObservedObject var task: Task
var body: some View {
TextField("name",text: $task.name.bound)
}
}
struct TaskListSectionView: View {
@EnvironmentObject var vm: TaskListViewModel
let objects: [Task]
@State var deleteAlert: Alert = Alert(title: Text("test"))
@State var presentAlert: Bool = false
@Binding var taskToEdit: Task?
var sectionName: String
var body: some View {
Section(header: Text(sectionName),content: { ForEach(objects,id: \.self){obj in
let task = obj as Task
Button(action: {
taskToEdit = task
},label: {
Text(task.name ?? "no name")
})
.listRowBackground(Color(UIColor.systemBackground))
}.onDelete(perform: deleteItems)
})
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
deleteAlert = Alert(title: Text("Sure you want to delete?"),primaryButton: Alert.Button.destructive(Text("yes"),action: {
let objs = offsets.map { objects[$0] }
for obj in objs{
vm.deleteObject(object: obj)
}
//Because the objects in the sections aren't being directly observed
vm.objectWillChange.send()
}),secondaryButton: Alert.Button.cancel())
self.presentAlert = true
}
}
}
struct TaskListView_Previews: PreviewProvider {
static var previews: some View {
NavigationView{
TaskListView()
}
}
}