Ошибка при переупорядочении элемента списка в подробном представлении с использованием SwiftUI Navigation View и Sorted FetchRequest - PullRequest
2 голосов
/ 03 апреля 2020

У меня есть NavigationView со списком, показывающим задачи из CoreData FetchRequest. FetchRequest сортируется по возрастанию на Task.dueDate. Представление TaskDetail в основном состоит из TextField для заголовка и выбора даты для даты. Изменение значений в подробном представлении работает. Хотя я получаю странное поведение каждый раз, когда пытаюсь изменить значение даты. Дата изменяется, но навигационное представление автоматически выходит из подробного представления и возвращается к представлению списка. Это происходит только тогда, когда я изменяю дату таким образом, что список перестраивается из-за сортировки.

Как предотвратить это странное поведение, описанное выше ??

enter image description here

//
//  ContentView.swift

import SwiftUI
import CoreData

struct ContentView: View {

    @Environment(\.managedObjectContext) var moc
    @FetchRequest(fetchRequest: Task.requestAllTasks()) var tasks: FetchedResults<Task>

    var body: some View {
        NavigationView {
            List(tasks, id: \.id) { task in
                NavigationLink(destination: TaskDetail(task: task)) {
                    Text("\(task.title)")
                }
            }.navigationBarTitle("Tasks").navigationBarItems(trailing: Button("new") {self.addTask()})
        }
    }

    func addTask() -> Void {
        let newTask = Task(context: self.moc)
        newTask.id = UUID()
        newTask.title = "task \(tasks.count)"
        newTask.dueDate = Date()
        print("created new Task")
        if (self.moc.hasChanges) {
            try? self.moc.save()
            print("saved MOC")
        }
        print(self.tasks)
    }

}

struct TaskDetail : View {

    @ObservedObject var task: Task

    var body: some View {
        VStack{
            TextField("name", text: $task.title)
            DatePicker("dueDate", selection: $task.dueDate, displayedComponents: .date)
                .labelsHidden()
        }
    }
}

//
//  Task.swift

import Foundation
import CoreData

public class Task: NSManagedObject, Identifiable {
    @NSManaged public var id: UUID?
    @NSManaged public var dueDate: Date
    @NSManaged public var title: String

    static func requestAllTasks() -> NSFetchRequest<Task> {
        let request: NSFetchRequest<Task> = Task.fetchRequest() as! NSFetchRequest<Task>

        let sortDescriptor = NSSortDescriptor(key: "dueDate", ascending: true)
        request.sortDescriptors = [sortDescriptor]

        return request
    }
}

Чтобы создать работающую минимальную воспроизводимую версию этой ... do:

  1. Создание нового Xcode "Single View App" Проект. Обязательно установите флажок CoreData.
  2. Скопируйте код для ContentView выше и вставьте / замените в ContentView.swift.
  3. Создайте новый файл Swift с именем Task. Скопируйте код для Task и вставьте в файл Task.swift.
  4. Добавьте объекты в ProjectName.xcdatamodeld в соответствии с изображением ниже.
  5. Выполните

enter image description here

Я нахожусь на Xcode 11.4.

Дайте мне знать, если вы хотите, чтобы я предоставил дополнительную информацию. Любая помощь высоко ценится! Спасибо!

Ответы [ 2 ]

1 голос
/ 06 апреля 2020

Как упоминалось в моем комментарии выше, я считаю, что это ошибка в iOS 13.4.

Обходным путем может быть использование NavigationLink вне списка и определение строк списка как кнопок, которые

a) установить редактируемое задание (новый @State var selectedTask) и

b) активировать NavigationLink для TaskDetail (task: selectedTask!).

Эта настройка отсоединит выбранная задача с позиции в отсортированном списке, что позволяет избежать неправильного поведения, вызванного повторной сортировкой, которая может быть вызвана редактированием dueDate.

Для этого:

1) добавьте эти две переменные @State структурировать ContentView

@State private var selectedTask: Task?
@State private var linkIsActive = false

2) обновить тело struct ContentView следующим образом

var body: some View {
    NavigationView {
        ZStack {
            NavigationLink(
                destination: linkDestination(selectedTask: selectedTask).onAppear(perform: {self.linkIsActive = false},
                isActive: self.$linkIsActive) {
                EmptyView()
            }
            List(tasks) { task in
                Button(action: {
                    self.selectedTask = task
                    self.linkIsActive = true
                }) {
                    NavigationLink(destination: EmptyView()){
                        Text("\(task.title)")
                    }
                }
            }
        }
        .navigationBarTitle("Tasks").navigationBarItems(trailing: Button("new") {self.addTask()})
    }
}

3) добавить следующую структуру в ContentView.swift

struct linkDestination: View {
    let selectedTask: Task?
    var body: some View {
        return Group {
            if selectedTask != nil {
                TaskDetail(task: selectedTask!)
            } else {
                EmptyView()
            }
        }
    }
}
0 голосов
/ 15 апреля 2020

Я столкнулся с той же проблемой и не смог найти хорошего решения. Но у меня есть другой обходной путь.

Я сделал динамический запрос Fetchrequest c и изменил дескриптор сортировки, когда ссылка активна. Это имеет отрицательный побочный эффект: список сортируется с анимацией каждый раз, когда вы возвращаетесь к ContentView.

Если вы добавите следующую структуру для своего списка:

struct TaskList: View {
    @Environment(\.managedObjectContext) var moc
    @FetchRequest var tasks: FetchedResults<Task>
    @Binding var activeTaskLink: Int?

    init(activeTaskLink: Binding<Int?>, currentSortKey: String) {
        self._activeTaskLink = activeTaskLink
        self._tasks = Task.requestAllTask(withSortKey: currentSortKey)
    }

    var body: some View {
        List(tasks, id: \.id) { task in
            NavigationLink(destination: TaskDetail(task: task), tag: task.objectId.hashValue, selection: self.$activeTaskLink) {
                Text("\(task.title)")
            }
        }
    }

}

Затем измените Функция requestAllTask ​​в Task.swift:

static func requestAllTasks(withSortKey key: String) -> NSFetchRequest<Task> {
    let request: NSFetchRequest<Task> = Task.fetchRequest() as! NSFetchRequest<Task>

    let sortDescriptor = NSSortDescriptor(key: key, ascending: true)
    request.sortDescriptors = [sortDescriptor]

    return request
}

Затем добавьте состояние для activeTask в ContentView

@State var activeTaskLink: Int? = nil

и измените тело на

var body: some View {
    TaskList(activeTaskLink: self.$activeTaskLink, currentSortKey: self.activeNavLink != nil ? "id" : "dueDate")
    .navigationBarTitle("Tasks")
    .navigationBarItems(trailing: Button("new") {self.addTask()})
}
...