在从 `NSFetchedResultsController` 后台更新期间避免跳入 `UITableView`
Avoid jump in `UITableView` during background update from `NSFetchedResultsController`
我有一个 UITableView
与书中的 NSFetchedResultsController
一起使用。
CoreData
对象由后台进程更新,因此每次更新都会自动触发 FetchedResult
和 UITableView
.
的更新
在这些更新期间下拉 UITableView
会导致令人不安的跳回顶部,然后在进一步拉动时快速回到下拉位置:
实体:
@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
}()
结合UITableView
和ResultsController
的通常更新逻辑:
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()
}
}
}
我已经在 Whosebug 上搜索了几个小时,几乎尝试了所有方法。
唯一的想法是不调用 tableView.reloadRowsAtIndexPaths()
也不调用 beginUpdates()
/ endUpdates()
所以两者似乎都会造成这种影响。
但这不是选择,因为 UITableView
根本不会更新。
任何人有什么建议或解决方案吗?
你能做的很简单;
不要总是为核心数据更新重新加载 table,首先检查 table 是否静止..
当您需要从核心数据更新中更新 table 视图时,请检查 table 视图是否正在滚动(有很多方法可以这样做)。如果是这样,请不要依赖,但要这样做:在 table 视图中 class 标记 table 之后需要用布尔值更新,例如 "shouldUpdateTable"。
比在 "did finish scrolling animation" 的 table 视图委托方法中检查是否需要从该布尔标记重新加载 table,如果为真,则重新加载 table 并重置布尔值。
希望此方法有所帮助。
我刚刚找到适合我的解决方案。
有两个操作与我的后台更新冲突:
- dragging/scrolling
- 编辑(上面没有提到,但也需要)
因此,根据 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 )
}
这是editing/delete
的整合
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 )
}
}
}
我有一个 UITableView
与书中的 NSFetchedResultsController
一起使用。
CoreData
对象由后台进程更新,因此每次更新都会自动触发 FetchedResult
和 UITableView
.
在这些更新期间下拉 UITableView
会导致令人不安的跳回顶部,然后在进一步拉动时快速回到下拉位置:
实体:
@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
}()
结合UITableView
和ResultsController
的通常更新逻辑:
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()
}
}
}
我已经在 Whosebug 上搜索了几个小时,几乎尝试了所有方法。
唯一的想法是不调用 tableView.reloadRowsAtIndexPaths()
也不调用 beginUpdates()
/ endUpdates()
所以两者似乎都会造成这种影响。
但这不是选择,因为 UITableView
根本不会更新。
任何人有什么建议或解决方案吗?
你能做的很简单; 不要总是为核心数据更新重新加载 table,首先检查 table 是否静止..
当您需要从核心数据更新中更新 table 视图时,请检查 table 视图是否正在滚动(有很多方法可以这样做)。如果是这样,请不要依赖,但要这样做:在 table 视图中 class 标记 table 之后需要用布尔值更新,例如 "shouldUpdateTable"。 比在 "did finish scrolling animation" 的 table 视图委托方法中检查是否需要从该布尔标记重新加载 table,如果为真,则重新加载 table 并重置布尔值。
希望此方法有所帮助。
我刚刚找到适合我的解决方案。
有两个操作与我的后台更新冲突:
- dragging/scrolling
- 编辑(上面没有提到,但也需要)
因此,根据 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 )
}
这是editing/delete
的整合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 )
}
}
}