【发布时间】:2020-11-20 11:38:30
【问题描述】:
我正在使用 TabView/NavigationView 和 NavigationLink 以编程方式从列表视图导航到详细视图,当我将布尔属性“固定”更新为 true 并将核心数据实体保存在详细视图中时,我得到以下信息不幸的副作用:
- 不自觉地导航回列表视图,然后再次返回详细视图或
- 不自觉地导航到同一详细视图的另一个副本,然后返回到列表视图。
我准备了一个小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:' 参数,我看不到这种行为。但是当我创建一个新的任务实体时,我需要能够以编程方式触发导航链接。
(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)
}
}
}
}
有什么建议吗?
【问题讨论】:
标签: core-data swiftui-list swiftui-navigationlink swiftui