NSFetchedResultsController - 反转 NSIndexPath 与乘法部分和分页
NSFetchedResultsController - reversed NSIndexPath with multiply sections and pagination
为了简化我的示例,我开发了 ChatViewController
,我需要在其中按部分显示消息(一天一个部分)。我可以向下滑动 UITableView
加载更多消息。我需要在这里分页。消息从 oldest(顶部)到 latest(table 的底部)显示。显而易见的是,我需要获取按 createdAt
属性 DESCENDING
.
排序的消息
这就是我在 viewDidLoad()
上设置 NSFetcheResultsController
的方式:
private func setupFetchedResultsController() {
let context = NSManagedObjectContext.MR_defaultContext()
let fetchedRequest = NSFetchRequest(entityName: "Message")
let createdAtDescriptor = NSSortDescriptor(key: "createdAt", ascending: false)
fetchedRequest.predicate = NSPredicate(format: "conversation.identifier = %lld", identifier)
fetchedRequest.sortDescriptors = [createdAtDescriptor]
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchedRequest, managedObjectContext: context, sectionNameKeyPath: "normalizedCreatedAt", cacheName: nil)
fetchedResultsController.delegate = self
try! fetchedResultsController.performFetch()
tableView.reloadData()
debugFRC()
}
假设...这是我从 NSFetchedResultsController
:
获取的非常简单的示例
Section 0
0 - 0 - message A, createdAt: '21-07-2016 08:42'
0 - 1 - message B, createdAt: '21-07-2016 08:40'
Section 1
1 - 0 - message C, createdAt: '20-07-2016 08:42'
1 - 1 - message D, createdAt: '20-07-2016 08:40'
1 - 2 - message E, createdAt: '20-07-2016 08:38'
1 - 3 - message F, createdAt: '20-07-2016 08:36'
1 - 4 - message G, createdAt: '20-07-2016 08:34'
1 - 5 - message H, createdAt: '20-07-2016 08:32'
Section 2
2 - 0 - message I, createdAt: '19-07-2016 08:42'
2 - 1 - message J, createdAt: '19-07-2016 08:40'
2 - 2 - message K, createdAt: '19-07-2016 08:38'
但是我需要在我的 UITableView
:
中这样显示
Section 0
0 - 0 - message K, createdAt: '19-07-2016 08:38'
0 - 1 - message J, createdAt: '19-07-2016 08:40'
0 - 2 - message I, createdAt: '19-07-2016 08:42'
Section 1
1 - 0 - message H, createdAt: '20-07-2016 08:32'
1 - 1 - message G, createdAt: '20-07-2016 08:34'
1 - 2 - message F, createdAt: '20-07-2016 08:36'
1 - 3 - message E, createdAt: '20-07-2016 08:38'
1 - 4 - message D, createdAt: '20-07-2016 08:40'
1 - 5 - message C, createdAt: '20-07-2016 08:42'
Section 2
0 - 0 - message B, createdAt: '21-07-2016 08:40'
0 - 1 - message A, createdAt: '21-07-2016 08:42'
所以,我创建了三个辅助私有函数:
//MARK: - Private
private func debugFRC() {
if let sections = fetchedResultsController.sections {
for (index, section) in sections.enumerate() {
print("FRC: \(index) ::: \(section.objects!.count)")
for (index, message) in (section.objects as! [Message]).enumerate() {
print("FRC MESSAGE: \(index) --->>> \(message.createdAt)")
}
}
}
}
private func reversedIndexPathForIndexPath(indexPath: NSIndexPath) -> NSIndexPath {
let section = reversedSectionForSection(indexPath.section)
let row = fetchedResultsController.sections![section].objects!.count - indexPath.row - 1
return NSIndexPath(forRow: row, inSection: section)
}
private func reversedSectionForSection(section: Int) -> Int {
return fetchedResultsController!.sections!.count - section - 1
}
然后我实现了我的 NSFetchedResultsControllerDelegate
:
//MARK: - NSFetchedResultsControllerDelegate
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
let indexSet = NSIndexSet(index: reversedSectionForSection(sectionIndex))
switch type {
case .Insert:
tableView.insertSections(indexSet, withRowAnimation: .Fade)
case .Delete:
tableView.deleteSections(indexSet, withRowAnimation: .Fade)
case .Update:
fallthrough
case .Move:
tableView.reloadSections(indexSet, withRowAnimation: .Fade)
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
if let newIndexPath = newIndexPath {
tableView.insertRowsAtIndexPaths([reversedIndexPathForIndexPath(newIndexPath)], withRowAnimation: .Fade)
}
case .Delete:
if let indexPath = indexPath {
tableView.deleteRowsAtIndexPaths([reversedIndexPathForIndexPath(indexPath)], withRowAnimation: .Fade)
}
case .Update:
if let indexPath = indexPath {
tableView.reloadRowsAtIndexPaths([reversedIndexPathForIndexPath(indexPath)], withRowAnimation: .Fade)
}
case .Move:
if let indexPath = indexPath, let newIndexPath = newIndexPath {
tableView.deleteRowsAtIndexPaths([reversedIndexPathForIndexPath(indexPath)], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([reversedIndexPathForIndexPath(newIndexPath)], withRowAnimation: .Fade)
}
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
和UITableViewDataSource
代表:
//MARK: - UITableViewDataSource
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return fetchedResultsController!.sections!.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchedResultsController.sections![reversedSectionForSection(section)].objects!.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let message = fetchedResultsController?.objectAtIndexPath(reversedIndexPathForIndexPath(indexPath)) as! Message
let cell = tableView.dequeueReusableCellWithIdentifier("cellIdentifier", forIndexPath: reversedIndexPathForIndexPath(indexPath)) as! TableViewCell
cell.cellTitleLabel?.text = message.content
return cell
}
debugFRC()
的输出如下:
FRC: 0 ::: 35
FRC MESSAGE: 0 --->>> 2016-08-23 13:02:32 +0000
FRC MESSAGE: 1 --->>> 2016-08-23 12:59:25 +0000
FRC MESSAGE: 2 --->>> 2016-08-23 11:49:07 +0000
FRC MESSAGE: 3 --->>> 2016-08-23 11:48:21 +0000
FRC MESSAGE: 4 --->>> 2016-08-23 11:32:44 +0000
FRC MESSAGE: 5 --->>> 2016-08-23 11:30:19 +0000
FRC MESSAGE: 6 --->>> 2016-08-23 11:30:16 +0000
FRC MESSAGE: 7 --->>> 2016-08-23 11:27:48 +0000
FRC MESSAGE: 8 --->>> 2016-08-23 11:23:58 +0000
FRC MESSAGE: 9 --->>> 2016-08-23 11:23:32 +0000
FRC MESSAGE: 10 --->>> 2016-08-23 11:23:21 +0000
FRC MESSAGE: 11 --->>> 2016-08-23 11:18:51 +0000
FRC MESSAGE: 12 --->>> 2016-08-23 11:18:45 +0000
FRC MESSAGE: 13 --->>> 2016-08-23 11:18:36 +0000
FRC MESSAGE: 14 --->>> 2016-08-23 11:16:23 +0000
FRC MESSAGE: 15 --->>> 2016-08-23 11:16:09 +0000
FRC MESSAGE: 16 --->>> 2016-08-23 11:15:49 +0000
FRC MESSAGE: 17 --->>> 2016-08-23 11:15:38 +0000
FRC MESSAGE: 18 --->>> 2016-08-23 11:15:29 +0000
FRC MESSAGE: 19 --->>> 2016-08-23 10:57:44 +0000
FRC MESSAGE: 20 --->>> 2016-08-23 10:57:44 +0000
FRC MESSAGE: 21 --->>> 2016-08-23 10:57:43 +0000
FRC MESSAGE: 22 --->>> 2016-08-23 10:57:43 +0000
FRC MESSAGE: 23 --->>> 2016-08-23 10:57:42 +0000
FRC MESSAGE: 24 --->>> 2016-08-23 10:57:42 +0000
FRC MESSAGE: 25 --->>> 2016-08-23 10:57:40 +0000
FRC MESSAGE: 26 --->>> 2016-08-23 10:56:31 +0000
FRC MESSAGE: 27 --->>> 2016-08-23 10:55:49 +0000
FRC MESSAGE: 28 --->>> 2016-08-23 10:55:08 +0000
FRC MESSAGE: 29 --->>> 2016-08-23 10:53:45 +0000
FRC MESSAGE: 30 --->>> 2016-08-23 10:48:59 +0000
FRC MESSAGE: 31 --->>> 2016-08-23 10:48:40 +0000
FRC MESSAGE: 32 --->>> 2016-08-23 10:47:02 +0000
FRC MESSAGE: 33 --->>> 2016-08-23 10:45:06 +0000
FRC MESSAGE: 34 --->>> 2016-08-23 10:45:01 +0000
FRC: 1 ::: 2
FRC MESSAGE: 0 --->>> 2016-08-16 09:16:38 +0000
FRC MESSAGE: 1 --->>> 2016-08-16 09:16:22 +0000
但它不起作用。问题如下:
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete row 34 from section 0 which only contains 2 rows before the update with userInfo (null)
我很确定我错过了什么,但我不知道是什么。有什么想法吗?
你能查一下吗this answer?它做的和你想做的一样。
关键是创建 ** 瞬态字段** 作为复选标记数据模型中特定属性的瞬态字段(例如 sectionTitle
)。
并计算为:
Message{
@NSManaged var body: String?
@NSManaged var seen: NSNumber?
@NSManaged var time: NSDate?
@NSManaged var uid: String?
@NSManaged var conversation: Conversation?
var sectionTitle: String? {
//this is **transient** property
//to set it as transient, check mark the box with same name in data model
return time!.getTimeStrWithDayPrecision()
}
}
然后将NSFetchedResultsController
初始化为:
let request = NSFetchRequest(entityName: "Message")
let sortDiscriptor = NSSortDescriptor(key: "time", ascending: true)
request.sortDescriptors = [sortDiscriptor]
let pred = NSPredicate(format: "conversation.contact.uid == %@", _conversation!.contact!.uid!)
request.predicate = pred
let mainThreadMOC = DatabaseInterface.sharedInstance.mainThreadManagedObjectContext()
fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: mainThreadMOC, sectionNameKeyPath: "sectionTitle", cacheName: nil)
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("Failed to initialize FetchedResultsController: \(error)")
}
为了简化我的示例,我开发了 ChatViewController
,我需要在其中按部分显示消息(一天一个部分)。我可以向下滑动 UITableView
加载更多消息。我需要在这里分页。消息从 oldest(顶部)到 latest(table 的底部)显示。显而易见的是,我需要获取按 createdAt
属性 DESCENDING
.
这就是我在 viewDidLoad()
上设置 NSFetcheResultsController
的方式:
private func setupFetchedResultsController() {
let context = NSManagedObjectContext.MR_defaultContext()
let fetchedRequest = NSFetchRequest(entityName: "Message")
let createdAtDescriptor = NSSortDescriptor(key: "createdAt", ascending: false)
fetchedRequest.predicate = NSPredicate(format: "conversation.identifier = %lld", identifier)
fetchedRequest.sortDescriptors = [createdAtDescriptor]
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchedRequest, managedObjectContext: context, sectionNameKeyPath: "normalizedCreatedAt", cacheName: nil)
fetchedResultsController.delegate = self
try! fetchedResultsController.performFetch()
tableView.reloadData()
debugFRC()
}
假设...这是我从 NSFetchedResultsController
:
Section 0
0 - 0 - message A, createdAt: '21-07-2016 08:42'
0 - 1 - message B, createdAt: '21-07-2016 08:40'
Section 1
1 - 0 - message C, createdAt: '20-07-2016 08:42'
1 - 1 - message D, createdAt: '20-07-2016 08:40'
1 - 2 - message E, createdAt: '20-07-2016 08:38'
1 - 3 - message F, createdAt: '20-07-2016 08:36'
1 - 4 - message G, createdAt: '20-07-2016 08:34'
1 - 5 - message H, createdAt: '20-07-2016 08:32'
Section 2
2 - 0 - message I, createdAt: '19-07-2016 08:42'
2 - 1 - message J, createdAt: '19-07-2016 08:40'
2 - 2 - message K, createdAt: '19-07-2016 08:38'
但是我需要在我的 UITableView
:
Section 0
0 - 0 - message K, createdAt: '19-07-2016 08:38'
0 - 1 - message J, createdAt: '19-07-2016 08:40'
0 - 2 - message I, createdAt: '19-07-2016 08:42'
Section 1
1 - 0 - message H, createdAt: '20-07-2016 08:32'
1 - 1 - message G, createdAt: '20-07-2016 08:34'
1 - 2 - message F, createdAt: '20-07-2016 08:36'
1 - 3 - message E, createdAt: '20-07-2016 08:38'
1 - 4 - message D, createdAt: '20-07-2016 08:40'
1 - 5 - message C, createdAt: '20-07-2016 08:42'
Section 2
0 - 0 - message B, createdAt: '21-07-2016 08:40'
0 - 1 - message A, createdAt: '21-07-2016 08:42'
所以,我创建了三个辅助私有函数:
//MARK: - Private
private func debugFRC() {
if let sections = fetchedResultsController.sections {
for (index, section) in sections.enumerate() {
print("FRC: \(index) ::: \(section.objects!.count)")
for (index, message) in (section.objects as! [Message]).enumerate() {
print("FRC MESSAGE: \(index) --->>> \(message.createdAt)")
}
}
}
}
private func reversedIndexPathForIndexPath(indexPath: NSIndexPath) -> NSIndexPath {
let section = reversedSectionForSection(indexPath.section)
let row = fetchedResultsController.sections![section].objects!.count - indexPath.row - 1
return NSIndexPath(forRow: row, inSection: section)
}
private func reversedSectionForSection(section: Int) -> Int {
return fetchedResultsController!.sections!.count - section - 1
}
然后我实现了我的 NSFetchedResultsControllerDelegate
:
//MARK: - NSFetchedResultsControllerDelegate
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
let indexSet = NSIndexSet(index: reversedSectionForSection(sectionIndex))
switch type {
case .Insert:
tableView.insertSections(indexSet, withRowAnimation: .Fade)
case .Delete:
tableView.deleteSections(indexSet, withRowAnimation: .Fade)
case .Update:
fallthrough
case .Move:
tableView.reloadSections(indexSet, withRowAnimation: .Fade)
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
if let newIndexPath = newIndexPath {
tableView.insertRowsAtIndexPaths([reversedIndexPathForIndexPath(newIndexPath)], withRowAnimation: .Fade)
}
case .Delete:
if let indexPath = indexPath {
tableView.deleteRowsAtIndexPaths([reversedIndexPathForIndexPath(indexPath)], withRowAnimation: .Fade)
}
case .Update:
if let indexPath = indexPath {
tableView.reloadRowsAtIndexPaths([reversedIndexPathForIndexPath(indexPath)], withRowAnimation: .Fade)
}
case .Move:
if let indexPath = indexPath, let newIndexPath = newIndexPath {
tableView.deleteRowsAtIndexPaths([reversedIndexPathForIndexPath(indexPath)], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([reversedIndexPathForIndexPath(newIndexPath)], withRowAnimation: .Fade)
}
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
和UITableViewDataSource
代表:
//MARK: - UITableViewDataSource
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return fetchedResultsController!.sections!.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fetchedResultsController.sections![reversedSectionForSection(section)].objects!.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let message = fetchedResultsController?.objectAtIndexPath(reversedIndexPathForIndexPath(indexPath)) as! Message
let cell = tableView.dequeueReusableCellWithIdentifier("cellIdentifier", forIndexPath: reversedIndexPathForIndexPath(indexPath)) as! TableViewCell
cell.cellTitleLabel?.text = message.content
return cell
}
debugFRC()
的输出如下:
FRC: 0 ::: 35 FRC MESSAGE: 0 --->>> 2016-08-23 13:02:32 +0000 FRC MESSAGE: 1 --->>> 2016-08-23 12:59:25 +0000 FRC MESSAGE: 2 --->>> 2016-08-23 11:49:07 +0000 FRC MESSAGE: 3 --->>> 2016-08-23 11:48:21 +0000 FRC MESSAGE: 4 --->>> 2016-08-23 11:32:44 +0000 FRC MESSAGE: 5 --->>> 2016-08-23 11:30:19 +0000 FRC MESSAGE: 6 --->>> 2016-08-23 11:30:16 +0000 FRC MESSAGE: 7 --->>> 2016-08-23 11:27:48 +0000 FRC MESSAGE: 8 --->>> 2016-08-23 11:23:58 +0000 FRC MESSAGE: 9 --->>> 2016-08-23 11:23:32 +0000 FRC MESSAGE: 10 --->>> 2016-08-23 11:23:21 +0000 FRC MESSAGE: 11 --->>> 2016-08-23 11:18:51 +0000 FRC MESSAGE: 12 --->>> 2016-08-23 11:18:45 +0000 FRC MESSAGE: 13 --->>> 2016-08-23 11:18:36 +0000 FRC MESSAGE: 14 --->>> 2016-08-23 11:16:23 +0000 FRC MESSAGE: 15 --->>> 2016-08-23 11:16:09 +0000 FRC MESSAGE: 16 --->>> 2016-08-23 11:15:49 +0000 FRC MESSAGE: 17 --->>> 2016-08-23 11:15:38 +0000 FRC MESSAGE: 18 --->>> 2016-08-23 11:15:29 +0000 FRC MESSAGE: 19 --->>> 2016-08-23 10:57:44 +0000 FRC MESSAGE: 20 --->>> 2016-08-23 10:57:44 +0000 FRC MESSAGE: 21 --->>> 2016-08-23 10:57:43 +0000 FRC MESSAGE: 22 --->>> 2016-08-23 10:57:43 +0000 FRC MESSAGE: 23 --->>> 2016-08-23 10:57:42 +0000 FRC MESSAGE: 24 --->>> 2016-08-23 10:57:42 +0000 FRC MESSAGE: 25 --->>> 2016-08-23 10:57:40 +0000 FRC MESSAGE: 26 --->>> 2016-08-23 10:56:31 +0000 FRC MESSAGE: 27 --->>> 2016-08-23 10:55:49 +0000 FRC MESSAGE: 28 --->>> 2016-08-23 10:55:08 +0000 FRC MESSAGE: 29 --->>> 2016-08-23 10:53:45 +0000 FRC MESSAGE: 30 --->>> 2016-08-23 10:48:59 +0000 FRC MESSAGE: 31 --->>> 2016-08-23 10:48:40 +0000 FRC MESSAGE: 32 --->>> 2016-08-23 10:47:02 +0000 FRC MESSAGE: 33 --->>> 2016-08-23 10:45:06 +0000 FRC MESSAGE: 34 --->>> 2016-08-23 10:45:01 +0000 FRC: 1 ::: 2 FRC MESSAGE: 0 --->>> 2016-08-16 09:16:38 +0000 FRC MESSAGE: 1 --->>> 2016-08-16 09:16:22 +0000
但它不起作用。问题如下:
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete row 34 from section 0 which only contains 2 rows before the update with userInfo (null)
我很确定我错过了什么,但我不知道是什么。有什么想法吗?
你能查一下吗this answer?它做的和你想做的一样。
关键是创建 ** 瞬态字段** 作为复选标记数据模型中特定属性的瞬态字段(例如 sectionTitle
)。
并计算为:
Message{
@NSManaged var body: String?
@NSManaged var seen: NSNumber?
@NSManaged var time: NSDate?
@NSManaged var uid: String?
@NSManaged var conversation: Conversation?
var sectionTitle: String? {
//this is **transient** property
//to set it as transient, check mark the box with same name in data model
return time!.getTimeStrWithDayPrecision()
}
}
然后将NSFetchedResultsController
初始化为:
let request = NSFetchRequest(entityName: "Message")
let sortDiscriptor = NSSortDescriptor(key: "time", ascending: true)
request.sortDescriptors = [sortDiscriptor]
let pred = NSPredicate(format: "conversation.contact.uid == %@", _conversation!.contact!.uid!)
request.predicate = pred
let mainThreadMOC = DatabaseInterface.sharedInstance.mainThreadManagedObjectContext()
fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: mainThreadMOC, sectionNameKeyPath: "sectionTitle", cacheName: nil)
fetchedResultsController.delegate = self
do {
try fetchedResultsController.performFetch()
} catch {
fatalError("Failed to initialize FetchedResultsController: \(error)")
}