每次尝试将项目添加到分组项目时,SwiftUI应用程序每次都会崩溃 [英] SwiftUI app crashes every time when trying to add item to grouped items

查看:75
本文介绍了每次尝试将项目添加到分组项目时,SwiftUI应用程序每次都会崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

My app crashes and I am getting a following error when I try to sort items by date in a ForEach loop:

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

2020-03-24 16:55:13.830146+0700 list-dates[60035:2135088] *** Assertion failure in -[_UITableViewUpdateSupport _setupAnimationsForNewlyInsertedCells], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore_Sim/UIKit-3901.4.2/UITableViewSupport.m:1311 (lldb)

在线:

class AppDelegate: UIResponder, UIApplicationDelegate {

AppDelegate.swift

of AppDelegate.swift

起初,我的应用程序在模拟器中加载,但是在我点击添加"按钮以打开模式窗口并添加新项之后,该应用程序立即崩溃并显示错误.

At first my app loads in simulator, but after I tap Add button to open modal window and add new item, the app crashes immediately with the error.

我认为func更新或ForEach循环本身存在问题.我已经在代码中标记了哪个替代循环对我有用.遗憾的是,这种替代方式不会按日期对项目进行分组.这是我要在我的应用程序中添加的功能.

I think that there is a problem in the func update or in the ForEach loop itself. I have marked in the code which alternative loop works for me. Sadly, this alternative doesn't group items by dates. And this is a feature I am trying to add in my app.

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)
    }
}

TodoAddView.swift

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.在此示例中,有1个名为Todo的实体,具有2个属性:日期(日期),标题(字符串).

I am using CoreData. In this example there is 1 entity named Todo with 2 attributes: date (Date), title (String).

如果有人可以帮助我解决此错误,我将不胜感激.或将项目分组的替代方法也可以:)

I would be grateful if someone can help me with the bug. Or an alternative to grouping items could work too :)

推荐答案

为获得灵感,如何使用您的模型,此处为简化示例

For inspiration, how to use your model, here is simplified example

import SwiftUI // it imports all the necessary stuff ...

我们需要一些符合Identifiable的数据结构(这将有助于SwiftUI识别每个动态生成的ToDoView)

We need some data structure for our task conforming to Identifiable (this will help SwiftUI to identify each dynamically generated ToDoView)

struct ToDo: Identifiable {
    let id = UUID()
    let date: Date
    let task: String
    var done = false
}

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

Simple model with all basic functionality could be defined as

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
    }
}

那里没有什么特别的,以后我将用一些随机安排的任务来填充它,并在模型中定义一个函数,该函数返回已排序任务数组的字典,其中字典键基于计划的日期(日期和时间)的日期部分.从现在"开始,所有任务将随机安排在0到50000秒的间隔内

There is nothing special there, I will fill it with some randomly scheduled tasks later and I defined in model a function which return dictionary of sorted tasks array, where dictionary key is based on date portion of scheduled Date (date and time). All tasks will be randomly scheduled in interval 0 to 50000 seconds from "now"

Rest是SwiftUI代码,这是自我解释"

Rest is SwiftUI code, which is "self explanatory"

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()
    }
}

如果要使用切换,则必须小心,否则删除分配为完成"的任务将崩溃.

If you like to use toggle, you have to be careful, otherwise removing tasks assigned as "done" will crash.

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)
    }
}

在11.3上还需要其他技巧",请参见 VStack中的SwiftUI切换未对齐以获取更多详细信息.

On 11.3 there is other "trick" required, see SwiftUI Toggle in a VStack is misaligned for further details.

这篇关于每次尝试将项目添加到分组项目时,SwiftUI应用程序每次都会崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆