背景
我正在使用具有主视图和明细表视图的拆分视图控制器-主视图是一种普通的动态表视图,向用户显示实体列表,并提供详细信息是一个分组的动态表视图,显示了主数据库中用户选择的实体的属性和关系值。
我已经实现了核心数据。
主表视图显示实体列表。主表视图的数据源是访存结果控制器。
详细信息表视图显示主表视图中当前所选行(实体)的属性和关联的关系值。明细表视图的数据源是与主表视图中当前所选行相关的实体,使用“显示明细”提示将其传递。
在splitViewController.isCollapsed == false
屏幕较大的设备上,根据下图,主视图和详细信息表视图均在屏幕上处于活动状态。
数据驱动应用程序的标准配置...?
逻辑流
当用户更新现有实体(在屏幕快照示例的情况下为现有“事件”)时,他们单击明细表视图的导航栏中的Save
按钮。
因为该实体已经存在,所以在主表视图控制器的controller(_:didChange:at:for:newIndexPath)
的“提取结果控制器委托”方法中:
case .update
下,实现tableView.reloadRows(at: [indexPath!], with: UITableViewRowAnimation.none)
,该事件触发tableView(_willDisplay:forRowAt:)
,后者又更新所选表视图单元格的格式;和
在case .insert
,case .update
和case .move
下,我为当前IndexPath indexPathForManagedObjectChanged
“提取的结果控制器委托”方法:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
tableView.insertRows(at: [newIndexPath!], with: .fade)
indexPathForManagedObjectChanged = newIndexPath
print("___controllerDidChangeObject: INSERTED OBJECT")
case .delete:
tableView.deleteRows(at: [indexPath!], with: .fade)
print("___controllerDidChangeObject: DELETED OBJECT")
case .update:
configureCell(tableView.cellForRow(at: indexPath!)!, withEvent: anObject as! PT_Events)
indexPathForManagedObjectChanged = indexPath
tableView.reloadRows(at: [indexPath!], with: UITableView.RowAnimation.none)
print("___controllerDidChangeObject: UPDATED OBJECT")
case .move:
configureCell(tableView.cellForRow(at: indexPath!)!, withEvent: anObject as! PT_Events)
indexPathForManagedObjectChanged = newIndexPath
tableView.moveRow(at: indexPath!, to: newIndexPath!)
print("___controllerDidChangeObject: MOVED OBJECT")
}
}
然后,在主表视图控制器的controllerDidChangeContent(_:)
获取的结果控制器委托方法中,我执行以下代码...
guard let indexPathOfRowToSelect: IndexPath = indexPathForManagedObjectChanged else {
return
}
indexPathForManagedObjectChanged = nil
tableView.selectRow(at: indexPathOfRowToSelect, animated: false, scrollPosition: UITableViewScrollPosition.none)
这可确保在插入,更新和移动后,选择与“明细表视图”中的数据相对应的行。
我使用的通知类型为UserDefaults.didChangeNotification
。
将观察者添加到主表视图控制器的viewDidLoad
(但现在为viewWillAppear(_:)
)方法中( EDIT )。在主表视图控制器的viewWillDisappear(_:)
方法中删除了观察者。
如果用户更改了其中一项设置,则通知observer
将调用以下功能...
@objc
func userDefaultSettingsDidChange(_ notification: Notification) {
if (notification.object as? UserDefaults) != nil {
NSFetchedResultsController<NSFetchRequestResult>.deleteCache(withName: classCacheName)
fetchedResultsController = nil
tableView.reloadData()
}
}
问题
在主表视图控制器的第一次运行中,有一个调用userDefaultSettingsDidChange
方法的通知,但在第一次运行Fetched Results Controller Delegate方法之后,它会响应于第一次用户交互而调用此通知。尽管用户默认设置没有任何变化。
问题发生在模拟器和设备上。
我添加了一堆print()
(为了便于阅读代码而删除)以跟踪按什么顺序发生的事情。
在获取结果控制器代表方法完成后,控制台在执行userDefaultSettingsDidChange
函数时记录日志-仅在第一次运行时执行。
因此,对reloadData
的呼叫会抹去我先前对selectRow
的呼叫。
解决方案
我可以将对selectRow(at:animated:scrollPosition:)
实例方法的调用包装在主表视图控制器的controllerDidChangeContent(_:)
的“提取结果控制器代表”方法中,以包括一些延迟...
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
guard let indexPathOfRowToSelect: IndexPath = indexPathForManagedObjectChanged else {
return
}
indexPathForManagedObjectChanged = nil
let when = DispatchTime.now() + 0.20
DispatchQueue.main.asyncAfter(deadline: when) {
self.tableView.selectRow(at: indexPathOfRowToSelect, animated: false, scrollPosition: UITableView.ScrollPosition.none)
}
}
这行得通!
但是-我不满意。
我已经读了很多书,但是由于某些原因,似乎无法理解NotificationCenter
中正在发生的事情,尽管没有更新,这仍然导致对用户设置的虚幻更新。
有人可以解释一下为什么我看到对用户设置的这种延迟的“更新”仅对UI中的首次用户交互造成严重破坏吗?