【问题标题】:UITableView Freezes After Swipe to Delete But Not Whole UI滑动删除后 UITableView 冻结但不是整个 UI
【发布时间】:2016-05-06 11:41:58
【问题描述】:

全屏表格视图仅适用于 iPad 的应用。我已启用滑动以删除我的行。行动画总是在删除(commitEditingStyle 完成)后完成,但有时整个表格视图会冻结。请注意,不是整个 UI,所以它不是阻塞的主线程。我可以点击列标题或点击导航控制器上的后退按钮,但表格本身已锁定且无法滑动。我可以很简单地通过点击我的列标题按钮之一来解冻它。

我完全不知道可能导致冻结的原因。我正在使用 NSFetchedResultsController,这是我的委托代码。这是相当样板(更新:现在不像样板。使用批处理方法):

// MARK: NSFetchedResultsController delegate methods

lazy var deletedSectionIndexes : NSMutableIndexSet = {
    return NSMutableIndexSet()
}()

lazy var insertedSectionIndexes : NSMutableIndexSet = {
    return NSMutableIndexSet()
}()

lazy var deletedRowIndexPaths : [NSIndexPath] = {
    return [NSIndexPath]()
}()

lazy var insertedRowIndexPaths : [NSIndexPath] = {
    return [NSIndexPath]()
}()

lazy var updatedRowIndexPaths : [NSIndexPath] = {
    return [NSIndexPath]()
}()


func controllerWillChangeContent(controller: NSFetchedResultsController) {

}

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
    switch(type) {

    case .Delete:
        if let indexPath = indexPath {
            self.deletedRowIndexPaths.appendDistinct(indexPath)
        }
    case .Update:
        if let indexPath = indexPath {
            self.updatedRowIndexPaths.appendDistinct(indexPath)
        }
    case .Insert:
        if let newIndexPath = newIndexPath {
            self.insertedRowIndexPaths.appendDistinct(newIndexPath)
        }
    case .Move:
        if let indexPath = indexPath, newIndexPath = newIndexPath {
            self.insertedRowIndexPaths.appendDistinct(newIndexPath)
            self.deletedRowIndexPaths.appendDistinct(indexPath)
        }
    }
}

func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
    switch(type) {

    case .Delete:
        self.deletedSectionIndexes.addIndex(sectionIndex)
    case .Insert:
        self.insertedSectionIndexes.addIndex(sectionIndex)
    default:
        break
    }
}

func controllerDidChangeContent(controller: NSFetchedResultsController) {
    self.tableView.beginUpdates()
    self.tableView.insertSections(self.insertedSectionIndexes, withRowAnimation: .None)
    self.tableView.deleteSections(self.deletedSectionIndexes, withRowAnimation: .None)

    self.tableView.insertRowsAtIndexPaths(self.insertedRowIndexPaths, withRowAnimation: .None)
    self.tableView.deleteRowsAtIndexPaths(self.deletedRowIndexPaths, withRowAnimation: .None)
    self.tableView.reloadRowsAtIndexPaths(self.updatedRowIndexPaths, withRowAnimation: .None)
    self.tableView.endUpdates()

    self.insertedSectionIndexes.removeAllIndexes()
    self.deletedSectionIndexes.removeAllIndexes()
    self.deletedRowIndexPaths.removeAll()
    self.insertedRowIndexPaths.removeAll()
    self.updatedRowIndexPaths.removeAll()        
}

delete 在 didChangeObject 委托方法中被调用,但是,从技术上讲,它并不是真正的删除。我只是将一个属性设置为 -1,然后通过 NSMangagedObjectContext 保存该元素——此时 NSFRC 似乎做了正确的事情,即从使用此谓词获取的获取对象列表中删除它:

NSPredicate(format: "account = %@ and quantity != -1", account)

其中account 是一个有效的帐户管理对象。该行在 90% 或更多的时间没有问题地消失。只是偶尔在动画完成后桌子冻结在我描述的庄园里。它永远不会因为仍然显示删除按钮而冻结,所以我知道它是在调用 commitEditingStyle 之后。删除按钮没有自定义实现。它是滑动删除的默认 UITableView 实现。这是我的 commitEditingStyle 方法:

func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if editingStyle == .Delete {
        if let frameboardItem = self.fetchedResultsController.objectAtIndexPath(indexPath) as? IRMFrameBoardItemMO {
            if frameboardItem.isNew {
                // If it's never been pushed to the server, just delete locally. This will trigger a table reload
                // via NSFetchedResultsController
                DataManager.mainContext.deleteObject(frameboardItem)
            } else {
                // Otherwise mark it with a negative quantity which tells the server to delete it and tells the
                // app to hide it.
                frameboardItem.quantity = -1
            }

            do {
                try DataManager.mainContext.save()
            } catch let error as NSError {
                dLog("Something went wrong: \(error.localizedDescription)")
            }

        }

    }

}

您可以在此处观看我所说的视频。两分钟多,所以你可能不想看完整的东西,但我会把它放在这里以供参考。

https://vimeo.com/153406113

很想听听任何建议。

更新

我更新了 NSFRC 委托方法以使用批处理方法来确保一次应用所有更新。这并没有解决问题。该表仍会定期冻结。

【问题讨论】:

  • 您确定数据源正在正确更新吗?在以下语句中是否存在frameboardItem 是nil 的情况:if let frameboardItem = self.fetchedResultsController.objectAtIndexPath(indexPath) as? IRMFrameBoardItemMO?而如果是nil,是不是更新了表而没有更新数据源?
  • 1) tableView 是否在子视图控制器中? 2)如果出于测试目的,您更改其中一个按钮(确实响应触摸)以触发电视滚动到顶部(或底部),它是否真的滚动?这可能会提供有关电视是否只是忽略触摸或尝试滚动时冻结的线索。
  • @pbasdf 不完全确定我理解你的建议,但是当表格视图冻结时,我可以点击状态栏并且表格视图按预期滚动到顶部。但是,在尝试通过滑动滚动时,它仍然被冻结。
  • @Matt 谢谢 - 所以问题是因为电视没有收到触摸,而不是因为它不能滚动。您的电视或手机是否有任何手势识别器可以吞下触摸?
  • @pbasdf 不。单元格上没有手势识别器。

标签: swift uitableview core-data nsfetchedresultscontroller


【解决方案1】:

您是否尝试以更常见的方式实现 NSFetchedResultsControllerDelegate 的委托,我的意思是在 fetchedResultController 要求时开始更新表,进行更新然后结束更新?

func controllerWillChangeContent(controller: NSFetchedResultsController) {
    self.tableView.beginUpdates()
}

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
    /* update table here */
}

func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
    /* update table here */
}

func controllerDidChangeContent(controller: NSFetchedResultsController) {    
    self.tableView.endUpdates()
}

更新:
是否有可能,当您“将对象标记为已删除”时,它会触发一些更复杂的对象更改链,进而导致 didChangeObject 函数被多次调用? 您是否跟踪过在单个“删除标记”期间调用了多少次 didChangeObject 函数?

【讨论】:

  • 是的。这就是切换到批量更新之前的情况。有关详细信息,请参阅问题。
  • @MattLong 是的,对不起,我错过了。
【解决方案2】:

我也有这个问题的猜测。我的想法是controllerDidChangeContent 可以被调用两次或更多次,并且比表刷新更快,这导致多次调用tableView.beginUpdates() 可以挂起表。

所以要解决这个问题,我建议在 dispatch_async 块中包装更新,或者只是简单的布尔标志

func controllerDidChangeContent(controller: NSFetchedResultsController) {
    dispatch_async(dispatch_get_main_queue(), { () -> Void in
         self.tableView.beginUpdates()
         // ..... rest of update code 
         self.updatedRowIndexPaths.removeAll()
    })
}

【讨论】:

  • 那么这到底在做什么呢?那么更新正在排队吗?
  • @MattLong 是的,更新正在队列中执行
  • 不幸的是,它不是灵丹妙药,但它非常接近。即使使用这种技术,我也重现了冻结,但频率非常低——几乎从来没有。我接受你的回答是正确的。感谢您在黑暗中刺伤。我很感激。
【解决方案3】:

我认为 TableView 不会因为内存问题或不平衡的 begin*/endEditing 调用而冻结(会引发异常或发送信号)。

我认为它可能在主线程以外的线程上做一些事情。在这种情况下,即使是块也无济于事。 (设置断点并检查什么线程停止...,也:在真实设备上测试)

我的想法是,尝试不同的方法,例如添加数据以添加或删除到临时数组,并在一个方法运行中更新 TableView(在获取结果控制器结束后调用该方法在主线程上显式运行它的委托调用)。

【讨论】:

    【解决方案4】:

    使用方块。

    我不清楚 MOC 是从哪个线程访问的,但由于您使用的是fetchedResultsController,它可能是主要线程。

    因此,您可能需要执行

    • deleteObject
    • save

    在performBlockAndWait 等待。这可能有助于保证数据的完整性。类似的东西:

    DataManager.mainContext.performBlockAndWait { () -> Void in
        DataManager.mainContext.deleteObject(frameboardItem)
        if DataManager.mainContext.hasChanges {
            do {
                try DataManager.mainContext.save()
            } catch let error as NSError {
                dLog("Something went wrong: \(error.localizedDescription)")
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多