滑动删除但不是整个UI后UITableView冻结

时间:2016-01-28 21:42:47

标签: swift uitableview core-data nsfetchedresultscontroller

全屏桌面视图仅适用于iPad的应用。我已启用滑动以删除我的行。删除后(rowEditingStyle完成)行动画总是结束,但有时整个表视图会冻结。请注意,不是整个用户界面,因此它不是一个被阻止的主线程。我可以点击列标题或点击导航控制器上的后退按钮,但表本身会锁定并且无法刷卡。我可以通过点击我的一个列标题按钮来解冻它。

enter image description here

我完全不知道可能导致冻结的原因。我正在使用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()        
}

删除在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委托方法以使用批处理方法来确保一次性应用所有更新。这还没有解决问题。桌子还会定期冻结。

4 个答案:

答案 0 :(得分: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()
    })
}

答案 1 :(得分:0)

使用块。

我不清楚从哪个线程访问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)")
        }
    }
}

答案 2 :(得分:0)

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

我认为它可以在除主线程之外的线程上执行操作。在这种情况下,即使是块也不会有帮助。 (设置一个断点并检查什么线程停止......,还有:在真实设备上测试)

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

答案 3 :(得分:0)

您是否尝试以更常见的方式实现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()
}

UPADATE:
是否有可能,当您将对象标记为已删除时'它会触发一些更复杂的对象链变化,这反过来会导致多次调用didChangeObject函数? 您是否跟踪了在单个删除标记期间调用了多少次调用对象功能?