避免在`NSFetchedResultsController`的后台更新期间跳转到'UITableView`

时间:2015-03-14 15:58:26

标签: ios uitableview swift core-data nsfetchedresultscontroller

我从书中找到UITableViewNSFetchedResultsController一起使用。

后台流程会更新CoreData个对象,因此每次更新都会自动触发FetchedResultUITableView的更新。

在这些更新过程中向下拉UITableView会导致一个令人不安的跳跃回到顶部,然后在进一步拉动时快速回到下拉位置: screencast showing the effect

实体:

@objc(Entity)
class Entity: NSManagedObject {

    @NSManaged var name: String
    @NSManaged var lastUpdated: NSDate

}

结果:

private lazy var resultsController: NSFetchedResultsController = {

    let fetchRequest = NSFetchRequest( entityName: "Entity" )
    fetchRequest.sortDescriptors = [ NSSortDescriptor( key: "name", ascending: true ) ]
    fetchRequest.relationshipKeyPathsForPrefetching = [ "Entity" ]

    let controller = NSFetchedResultsController(
        fetchRequest: fetchRequest,
        managedObjectContext: self.mainContext,
        sectionNameKeyPath: nil,
        cacheName: nil
    )
    controller.delegate = self
    controller.performFetch( nil )

    return controller
}()

组合UITableViewResultsController的通常更新逻辑:

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

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

    switch( type ) {
    case NSFetchedResultsChangeType.Insert:
        tableView.insertRowsAtIndexPaths( [ newIndexPath! ], withRowAnimation: UITableViewRowAnimation.Fade )
    case NSFetchedResultsChangeType.Delete:
        tableView.deleteRowsAtIndexPaths( [ indexPath! ], withRowAnimation: UITableViewRowAnimation.Fade )
    case NSFetchedResultsChangeType.Update:
        tableView.reloadRowsAtIndexPaths( [ indexPath! ], withRowAnimation: UITableViewRowAnimation.None )
    case NSFetchedResultsChangeType.Move:
        tableView.moveRowAtIndexPath( indexPath!, toIndexPath: newIndexPath! )
    default:
        break
    }
}

显示效果的背景更新(不是原始的更复杂的):

override func viewDidLoad() {
    super.viewDidLoad()

    //...

    let timer = NSTimer.scheduledTimerWithTimeInterval( 1.0, target: self, selector: "refresh", userInfo: nil, repeats: true)
    NSRunLoop.mainRunLoop().addTimer( timer, forMode: NSRunLoopCommonModes )
}

func refresh() {
    let request = NSFetchRequest( entityName: "Entity" )

    if let result = mainContext.executeFetchRequest( request, error: nil ) {
        for entity in result as [Entity] {
            entity.lastUpdated = NSDate()
        }
    }
}

我已经搜索了stackoverflow几个小时,并尝试了几乎所有的东西。 唯一的想法就是停止tableView.reloadRowsAtIndexPaths()而不是beginUpdates() / endUpdates(),所以两者似乎都会造成这种影响。

但这不是选项,因为UITableView根本不会更新。

任何建议或解决方案?

2 个答案:

答案 0 :(得分:1)

你能做的很简单; 不要重新加载表格以进行coredata更新,首先检查表格是否静止不动..

当您需要从coredata更新更新tableview时,请检查tableview是否正在滚动(有很多方法可以执行此操作)。如果是这样,不要继续,但要这样做:在tableview类标记中,表之后需要使用布尔值更新表,例如“shouldUpdateTable”。 比用于“完成滚动动画”的tableview委托方法检查表是否需要从该布尔标记重新加载,如果为true,则重新加载表并重置布尔值。

希望这种方法有所帮助。

答案 1 :(得分:1)

我刚刚找到了一个适合我的解决方案。

有两项操作与我的后台更新有冲突:

  1. 拖动/滚动
  2. 编辑(上面没有提到,但也需要)
  3. 因此,根据Timur X的建议,这两项行动都必须锁定更新。这就是我所做的:

    出于多种原因的锁定机制

    enum LockReason: Int {
        case scrolling = 0x01,
        editing   = 0x02
    }
    
    var locks: Int = 0
    
    func setLock( reason: LockReason ) {
        locks |= reason.rawValue
        resultsController.delegate = nil
        NSLog( "lock set = \(locks)" )
    }
    
    func removeLock( reason: LockReason ) {
        locks &= ~reason.rawValue
        NSLog( "lock removed = \(locks)" )
        if 0 == locks {
            resultsController.delegate = self
        }
    }
    

    集成滚动锁定机制

    override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
        setLock( .scrolling )
    }
    
    override func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if !decelerate {
            removeLock( .scrolling )
        }
    }
    
    override func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
        removeLock( .scrolling )
    }
    

    处理编辑(在这种情况下删除)

    override func tableView(tableView: UITableView, willBeginEditingRowAtIndexPath indexPath: NSIndexPath) {
        setLock( .editing )
    }
    
    override func tableView(tableView: UITableView, didEndEditingRowAtIndexPath indexPath: NSIndexPath) {
        removeLock( .editing )
    }
    

    ,这是编辑/删除

    的整合
    override func tableView(tableView: UITableView, titleForDeleteConfirmationButtonForRowAtIndexPath indexPath: NSIndexPath) -> String! {
        return "delete"
    }
    
    override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        return true
    }
    
    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        if editingStyle == UITableViewCellEditingStyle.Delete {
            removeLock( .editing )
            if let entity = resultsController.objectAtIndexPath( indexPath ) as? Entity {
                mainContext.deleteObject( entity )
            }
        }
    }