首页 > 解决方案 > Swift拆分视图控制器,首次加载时出现“重复”通知

问题描述

完整标题:Swift 4.2 使用拆分视图控制器,首次加载时发出“重复”通知,这会破坏以编程方式在主表视图中选择“新”对象的能力。

背景

我正在使用带有主表和详细表视图的拆分视图控制器 -表是一个普通的动态表视图,它向用户显示实体列表,详细信息是一个分组的动态表视图,它显示用户的属性和关系值 -主控中的选定实体。

我已经实现了核心数据。

主表视图显示实体列表。主表视图的数据源是一个获取的结果控制器。

明细表视图显示主表视图中当前选定行(实体)的属性和关联关系值。详细表视图的数据源是与主表视图中当前选定行相关的实体,它使用“显示详细信息”segue 传递。

在屏幕较大的设备上splitViewController.isCollapsed == false,主表视图和详细表视图在屏幕上都处于活动状态,如下图所示。

使用 iPad Pro 2nd gen 模拟器在屏幕上显示主表和详细表视图

数据驱动应用程序的相当标准的安排......?

逻辑流程

当用户更新现有实体(在屏幕截图示例中为现有“事件”)时,他们单击Save详细信息表视图导航栏中的按钮。

因为实体已经存在,所以在 Master Table View Controller 的controller(_:didChange:at:for:newIndexPath)Fetched Results Controller Delegate 方法中:

获取结果控制器委托方法:

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

然后,在 Master Table View Controller 的controllerDidChangeContent(_:)Fetched Results Controller Delegate 方法中,我执行以下代码...

guard let indexPathOfRowToSelect: IndexPath = indexPathForManagedObjectChanged else {
    return
}
indexPathForManagedObjectChanged = nil
tableView.selectRow(at: indexPathOfRowToSelect, animated: false, scrollPosition: UITableViewScrollPosition.none)

这可确保在插入、更新和移动之后,选择与详细表视图中的数据对应的行。

我使用类型的通知UserDefaults.didChangeNotification

观察者被添加到主表视图控制器的(EDITviewDidLoad,但现在)viewWillAppear(_:)方法中。观察者在主表视图控制器的viewWillDisappear(_:)方法中被移除。

如果用户更改其中一项设置,通知observer将调用以下函数...

@objc
func userDefaultSettingsDidChange(_ notification: Notification) {

    if (notification.object as? UserDefaults) != nil {            
        NSFetchedResultsController<NSFetchRequestResult>.deleteCache(withName: classCacheName)
        fetchedResultsController = nil
        tableView.reloadData() 
    }
}

问题

在主表视图控制器第一次运行时,有一个调用该userDefaultSettingsDidChange方法的通知,但它在第一次运行获取结果控制器委托方法以响应第一次用户交互后调用此方法。尽管没有更改用户默认设置。

该问题发生在模拟器和设备上。

我添加了一堆print()s (为了便于阅读代码而删除了)来跟踪以什么顺序发生的事情。

控制台将userDefaultSettingsDidChange函数记录为在 Fetched Results Controller Delegate 方法完成后执行 - 但仅在第一次运行时执行。

正因为如此,对 的调用reloadData清除了我之前对 的调用selectRow

解决方案

我可以selectRow(at:animated:scrollPosition:)在 Master Table View Controller 的 Fetched Results Controller Delegate 方法中包装对实例方法的调用,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 中的第一次用户交互造成严重破坏吗?

标签: swiftuitableviewcore-datanotificationsnsfetchedresultscontroller

解决方案


推荐阅读