每次尝试将项目添加到分组项目时,SwiftUI应用程序都会崩溃

每次尝试将项目添加到分组项目时,SwiftUI应用程序都会崩溃,swift,xcode,core-data,foreach,swiftui,Swift,Xcode,Core Data,Foreach,Swiftui,当我尝试在ForEach循环中按日期对项目排序时,我的应用程序崩溃,并出现以下错误: 2020-03-24 16:55:13.830146+0700列表日期[60035:2135088]*** -[\u UITableViewUpdateSupport中的断言失败 _setupAnimationsForNewlyInsertedCells],/BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore\u Sim/UIKit-3901.4.2

当我尝试在ForEach循环中按日期对项目排序时,我的应用程序崩溃,并出现以下错误:

2020-03-24 16:55:13.830146+0700列表日期[60035:2135088]*** -[\u UITableViewUpdateSupport中的断言失败 _setupAnimationsForNewlyInsertedCells],/BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore\u Sim/UIKit-3901.4.2/UITableViewSupport.m:1311 (lldb)

在线:

class AppDelegate: UIResponder, UIApplicationDelegate {
关于AppDelegate.swift

起初,我的应用程序在模拟器中加载,但当我点击“添加”按钮打开模式窗口并添加新项目后,应用程序立即崩溃并出现错误

我认为func更新或ForEach循环本身存在问题。我已经在代码中标记了哪个可选循环适合我。遗憾的是,这个替代方案没有按日期对项目进行分组。这是我试图在我的应用程序中添加的一项功能

ContentView.swift

import SwiftUI

struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    @State private var date = Date()
    @FetchRequest(
        entity: Todo.entity(),
        sortDescriptors: [
            NSSortDescriptor(keyPath: \Todo.date, ascending: true)
        ]
    ) var todos: FetchedResults<Todo>

    @State private var show_modal: Bool = false

    var dateFormatter: DateFormatter {
        let formatter = DateFormatter()
        formatter.dateStyle = .short
        return formatter
    }

    // func to group items per date
    func update(_ result : FetchedResults<Todo>)-> [[Todo]]{
        return  Dictionary(grouping: result){ (element : Todo)  in
            dateFormatter.string(from: element.date!)
        }.values.map{$0}
    }

    var body: some View {
        NavigationView {
            VStack {
                List {
                    ForEach(update(todos), id: \.self) { (section: [Todo]) in
                        Section(header: Text( self.dateFormatter.string(from: section[0].date!))) {
                            ForEach(section, id: \.self) { todo in
                                HStack {
                                    Text(todo.title ?? "")
                                    Text("\(todo.date ?? Date(), formatter: self.dateFormatter)")
                                }
                            }
                        }
                    }.id(todos.count)

                    // With this loop there is no crash, but it doesn't group items
                    //                      ForEach(Array(todos.enumerated()), id: \.element) {(i, todo) in
                    //                              HStack {
                    //                                  Text(todo.title ?? "")
                    //                                  Text("\(todo.date ?? Date(), formatter: self.dateFormatter)")
                    //                              }
                    //                      }

                }
            }
            .navigationBarTitle(Text("To do items"))
            .navigationBarItems(
                trailing:
                Button(action: {
                    self.show_modal = true
                }) {
                    Text("Add")
                }.sheet(isPresented: self.$show_modal) {
                    TodoAddView().environment(\.managedObjectContext, self.moc)
                }
            )
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        return ContentView().environment(\.managedObjectContext, context)
    }
}
import SwiftUI

struct TodoAddView: View {

    @Environment(\.presentationMode) var presentationMode
    @Environment(\.managedObjectContext) var moc

    static let dateFormat: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        return formatter
    }()

    @State private var showDatePicker = false
    @State private var title = ""
    @State private var date : Date = Date()

    var body: some View {
        NavigationView {

            VStack {
                HStack {
                    Button(action: {
                        self.showDatePicker.toggle()
                    }) {
                        Text("\(date, formatter: Self.dateFormat)")
                    }

                    Spacer()
                }

                if self.showDatePicker {
                    DatePicker(
                        selection: $date,
                        displayedComponents: .date,
                        label: { Text("Date") }
                    )
                        .labelsHidden()
                }

                TextField("title", text: $title)

                Spacer()

            }
            .padding()
            .navigationBarTitle(Text("Add to do item"))
            .navigationBarItems(
                leading:
                Button(action: {
                    self.presentationMode.wrappedValue.dismiss()
                }) {
                    Text("Cancel")
                },

                trailing:
                Button(action: {

                    let todo = Todo(context: self.moc)
                    todo.date = self.date
                    todo.title = self.title

                    do {
                        try self.moc.save()
                    }catch{
                        print(error)
                    }

                    self.presentationMode.wrappedValue.dismiss()
                }) {
                    Text("Done")
                }
            )
        }
    }
}

struct TodoAddView_Previews: PreviewProvider {
    static var previews: some View {
        TodoAddView()
    }
}
我正在使用CoreData。在本例中,有一个名为Todo的实体,具有两个属性:日期(date)、标题(String)


如果有人能帮我解决这个问题,我将不胜感激。或者也可以使用分组项目的替代方法:)

关于灵感,如何使用您的模型,下面是一个简化的示例

import SwiftUI // it imports all the necessary stuff ...
我们的任务需要一些符合可识别的数据结构(这将有助于SwiftUI识别每个动态生成的ToDoView)

具有所有基本功能的简单模型可以定义为

class ToDoModel: ObservableObject {
    @Published var todo: [ToDo] = []

    func groupByDay()->[Int:[ToDo]] {
        let calendar  = Calendar.current
        let g: [Int:[ToDo]] = todo.reduce([:]) { (res, todo) in
            var res = res
            let i = calendar.ordinality(of: .day, in: .era, for: todo.date) ?? 0
            var arr = res[i] ?? []
            arr.append(todo)
            arr.sort { (a, b) -> Bool in
                a.date < b.date
            }
            res.updateValue(arr, forKey: i)
            return res
        }
        return g
    }
}

11.3中还需要其他“技巧”,请参阅以了解更多详细信息。

首先,ForEach不是任何类型的循环。。。您必须改变主意,尝试理解SwftUI的真正声明性语法的含义以及如何使用它。第二步是将业务逻辑与用户界面隔离,将所有功能移到模型中。。。接下来的生活将更加轻松,您的代码将具有逻辑性并且易于维护。没有简单的方法可以将当前的UIKit代码更改为SwiftUI。感谢您的反馈。我不明白这个“第二步”的部分。我从一开始就在SwiftUI中编写这个应用程序。我不熟悉斯威夫特。我不知道如何使用ForEach对项目进行分组。我在这里找到了一个解决方案:但在将其与我的代码一起使用后,它会导致我的应用程序因错误而崩溃。我想这是因为使用了.sheet。它看起来不是一个SwiftUI解决方案,但我还没有找到更好的解决方案。如果你知道,请分享你的知识。谢谢:)按某些参数分组数据与UI任务无关,所以请在您的模型中进行分组。完成后,在UI中显示结果。最糟糕的想法是调用ForEach构造函数作为另一个ForEach构造函数的一部分。即使在某些情况下is可能会起作用,也要避免这种情况。避免(如果可用)id:\self作为ForEach构造函数的一部分。很难修复您的代码,只是因为我们对您的数据一无所知……谢谢。如何在模型中实现它?您的意思是将Codegen从类定义(我目前使用的)更改为手动/无,并在其中添加一些代码吗?你能告诉我一些关于你的解决方案的教程吗?如果将来我想用UI动态地改变分组呢?现在我想按日期对项目进行分组。但是,如果我想允许用户从一个日期更改为另一个类别,该怎么办呢。通过数据模型处理分组是否比通过UI处理分组更好?避免将id:\self作为ForEach构造函数的一部分的原因是什么?我在很多例子中都看到了?首先,非常感谢您提出这个“切换”问题。4个月前我发现了这个bug:我不知道这个解决方案。我已经立即向苹果提交了这个bug。他们几天前联系过我,说不能重现这个问题。我已经向他们发送了仍然存在问题的简化代码和问题的屏幕截图,希望他们能尽快解决,但是。。。现在已经4个月了,它仍然不起作用。在iOS 13.1模拟器上运行良好,你知道吗?@mallow它似乎在Xcode 11.4版(11E146)上运行,他们昨天发布了Xcode 11.4版(11E146)来回答我的问题。。。谢谢你的时间,你的代码很有效。而且看起来也不错。但由于我是Swift的新手,我需要更多的时间来测试它。我的原始代码与.sheet modal不兼容。它使用了CoreData。我仍然不知道如何翻译代码以用于核心数据。在我完成之后,我将处理模态。所以我尝试注释掉结构ToDo,并创建一个CD实体。但还是不知道下一步该怎么办。需要更多的时间,抱歉:/我仍在试图找到一个解决方案,如何使用CoreData的答案。如果有任何建议,我将不胜感激。
class ToDoModel: ObservableObject {
    @Published var todo: [ToDo] = []

    func groupByDay()->[Int:[ToDo]] {
        let calendar  = Calendar.current
        let g: [Int:[ToDo]] = todo.reduce([:]) { (res, todo) in
            var res = res
            let i = calendar.ordinality(of: .day, in: .era, for: todo.date) ?? 0
            var arr = res[i] ?? []
            arr.append(todo)
            arr.sort { (a, b) -> Bool in
                a.date < b.date
            }
            res.updateValue(arr, forKey: i)
            return res
        }
        return g
    }
}
struct ContentView: View {
    @ObservedObject var model = ToDoModel()
    var body: some View {
        VStack {
            Button(action: {
                let todos: [ToDo] = (0 ..< 5).map { (_) in
                    ToDo(date: Date(timeIntervalSinceNow: Double.random(in: 0 ... 500000)), task: "task \(Int.random(in: 0 ..< 100))")
                }
                self.model.todo.append(contentsOf: todos)
            }) {
                Text("Add 5 random task")
            }.padding()

            Button(action: {
                self.model.todo.removeAll { (t) -> Bool in
                    t.done == true
                }
            }) {
                Text("Remove done")
            }.padding()

            List {
                ForEach(model.groupByDay().keys.sorted(), id: \.self) { (idx) in
                    Section(header: Text(self.sectionDate(section: idx)), content: {
                        ForEach(self.model.groupByDay()[idx]!) { todo in
                            ToDoView(todo: todo).environmentObject(self.model)
                        }
                    })
                }
            }
        }
    }

    // this convert back section index (number of days in current era) to date string
    func sectionDate(section: Int)->String {
        let calendar = Calendar.current
        let j = calendar.ordinality(of: .day, in: .era, for: Date(timeIntervalSinceReferenceDate: 0)) ?? 0
        let d = Date(timeIntervalSinceReferenceDate: 0)
        let dd = calendar.date(byAdding: .day, value: section - j, to: d) ?? Date(timeIntervalSinceReferenceDate: 0)
        let formater = DateFormatter.self
        return formater.localizedString(from: dd, dateStyle: .short, timeStyle: .none)
    }

}

struct ToDoView: View {
    @EnvironmentObject var model: ToDoModel
    let todo: ToDo

    var body: some View {
        VStack {
            Text(todoTime(todo: todo)).bold()
            Text(todo.task).font(.caption)
            Text(todo.done ? "done" : "active").foregroundColor(todo.done ? Color.green: Color.orange).onTapGesture {
                let idx = self.model.todo.firstIndex { (t) -> Bool in
                    t.id == self.todo.id
                }
                if let idx = idx {
                    self.model.todo[idx].done.toggle()
                }
            }
        }
    }

    // returns time string
    func todoTime(todo: ToDo)->String {
        let formater = DateFormatter.self
        return formater.localizedString(from: todo.date, dateStyle: .none, timeStyle: .short)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
struct ToDoView: View {
    @EnvironmentObject var model: ToDoModel
    let todo: ToDo
    var idx: Int? {
        self.model.todo.firstIndex { (t) -> Bool in
            t.id == self.todo.id
        }
    }
    var body: some View {

        VStack(alignment: .leading) {
            Text(todoTime(todo: todo)).bold()
            Text(todo.task).font(.caption)

            Text(todo.done ? "done" : "active").foregroundColor(todo.done ? Color.green: Color.orange).onTapGesture {
                self.model.todo[self.idx!].done.toggle()
            }

            // using toggle needs special care!!
            // we have to "remove" it before animation transition
            if idx != nil {
                Toggle(isOn: $model.todo[self.idx!].done) {
                    Text("done")
                }
            }
        }
    }

    // returns time string
    func todoTime(todo: ToDo)->String {
        let formater = DateFormatter.self
        return formater.localizedString(from: todo.date, dateStyle: .none, timeStyle: .short)
    }
}