SwiftUI /核心数据-更新详细视图时在列表视图和详细视图之间进行非自愿导航

时间:2020-07-30 17:04:26

标签: core-data swiftui-list swiftui-navigationlink swiftui

我正在使用TabView / NavigationView和NavigationLink以编程方式从列表视图导航到详细视图,并且当我将布尔属性“固定”更新为true并将核心数据实体保存在详细视图中时,我得到以下信息不幸的副作用:

  1. 非自愿导航回到列表视图,然后再次回到详细视图
  2. 非自愿导航到相同详细视图的另一个副本,然后返回列表视图。

我准备了一个小的Xcode project with the complete sample code

在列表视图中,我使用@FetchRequest查询列表并对以下内容进行排序:

    @FetchRequest(entity: Task.entity(),sortDescriptors: [NSSortDescriptor(key: "pinned", ascending: false),
                                                          NSSortDescriptor(key: "created", ascending: true),
                                                          NSSortDescriptor(key: "name", ascending: true)])

在列表视图中,我使用以下内容:

List() {
   ForEach(tasks, id: \.self) { task in
       NavigationLink(destination: DetailsView(task: task), tag: task.id!.uuidString, selection: self.$selectionId) {
         HStack() {
            ...
         }
      }
}

(1)如果我省略了'NSSortDescriptor(key:“ pinned” ...)',我看不到该行为。

(2)如果在NavigationLink()中省略了'tag:'和'selection:'参数,则看不到此行为。但是创建新的Task实体时,我需要能够以编程方式触发导航链接。

(3)当我在列表中只有一个实体或在列表中的第一个实体中更改“固定”布尔属性的值时,似乎永远不会发生。

(4)我得到警告:

[TableView]仅警告一次:通知UITableView布置其可见单元格和其他内容,而不必放在视图层次结构中(表视图或其父视图之一尚未添加到窗口中)...

列表视图(TasksListView)的父视图包含一个TabView:

struct ContentView: View {
    var body: some View {
        TabView {
            NavigationView {
                TasksListView()
            }
            .tabItem {
                Image(systemName: "tray.full")
                    .font(.title)
                Text("Master")
            }
            NavigationView {
                EmptyView()
            }
            .tabItem {
                Image(systemName: "magnifyingglass")
                    .font(.title)
                Text("Search")
            }
        }  
    }
}

struct TasksListView: View {
    
    // NSManagedObjectContext
    @Environment(\.managedObjectContext) var viewContext

    // Results of fetch request for tasks:
    @FetchRequest(entity: Task.entity(),sortDescriptors: [NSSortDescriptor(key: "pinned", ascending: false),
                                                          NSSortDescriptor(key: "created", ascending: true),
                                                          NSSortDescriptor(key: "name", ascending: true)])
    var tasks: FetchedResults<Task>
    
    // when we create a new task and navigate to it programitically
    @State var selectionId : String?
    @State var newTask : Task?


    var body: some View {
        
        List() {
            ForEach(tasks, id: \.self) { task in
                NavigationLink(destination: DetailsView(task: task), tag: task.id!.uuidString, selection: self.$selectionId) {
                    HStack() {
                        VStack(alignment: .leading) {
                            Text("\(task.name ?? "unknown")")
                                .font(Font.headline.weight(.light))
                                .padding(.bottom,5)
                            Text("Created:\t\(task.created ?? Date(), formatter: Self.dateFormatter)")
                                .font(Font.subheadline.weight(.light))
                                .padding(.bottom,5)
                            if task.due != nil {
                                Text("Due:\t\t\(task.due!, formatter: Self.dateFormatter)")
                                    .font(Font.subheadline.weight(.light))
                                    .padding(.bottom,5)
                            }
                        }
                    }
                }
                
            }
        }
        .navigationBarTitle(Text("Tasks"),displayMode: .inline)
        .navigationBarItems(trailing: rightButton)
    }
    
    
    var rightButton: some View {
        Image(systemName: "plus.circle")
            .foregroundColor(Color(UIColor.systemBlue))
            .font(.title)
            .contentShape(Rectangle())
            .onTapGesture {
                // create a new task and navigate to it's detailed view to add values
                Task.create(in: self.viewContext) { (task, success, error) in
                    if success {
                        self.newTask = task
                        self.selectionId = task!.id!.uuidString
                    }
                }
                
        }
    }
}

struct DetailsView: View {
    
    // NSManagedObjectContext
    @Environment(\.managedObjectContext) var viewContext
        
    @ObservedObject var task : Task
    
    @State var name : String = ""
    @State var dueDate : Date = Date()
    @State var hasDueDate : Bool = false
    @State var isPinned : Bool = false
    
    var body: some View {
        
        List() {
            
            Section() {
                Toggle(isOn: self.$isPinned) {
                    Text("Pinned")
                }
            }
            
            Section() {
                TextField("Name", text: self.$name)
                    .font(Font.headline.weight(.light))
                Text("\(task.id?.uuidString ?? "unknown")")
                    .font(Font.headline.weight(.light))
            }
            
            Section() {
                HStack() {
                    Text("Created")
                    Spacer()
                    Text("\(task.created ?? Date(), formatter: Self.dateFormatter)")
                        .font(Font.subheadline.weight(.light))
                }
            
                Toggle(isOn: self.$hasDueDate) {
                    Text("Set Due Date")
                }
                
                if self.hasDueDate {
                    DatePicker("Due Date", selection: self.$dueDate, in: Date()... , displayedComponents: [.hourAndMinute, .date])
                }
            }
        }
        .navigationBarTitle(Text("Task Details"),displayMode: .inline)
        .navigationBarItems(trailing: rightButton)
        .listStyle(GroupedListStyle())
        .onAppear() {
            if self.task.pinned {
                self.isPinned = true
            }
            if self.task.name != nil {
                self.name = self.task.name!
            }
            if self.task.due != nil {
                self.dueDate = self.task.due!
                self.hasDueDate = true
            }
        }
        
    }
    
    // save button
    
    var rightButton: some View {
        
        Button("Save") {

            // save values in task & save:
            self.task.pinned = self.isPinned
            if self.hasDueDate  {
                self.task.due = self.dueDate
            }
            
            if self.name.count > 0 {
                self.task.name = self.name
            }
            
            Task.save(in: self.viewContext) { (success, error) in
                DispatchQueue.main.async {
                    if success {
                        print("Task saved")
                    }
                    else {
                        print("****** Error: Task can't be saved, error = \(error!.localizedDescription)")
                    }
                }
            }
        }
        .contentShape(Rectangle())        
    }
    
    
}

extension Task {
    
    static func save(in managedObjectContext: NSManagedObjectContext, completion: @escaping (Bool, NSError?) -> Void ) {
        managedObjectContext.performAndWait() {
            do {
                try managedObjectContext.save()
                completion(true, nil)
            } catch {
                let nserror = error as NSError
                print("****** Error: Unresolved error \(nserror), \(nserror.userInfo)")
                completion(false, nserror)
            }
        }
     }
}




有什么建议吗?

2 个答案:

答案 0 :(得分:0)

您似乎以与我不同的方式创建了标签视图。这些多余的导航视图会引起问题。

不确定是否会有所帮助,但是我这样做是这样的:

struct ContentView: View {
var body: some View {
    TabView {
         TasksListView()
            .tabItem {
                Image(systemName: "tray.full")
                    .font(.title)
                Text("Master")
        }

            EmptyView()
              .tabItem {
                  Image(systemName: "magnifyingglass")
                      .font(.title)
                  Text("Search")
        }
    }  
}

}

答案 1 :(得分:0)

这是iOS 13中的错误。

iOS 14.0 beta 3起已修复。

您将在此处找到类似的问题(接受的答案提供了一种解决方法):

Issue when rearranging List item in detail view using SwiftUI Navigation View and Sorted FetchRequest