每次尝试将项目添加到分组项目时,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/UITableViewSupport.m:1311 (lldb) 在线:每次尝试将项目添加到分组项目时,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
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)
}
}