NSFetchedResultsController 仅在控制器即将插入另一个部分时将相同的单元格插入两个部分
Posted
技术标签:
【中文标题】NSFetchedResultsController 仅在控制器即将插入另一个部分时将相同的单元格插入两个部分【英文标题】:NSFetchedResultsController inserts the same cell into two sections only when controller is about to insert another section 【发布时间】:2016-08-29 08:22:18 【问题描述】:这是我的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:错误:严重的应用程序错误。在调用 -controllerDidChangeContent: 期间,从 NSFetchedResultsController 的委托中捕获了异常。无效更新:第 0 节中的行数无效。更新后现有节中包含的行数 (2) 必须等于更新前该节中包含的行数 (1),加上或减去数字从该部分插入或删除的行数(0 插入,0 删除)加上或减去移入或移出该部分的行数(0 移入,0 移出)。与 userInfo (null)
为什么会这样?
NSFetchedResultsController
出于某种原因将同一个单元格加载到两个部分:first 和 second。为什么?
注意:
仅当 FRC 插入新部分时才会出现此问题。如果它需要将行插入现有部分,没有问题。问题与部分密切相关。 问题仅在 FRC 尝试插入 SECOND 部分时出现。到了第三、第四节的时候,完全没有问题。【问题讨论】:
你能添加numberOfSectionsInTableView
和cellForRowAtIndexPath
的代码吗?
是的,我更新了问题。
代码看起来不错,没有模棱两可的地方。只需检查createdAt
属性是否正确,dateWithDayMonthAndYearComponents()
方法工作正常。
是的,它工作正常......他们两个...... :(
我也在使用完全相同的代码。还是,让我再检查一下……
【参考方案1】:
我试过你的代码,只做了一个改动,这个:
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)
而且效果很好,即使是第二部分!
出于演示目的,我添加了ADD
按钮,按下它,代码会将当前日期字符串为content
的新消息添加到DB。
这是我的完整实现: 视图控制器-
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 截图:
为了测试多个部分,我更改了 iPad 上的日期和时间设置。
【讨论】:
我认为这与String
无关。我将 normalizedCreatedAt
替换为 content
以创建部分,但问题仍然存在。
好的,我现在试试。
我用你的建议替换了normalizedCreatedAt
,但还是一样...:(我应该做更多吗?
您是否使用单独的演示项目检查过这一点,或者您正在对现有项目进行更改?尝试从上面实现一切,包括创建和保存消息。
你保存的和我不一样,因为我用magicRecord,但是一切都是一样的......【参考方案2】:
我不知道为什么,但要使其正常工作,您需要更换:
fetchedResultsController.sections![section].objects!.count
与
fetchedResultsController.sections![section].numberOfObjects
由于某种原因,objects!.count
返回与numberOfObjects
属性相反的不正确对象数。
【讨论】:
以上是关于NSFetchedResultsController 仅在控制器即将插入另一个部分时将相同的单元格插入两个部分的主要内容,如果未能解决你的问题,请参考以下文章