Приложение SwiftUI вылетает каждый раз при попытке добавить элемент в сгруппированные элементы - PullRequest
0 голосов
/ 24 марта 2020

Мое приложение падает, и я получаю следующую ошибку, когда пытаюсь отсортировать элементы по дате в ForEach l oop:

2020-03-24 16: 55: 13.830146 + 0700 list-date [60035: 2135088] *** Ошибка подтверждения в - [_ _UITableViewUpdateSupport _setupAnimationsForNewlyInsertedCells], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore_Sim/UIKit-39.mb1/1 (UL) )

on line:

class AppDelegate: UIResponder, UIApplicationDelegate {

из AppDelegate.swift

Сначала мое приложение загружается в симуляторе, но после нажатия кнопки Добавить, чтобы открыть модальное окно и добавление нового элемента, приложение сразу вылетает с ошибкой.

Я думаю, что есть проблема в забавном обновлении c или в самом ForEach l oop. Я отметил в коде, альтернатива которой у меня работает oop. К сожалению, эта альтернатива не группирует элементы по датам. И эту функцию я пытаюсь добавить в мое приложение.

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 атрибутами: date (Date), title (String).

Буду признателен, если кто-нибудь поможет мне с ошибкой. Или альтернатива группированию предметов тоже может сработать:)

1 Ответ

1 голос
/ 24 марта 2020

Для вдохновения, как использовать вашу модель, вот упрощенный пример

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

Для нашей задачи нам нужна некоторая структура данных, соответствующая Identifiable (это поможет SwiftUI идентифицировать каждый динамически генерируемый ToDoView)

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

Простая модель со всеми основными функциями c может быть определена как

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 секунд с момента «сейчас»

Остаток - это код SwiftUI, который не требует пояснений

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

enter image description here

Если вы хотите использовать переключатель, вы должны быть осторожны, в противном случае удаление задач, назначенных как «выполнено», приведет к sh.

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 требуется другой «трюк», см. SwiftUI Toggle в VStack смещен для получения дополнительной информации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...