NSFetchedResultsController 仅在控制器即将插入另一个部分时将相同的单元格插入两个部分
NSFetchedResultsController inserts the same cell into two sections only when controller is about to insert another section
这是我的 Message.swift
文件:
@objc(Message)
class Message: NSManagedObject {
@NSManaged var content: String
@NSManaged var createdAt: NSDate
@NSManaged var identifier: Int64
@NSManaged var conversation: Conversation
@NSManaged var sender: Contributor
var normalizedCreatedAt: NSDate {
return createdAt.dateWithDayMonthAndYearComponents()!
}
}
这是我设置 FRC 的方式:
private func setupFetchedResultsController() {
let context = NSManagedObjectContext.MR_defaultContext()
let fetchRequest = NSFetchRequest(entityName: "Message")
let createdAtDescriptor = NSSortDescriptor(key: "createdAt", ascending: true)
fetchRequest.predicate = NSPredicate(format: "conversation.identifier = %lld", conversation.identifier)
fetchRequest.sortDescriptors = [createdAtDescriptor]
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: "normalizedCreatedAt", cacheName: nil)
fetchedResultsController.delegate = self
try! fetchedResultsController.performFetch()
tableView.reloadData()
}
及其标准代表。
在 viewDidLoad
我的控制器有 1 个部分,1 行。我使用以下函数在控制台上打印它:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("--->>>")
print(section)
print(fetchedResultsController.sections![section].objects!.count)
return fetchedResultsController.sections![section].objects!.count
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return fetchedResultsController?.sections?.count ?? 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let message = fetchedResultsController?.objectAtIndexPath(indexPath) as! Message
let cellIdentifier = message.sender.identifier == Settings.currentUser?.profile?.identifier ? SentTableViewCellIdentifier : ReceivedTableViewCellIdentifier
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! TableViewCell
cell.cellTitleLabel?.text = message.content
return cell
}
输出如下:
--->>>
0
1
一旦我尝试添加另一条不同部分的消息,我得到以下信息:
--->>>
0
2
--->>>
1
1
然后报错:
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)
为什么会这样?
NSFetchedResultsController
出于某种原因将同一个单元格加载到两个部分:first 和 second。为什么?
注意:
- 问题仅在 FRC 插入新部分时出现。如果需要将行插入现有部分,则没有问题。问题与部分密切相关。
- 问题仅在 FRC 尝试插入第二部分时出现。到三四段的时候,完全没有问题。
我试过你的代码,只做了一处改动,这个:
var normalizedCreatedAt: String {
return getTimeStrWithDayPrecision(createdAt!)
}
func getTimeStrWithDayPrecision(date: NSDate) -> String {
let formatter = NSDateFormatter()
formatter.timeStyle = .NoStyle
formatter.dateStyle = .ShortStyle
formatter.doesRelativeDateFormatting = true
return formatter.stringFromDate(date)
}
它工作正常,即使对于第 2 部分也是如此!
为了演示 purpose,我添加了 ADD
按钮,通过按下它,代码会将当前日期字符串为 content
的新消息添加到数据库中。
这是我的完整实现:
查看控制器-
class ChatTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
private var fetchedResultsController: NSFetchedResultsController?
private var _mainThreadMOC: NSManagedObjectContext?
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
setupFetchedResultsController()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
private func getMainMOC() -> NSManagedObjectContext {
if _mainThreadMOC == nil {
let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
_mainThreadMOC = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
_mainThreadMOC!.persistentStoreCoordinator = appDel.persistentStoreCoordinator
_mainThreadMOC!.undoManager = nil
}
return _mainThreadMOC!
}
private func setupFetchedResultsController() {
let fetchRequest = NSFetchRequest(entityName: "Message")
let createdAtDescriptor = NSSortDescriptor(key: "createdAt", ascending: true)
fetchRequest.sortDescriptors = [createdAtDescriptor]
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: getMainMOC(), sectionNameKeyPath: "normalizedCreatedAt", cacheName: nil)
fetchedResultsController!.delegate = self
try! fetchedResultsController!.performFetch()
tableView.reloadData()
}
@IBAction func addMessage(sender: AnyObject) {
print("addMessage")
let MOC = getMainMOC()
let date = NSDate()
let _ = Message(text: "\(date)", moc: MOC)
do {
try MOC.save()
}catch {
print("Error saving main MOC: \(error)")
}
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return fetchedResultsController?.sections?.count ?? 0
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let sectionInfo = fetchedResultsController!.sections! as [NSFetchedResultsSectionInfo]
let title = sectionInfo[section].name
let headerHeight:CGFloat = tableView.sectionHeaderHeight
let headerLbl = UILabel(frame: CGRectMake(0, 0, tableView.frame.width, headerHeight))
headerLbl.backgroundColor = UIColor.lightGrayColor()
headerLbl.textAlignment = .Center
headerLbl.text = title
return headerLbl
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("--->>>")
print(section)
print(fetchedResultsController?.sections![section].objects!.count)
return (fetchedResultsController?.sections![section].objects!.count)!
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let message = fetchedResultsController?.objectAtIndexPath(indexPath) as! Message
let cell = tableView.dequeueReusableCellWithIdentifier("MessageCellId", forIndexPath: indexPath)
cell.textLabel?.text = message.content!
return cell
}
//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: 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([newIndexPath], withRowAnimation: .Fade)
}
case .Delete:
if let indexPath = indexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
case .Update:
if let indexPath = indexPath {
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
case .Move:
if let indexPath = indexPath, let newIndexPath = newIndexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
}
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
}
留言-
func getTimeStrWithDayPrecision(date: NSDate) -> String {
let formatter = NSDateFormatter()
formatter.timeStyle = .NoStyle
formatter.dateStyle = .ShortStyle
formatter.doesRelativeDateFormatting = true
return formatter.stringFromDate(date)
}
extension Message {
@NSManaged var content: String?
@NSManaged var createdAt: NSDate?
var normalizedCreatedAt: String {
return getTimeStrWithDayPrecision(createdAt!)
}
}
class Message: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
override init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?) {
super.init(entity: entity, insertIntoManagedObjectContext: context)
}
init(text: String, moc:NSManagedObjectContext) {
let entity = NSEntityDescription.entityForName("Message", inManagedObjectContext: moc)
super.init(entity: entity!, insertIntoManagedObjectContext: moc)
content = text
createdAt = NSDate()
}
}
这是 iPad 屏幕截图:
为了测试多个部分,我更改了日期和时间设置 os iPad。
我不知道为什么,但要使其正常工作,您需要更换:
fetchedResultsController.sections![section].objects!.count
与
fetchedResultsController.sections![section].numberOfObjects
由于某些原因objects!.count
returns 不正确 与numberOfObjects
相反的对象数量 属性.
这是我的 Message.swift
文件:
@objc(Message)
class Message: NSManagedObject {
@NSManaged var content: String
@NSManaged var createdAt: NSDate
@NSManaged var identifier: Int64
@NSManaged var conversation: Conversation
@NSManaged var sender: Contributor
var normalizedCreatedAt: NSDate {
return createdAt.dateWithDayMonthAndYearComponents()!
}
}
这是我设置 FRC 的方式:
private func setupFetchedResultsController() {
let context = NSManagedObjectContext.MR_defaultContext()
let fetchRequest = NSFetchRequest(entityName: "Message")
let createdAtDescriptor = NSSortDescriptor(key: "createdAt", ascending: true)
fetchRequest.predicate = NSPredicate(format: "conversation.identifier = %lld", conversation.identifier)
fetchRequest.sortDescriptors = [createdAtDescriptor]
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: "normalizedCreatedAt", cacheName: nil)
fetchedResultsController.delegate = self
try! fetchedResultsController.performFetch()
tableView.reloadData()
}
及其标准代表。
在 viewDidLoad
我的控制器有 1 个部分,1 行。我使用以下函数在控制台上打印它:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("--->>>")
print(section)
print(fetchedResultsController.sections![section].objects!.count)
return fetchedResultsController.sections![section].objects!.count
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return fetchedResultsController?.sections?.count ?? 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let message = fetchedResultsController?.objectAtIndexPath(indexPath) as! Message
let cellIdentifier = message.sender.identifier == Settings.currentUser?.profile?.identifier ? SentTableViewCellIdentifier : ReceivedTableViewCellIdentifier
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! TableViewCell
cell.cellTitleLabel?.text = message.content
return cell
}
输出如下:
--->>>
0
1
一旦我尝试添加另一条不同部分的消息,我得到以下信息:
--->>>
0
2
--->>>
1
1
然后报错:
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)
为什么会这样?
NSFetchedResultsController
出于某种原因将同一个单元格加载到两个部分:first 和 second。为什么?
注意:
- 问题仅在 FRC 插入新部分时出现。如果需要将行插入现有部分,则没有问题。问题与部分密切相关。
- 问题仅在 FRC 尝试插入第二部分时出现。到三四段的时候,完全没有问题。
我试过你的代码,只做了一处改动,这个:
var normalizedCreatedAt: String {
return getTimeStrWithDayPrecision(createdAt!)
}
func getTimeStrWithDayPrecision(date: NSDate) -> String {
let formatter = NSDateFormatter()
formatter.timeStyle = .NoStyle
formatter.dateStyle = .ShortStyle
formatter.doesRelativeDateFormatting = true
return formatter.stringFromDate(date)
}
它工作正常,即使对于第 2 部分也是如此!
为了演示 purpose,我添加了 ADD
按钮,通过按下它,代码会将当前日期字符串为 content
的新消息添加到数据库中。
这是我的完整实现: 查看控制器-
class ChatTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
private var fetchedResultsController: NSFetchedResultsController?
private var _mainThreadMOC: NSManagedObjectContext?
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
setupFetchedResultsController()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
private func getMainMOC() -> NSManagedObjectContext {
if _mainThreadMOC == nil {
let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
_mainThreadMOC = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
_mainThreadMOC!.persistentStoreCoordinator = appDel.persistentStoreCoordinator
_mainThreadMOC!.undoManager = nil
}
return _mainThreadMOC!
}
private func setupFetchedResultsController() {
let fetchRequest = NSFetchRequest(entityName: "Message")
let createdAtDescriptor = NSSortDescriptor(key: "createdAt", ascending: true)
fetchRequest.sortDescriptors = [createdAtDescriptor]
fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: getMainMOC(), sectionNameKeyPath: "normalizedCreatedAt", cacheName: nil)
fetchedResultsController!.delegate = self
try! fetchedResultsController!.performFetch()
tableView.reloadData()
}
@IBAction func addMessage(sender: AnyObject) {
print("addMessage")
let MOC = getMainMOC()
let date = NSDate()
let _ = Message(text: "\(date)", moc: MOC)
do {
try MOC.save()
}catch {
print("Error saving main MOC: \(error)")
}
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return fetchedResultsController?.sections?.count ?? 0
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let sectionInfo = fetchedResultsController!.sections! as [NSFetchedResultsSectionInfo]
let title = sectionInfo[section].name
let headerHeight:CGFloat = tableView.sectionHeaderHeight
let headerLbl = UILabel(frame: CGRectMake(0, 0, tableView.frame.width, headerHeight))
headerLbl.backgroundColor = UIColor.lightGrayColor()
headerLbl.textAlignment = .Center
headerLbl.text = title
return headerLbl
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("--->>>")
print(section)
print(fetchedResultsController?.sections![section].objects!.count)
return (fetchedResultsController?.sections![section].objects!.count)!
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let message = fetchedResultsController?.objectAtIndexPath(indexPath) as! Message
let cell = tableView.dequeueReusableCellWithIdentifier("MessageCellId", forIndexPath: indexPath)
cell.textLabel?.text = message.content!
return cell
}
//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: 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([newIndexPath], withRowAnimation: .Fade)
}
case .Delete:
if let indexPath = indexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
case .Update:
if let indexPath = indexPath {
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
case .Move:
if let indexPath = indexPath, let newIndexPath = newIndexPath {
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
}
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
}
留言-
func getTimeStrWithDayPrecision(date: NSDate) -> String {
let formatter = NSDateFormatter()
formatter.timeStyle = .NoStyle
formatter.dateStyle = .ShortStyle
formatter.doesRelativeDateFormatting = true
return formatter.stringFromDate(date)
}
extension Message {
@NSManaged var content: String?
@NSManaged var createdAt: NSDate?
var normalizedCreatedAt: String {
return getTimeStrWithDayPrecision(createdAt!)
}
}
class Message: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
override init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?) {
super.init(entity: entity, insertIntoManagedObjectContext: context)
}
init(text: String, moc:NSManagedObjectContext) {
let entity = NSEntityDescription.entityForName("Message", inManagedObjectContext: moc)
super.init(entity: entity!, insertIntoManagedObjectContext: moc)
content = text
createdAt = NSDate()
}
}
这是 iPad 屏幕截图:
为了测试多个部分,我更改了日期和时间设置 os iPad。
我不知道为什么,但要使其正常工作,您需要更换:
fetchedResultsController.sections![section].objects!.count
与
fetchedResultsController.sections![section].numberOfObjects
由于某些原因objects!.count
returns 不正确 与numberOfObjects
相反的对象数量 属性.