ios - NSFetchedResultsController inserts the same cell into two sections only when controller is about to insert another section -
this message.swift
file:
@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()! } }
this how setup 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() }
with standard delegate.
on viewdidload
controller has 1 section 1 row. print on console using following function:
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 }
and output following:
--->>> 0 1
once try add 1 message different section, following get:
--->>> 0 2 --->>> 1 1
and error:
coredata: error: serious application error. exception caught delegate of nsfetchedresultscontroller during call -controllerdidchangecontent:. invalid update: invalid number of rows in section 0. number of rows contained in existing section after update (2) must equal number of rows contained in section before update (1), plus or minus number of rows inserted or deleted section (0 inserted, 0 deleted) , plus or minus number of rows moved or out of section (0 moved in, 0 moved out). userinfo (null)
why happens that?
nsfetchedresultscontroller
reason loads same cell 2 sections: first , second. why?
note:
- the problem arise when frc insert new section. if needs insert row existing section, there no problem. issue strong related sections.
- the problem when frc try insert second section. when third or fourth section, there no problem @ all.
i tried code, made 1 change, this:
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) }
and works fine, 2nd section also!
for demo purpose, added add
button, pressing it, code add new message current date string content
db.
here complete implementation: view controller-
class chattableviewcontroller: uitableviewcontroller, nsfetchedresultscontrollerdelegate { private var fetchedresultscontroller: nsfetchedresultscontroller? private var _mainthreadmoc: nsmanagedobjectcontext? override func viewdidload() { super.viewdidload() // uncomment following line preserve selection between presentations // self.clearsselectiononviewwillappear = false // uncomment following line display edit button in navigation bar view controller. // self.navigationitem.rightbarbuttonitem = self.editbuttonitem() setupfetchedresultscontroller() } override func didreceivememorywarning() { super.didreceivememorywarning() // dispose of resources can 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) { 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! [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() } }
message-
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 add functionality 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() } }
for testing multiple sections, changed date & time setting os ipad.
Comments
Post a Comment