ios - UITableView delete/add row causes CoreData: Serious Application Error if another object has been selected in the MasterView of a SplitViewController -
update 18/3 #2. i've started counting beginupdates , endupdates make sure they're even. right before there's exception, out of sync. not sure why though.
update 18/3: think i've found problem, i'm not sure if know how fix it. after experimenting couple hours, found crash app when had selected more 1 item in master tableview of svc during session. when item selected in master tableview, detail table view gets new object set , refreshtables called if it's halfway through update/move/delete/insert cycle. hence problem; think has instructions update old story object though new story object has been set on detailviewcontroller.
how can animation/coredata/tableview updates processed before new story set in detail view? save changes managedobjectcontext when seteditting == no. i'm guessing need make custom setstory setter processes updates uitableview/coredata set before accepting new object?
this code gets called on master tableview controller of svc in didselectrowatindexpath:
[detailviewcontroller setstory:storyset]; //where storyset new story object [detailviewcontroller refreshtables];
i have intermittent errors on attempting delete row action won't animate , application hangs following error (the row deleted coredata set though). happens if have chosen more 1 row master tableview controller in svc in 1 session.
after googling thought might problem (void)controller:(nsfetchedresultscontroller *)controller didchangeobject:(id) called after update animate changes user has made.
how can fix these intermittent bugs?
- 16/3i've tried simplify code. i've removed calls managed object context , put them in setediting, removed superfluous [self.tableview reloaddata] , [self.tableview setneedsdisplay] , invalidated 'reordering' bool (it's still in code, it's set no, makes no difference). uitableview more stable ever, still manage intermittent errors on delete (and on add) - seems take while crash still will.**
- 15/3: think has coredata / uitableview being out of sync - coredata thinks has less or more uitableview
- there's no problem coredata set, it's animation/ui side of things (things removed)
- it's intermittent, on deletes
- after railwayparade implemented nsfetchedresultschangemove in didchangeobject: fixed moving error not delete errors.
can see i've missed or can check? happy give more information if helps solve problem.
apologies obscene amount of code posted here.
the error:
coredata: error: serious application error. exception caught delegate of nsfetchedresultscontroller during call -controllerdidchangecontent:. attempt insert row 3 section 0, there 3 rows in section 0 after update userinfo (null)
// // makesentencetableviewcontroller.h // storybot // // created glen storey on 25/10/10. // copyright 2010 glen storey. rights reserved. // #import <uikit/uikit.h> #import "addstoryitem.h" #import "story.h" #import "sentence.h" @interface makesentencetableviewcontroller : uitableviewcontroller <nsfetchedresultscontrollerdelegate, addstoryitemdelegate, uinavigationcontrollerdelegate, uiimagepickercontrollerdelegate, uitextfielddelegate, uipopovercontrollerdelegate> { nsmanagedobjectcontext *managedobjectcontext; nsfetchedresultscontroller *fetchedresultscontroller; uipopovercontroller *popovercontroller; uibarbuttonitem *playbuttonitem; uibarbuttonitem *addbuttonitem; bool reordering; bool insert; bool delete; bool move; int beginupdatescount; int endupdatescount; } @property (nonatomic, retain) story *story; @property (nonatomic, retain) nsmanagedobjectcontext *managedobjectcontext; @property (nonatomic, retain) uibarbuttonitem *playbuttonitem; @property (nonatomic, retain) uibarbuttonitem *addbuttonitem; @property bool reordering, insert, delete, move; -(ibaction)createstorymodal:(id)sender; -(void)refreshtables; -(ibaction)pushshare:(id)sender; @end // // makesentencetableviewcontroller.m // // // created glen storey on 25/10/10. // copyright 2010 glen storey. rights reserved. // #import "makesentencetableviewcontroller.h" #import "shareviewcontroller.h" #import "storybotappdelegate.h" @implementation makesentencetableviewcontroller @synthesize story, managedobjectcontext, addbuttonitem, playbuttonitem, reordering, insert, delete, move; -(void)addstoryitemaction:(nsstring*)text order:(nsnumber*)order image:(nsstring*)image thumb:(nsstring*)thumb{ nslog(@"text: %@, order: %@, image: %@.", text, order, image); nslog(@"beginupdatescount: %d vs. endupdatescount: %d", beginupdatescount, endupdatescount); nsset *sentences = [story sentences]; nsnumber *maxorder = [sentences valueforkeypath:@"@max.order"]; nslog(@"maxorder: %@", maxorder); if(maxorder == 0){ maxorder = [[nsnumber alloc] initwithinteger: 0]; } //make new sentence! sentence *sentence = [nsentitydescription insertnewobjectforentityforname:@"sentence" inmanagedobjectcontext:managedobjectcontext]; [sentence settext: text]; [sentence setimage: image]; [sentence setthumb: thumb]; [sentence setbelongsto: story]; if([maxorder intvalue] >= 1 ){ [sentence setorder: [[nsnumber alloc] initwithinteger:[maxorder intvalue]+1]]; }else{ [sentence setorder: [[nsnumber alloc] initwithinteger:1]]; } nsmutableset *mutableset = [[nsmutableset alloc] initwithset:sentences]; [mutableset addobject:sentence]; //nslog(@"sentences before setwithset %@", mutableset); sentences = [[nsset alloc] initwithset: mutableset]; //nslog(@"sentences after setwithset %@", sentences); [story setsentences:sentences]; //nserror *error; //bool issaved = [managedobjectcontext save:&error]; //nslog(@"issaved? %@", (issaved ? @"yes" :@"no ") ); //if (!issaved) { //nslog(@"%@:%s error saving context: %@", [self class], _cmd, [error localizeddescription]); //don't worry warning - rem out when finished (just log) // return; //} [sentences release]; [mutableset release]; //[self.tableview reloaddata]; //[self.tableview setneedsdisplay]; [self dismissmodalviewcontrolleranimated:yes]; } #pragma mark - #pragma mark view lifecycle -(id)initwithnibname:(nsstring*)name bundle:(nsbundle*)bundle; { self = [super initwithnibname:name bundle:bundle]; if (self) { self.title = @"my stories"; addbuttonitem = [[uibarbuttonitem alloc] initwithbarbuttonsystemitem:uibarbuttonsystemitemadd target:self action:@selector(createstorymodal:)]; playbuttonitem = [[uibarbuttonitem alloc] initwithbarbuttonsystemitem:uibarbuttonsystemitemplay target:self action:@selector(pushshare:)]; if (ui_user_interface_idiom() == uiuserinterfaceidiompad) { [addbuttonitem setenabled:no]; [playbuttonitem setenabled:no]; } nsarray* toolbaritems = [nsarray arraywithobjects: addbuttonitem, [[uibarbuttonitem alloc] initwithbarbuttonsystemitem:uibarbuttonsystemitemflexiblespace target:nil action:nil], playbuttonitem, nil]; [toolbaritems makeobjectsperformselector:@selector(release)]; self.toolbaritems = toolbaritems; //nslog(@"self: %@ self.toolbaritems: %@", self, self.toolbaritems); //do need release uibarbuttonitems? nslog(@"initwithnibname:"); } return self; } -(void)refreshtables{ //use refresh new table content. calls self.tableview reloaddata don't need call afterward. nslog(@"===================================refreshtables"); nslog(@"story object %@", story); if (managedobjectcontext == nil) { nslog(@"managedobjectcontext == nil"); managedobjectcontext = [(storybotappdelegate *)[[uiapplication sharedapplication] delegate] managedobjectcontext]; nslog(@"after managedobjectcontext: %@", managedobjectcontext); }else{ nslog(@"managedobjectcontext != nil"); } nsfetchrequest *request = [[nsfetchrequest alloc] init]; nsentitydescription *entity = [nsentitydescription entityforname:@"sentence" inmanagedobjectcontext:managedobjectcontext]; [request setentity:entity]; //sorting stuff: nssortdescriptor *sortdescriptor = [[nssortdescriptor alloc] initwithkey:@"order" ascending: yes]; nsarray *sortdescriptors = [[nsarray alloc] initwithobjects: sortdescriptor, nil]; [request setsortdescriptors:sortdescriptors]; [sortdescriptors release]; [sortdescriptor release]; nspredicate *predicatetitle = [nspredicate predicatewithformat:@"belongsto=%@",story]; [request setpredicate :predicatetitle]; nsdateformatter *dateformatter = [[[nsdateformatter alloc] init] autorelease]; [dateformatter setdateformat:@"eee, d mmm yyyy hh:mm:ss"]; nsstring *datestring = [dateformatter stringfromdate:[story creationdate]]; nslog(@"datestring: %@", datestring); fetchedresultscontroller = [[nsfetchedresultscontroller alloc] initwithfetchrequest:request managedobjectcontext:managedobjectcontext sectionnamekeypath:nil cachename:datestring]; fetchedresultscontroller.delegate = self; [request release]; nserror *error; [fetchedresultscontroller performfetch:&error]; [self.tableview reloaddata]; } - (void)viewdidload { [super viewdidload]; self.title = @"my story"; nslog(@"passed story object: %@", story); //nslog(@"managedobjectcontext: %@", managedobjectcontext); //need predicate belongsto here. self.tableview.rowheight = 50; if(story != null){ if (managedobjectcontext == nil) { nslog(@"managedobjectcontext == nil"); managedobjectcontext = [(storybotappdelegate *)[[uiapplication sharedapplication] delegate] managedobjectcontext]; nslog(@"after managedobjectcontext: %@", managedobjectcontext); }else{ nslog(@"managedobjectcontext != nil"); } nsfetchrequest *request = [[nsfetchrequest alloc] init]; nsentitydescription *entity = [nsentitydescription entityforname:@"sentence" inmanagedobjectcontext:managedobjectcontext]; [request setentity:entity]; //sorting stuff: nssortdescriptor *sortdescriptor = [[nssortdescriptor alloc] initwithkey:@"order" ascending: yes]; nsarray *sortdescriptors = [[nsarray alloc] initwithobjects: sortdescriptor, nil]; [request setsortdescriptors:sortdescriptors]; [sortdescriptors release]; [sortdescriptor release]; nspredicate *predicatetitle = [nspredicate predicatewithformat:@"belongsto=%@",story]; [request setpredicate :predicatetitle]; fetchedresultscontroller = [[nsfetchedresultscontroller alloc] initwithfetchrequest:request managedobjectcontext:managedobjectcontext sectionnamekeypath:nil cachename:nil]; fetchedresultscontroller.delegate = self; [request release]; nserror *error; [fetchedresultscontroller performfetch:&error]; } #pragma mark - #pragma mark table view data source - (nsinteger)numberofsectionsintableview:(uitableview *)tableview { // return number of sections. return 1; } - (nsinteger)tableview:(uitableview *)tableview numberofrowsinsection:(nsinteger)section { //return number of rows in section. nsarray *sections = [fetchedresultscontroller sections]; nsinteger count = 0; if ([sections count]){ id <nsfetchedresultssectioninfo> sectioninfo = [sections objectatindex:section]; count = [sectioninfo numberofobjects]; } nslog(@"# of rows in section %d", count); return count; } // customize appearance of table view cells. - (uitableviewcell *)tableview:(uitableview *)tableview cellforrowatindexpath:(nsindexpath *)indexpath { static nsstring *cellidentifier = @"cell"; uitableviewcell *cell = [tableview dequeuereusablecellwithidentifier:cellidentifier]; if (cell == nil) { cell = [[[uitableviewcell alloc] initwithstyle:uitableviewcellstyledefault reuseidentifier:cellidentifier] autorelease]; } // configure cell... sentence *sentenceatcell = [fetchedresultscontroller objectatindexpath:indexpath]; //nslog(@"sentenceatcell: %@", sentenceatcell); cell.textlabel.text = [sentenceatcell text]; nsarray *paths = nssearchpathfordirectoriesindomains(nsdocumentdirectory, nsuserdomainmask, yes); nsstring *uniquepath = [[paths objectatindex:0] stringbyappendingpathcomponent:[sentenceatcell thumb]]; // should crop want - you've got create croprect. uiimage *largeimage = [uiimage imagewithcontentsoffile: uniquepath]; cgrect croprect = cgrectmake(0, 0, 66, 50); /*if ([[uiscreen mainscreen] respondstoselector:@selector(scale)]) { if ([[uiscreen mainscreen] scale] == 2) { // running iphone 4 or equiv. res device. croprect = cgrectmake(15, 14, 100, 75); } }*/ cgimageref imageref = cgimagecreatewithimageinrect([largeimage cgimage], croprect); cell.imageview.image = [uiimage imagewithcgimage: imageref]; cgimagerelease(imageref); //nslog(@"indexpath: %@", indexpath); return cell; } // override support editing table view. - (void)tableview:(uitableview *)tableview commiteditingstyle:(uitableviewcelleditingstyle)editingstyle forrowatindexpath:(nsindexpath *)indexpath { if (editingstyle == uitableviewcelleditingstyledelete) { nslog(@"delete row"); nsfilemanager *filemanager = [nsfilemanager defaultmanager]; nsarray *paths = nssearchpathfordirectoriesindomains(nsdocumentdirectory, nsuserdomainmask, yes); nsstring *documentsdirectorypath = [paths objectatindex:0]; // 1. @ sentence we're delete. sentence *sentenceretire = [fetchedresultscontroller objectatindexpath:indexpath]; // 2. have order of 0? nslog(@"=================== sentenceretire: %@", sentenceretire); if([[sentenceretire order] intvalue] == 0){ // yes: there sentence in story? story *storyretire = [sentenceretire belongsto]; nsset *sentencesinretiredsentenceset = [storyretire sentences]; if ([sentencesinretiredsentenceset count] > 1){ // yes: set sentence smallest order order of 0 // delete sentence + files nspredicate *predicatetitle = [nspredicate predicatewithformat:@"order>0"]; nsset *sentenceswithpotentialtobetitle = [sentencesinretiredsentenceset filteredsetusingpredicate:predicatetitle]; nsnumber *minorder = [sentenceswithpotentialtobetitle valueforkeypath:@"@min.order"]; nspredicate *predicateorder = [nspredicate predicatewithformat:@"order=%d",[minorder intvalue]]; nsset *sentencewithpotentialtobetitle = [sentenceswithpotentialtobetitle filteredsetusingpredicate:predicateorder]; //note sentence (singular) not sentences. sentence who's order needs updated. nslog(@"setencewithpotentialtobetitle (check order isn't null on crash): %@", sentencewithpotentialtobetitle); sentence *sentencetopromote = [sentencewithpotentialtobetitle anyobject]; //now know sentence promote can delete sentence & files. [managedobjectcontext deleteobject:[fetchedresultscontroller objectatindexpath:indexpath]]; nsstring *imagetrash = [documentsdirectorypath stringbyappendingpathcomponent:(nsstring*)[sentenceretire image]]; nsstring *thumbtrash = [documentsdirectorypath stringbyappendingpathcomponent:[sentenceretire thumb]]; nslog(@"about delete these files: %@, %@", imagetrash, thumbtrash); [filemanager removeitematpath:imagetrash error:null]; [filemanager removeitematpath:thumbtrash error:null]; //and promote new title. [sentencetopromote setorder:[[nsnumber alloc] initwithinteger:0]]; }else{ // no: we're deleting story // delete files! [managedobjectcontext deleteobject:storyretire]; nsstring *imagetrash = [documentsdirectorypath stringbyappendingpathcomponent:(nsstring*)[sentenceretire image]]; nsstring *thumbtrash = [documentsdirectorypath stringbyappendingpathcomponent:[sentenceretire thumb]]; //nslog(@"about delete these files: %@, %@", imagetrash, thumbtrash); [filemanager removeitematpath:imagetrash error:null]; [filemanager removeitematpath:thumbtrash error:null]; //pop back. if (ui_user_interface_idiom() == uiuserinterfaceidiompad) { nslog(@"last sentence in story - delete story , point, somewhere!"); //probably delete sentece clear table, delete story using storyretire } else{ [self.navigationcontroller popviewcontrolleranimated:yes]; } } }else{ // no: delete sentence object. [managedobjectcontext deleteobject:[fetchedresultscontroller objectatindexpath:indexpath]]; nsstring *imagetrash = [documentsdirectorypath stringbyappendingpathcomponent:(nsstring*)[sentenceretire image]]; nsstring *thumbtrash = [documentsdirectorypath stringbyappendingpathcomponent:[sentenceretire thumb]]; //nslog(@"about delete these files: %@, %@", imagetrash, thumbtrash); [filemanager removeitematpath:imagetrash error:null]; [filemanager removeitematpath:thumbtrash error:null]; } // save context. //nserror *error; //if (![managedobjectcontext save:&error]) { /* replace implementation code handle error appropriately. abort() causes application generate crash log , terminate. should not use function in shipping application, although may useful during development. if not possible recover error, display alert panel instructs user quit application pressing home button. */ // nslog(@"unresolved error %@, %@", error, [error userinfo]); // abort(); //} } } - (void)setediting:(bool)editing animated:(bool)animated { [super setediting:editing animated:animated]; if (!editing) { //save here nserror *error; bool issaved = [managedobjectcontext save:&error]; nslog(@"issaved? %@ ======================================", (issaved ? @"yes" :@"no ") ); } } // override support rearranging table view. - (void)tableview:(uitableview *)tableview moverowatindexpath:(nsindexpath *)fromindexpath toindexpath:(nsindexpath *)toindexpath { //this implementation here: http://www.cimgf.com/2010/06/05/re-ordering-nsfetchedresultscontroller/ nsmutablearray *things = [[fetchedresultscontroller fetchedobjects] mutablecopy]; // grab item we're moving. nsmanagedobject *thing = [fetchedresultscontroller objectatindexpath:fromindexpath]; // remove object we're moving array. [things removeobject:thing]; // re-insert @ destination. [things insertobject:thing atindex:[toindexpath row]]; // of objects in correct order. update each // object's displayorder field iterating through array. int = 0; (nsmanagedobject *mo in things) { [mo setvalue:[nsnumber numberwithint:i++] forkey:@"order"]; } nslog(@"things: %@", things); [things release], things = nil; //reordering = yes; //nslog(@"moverowatindexpath: reordering"); //nserror *error; //[managedobjectcontext save:&error]; } #pragma mark - #pragma mark table view delegate /** delegate methods of nsfetchedresultscontroller respond additions, removals , on. */ - (void)controllerwillchangecontent:(nsfetchedresultscontroller *)controller { // fetch controller start sending change notifications, prepare table view updates. nslog(@"controllerwillchangecontent: beginupdates"); [self.tableview beginupdates]; beginupdatescount++; nslog(@"====================beginupdates incremented"); nslog(@"beginupdatescount %d", beginupdatescount); nslog(@"endupdatescount %d", endupdatescount); } - (void)controller:(nsfetchedresultscontroller *)controller didchangeobject:(id)anobject atindexpath:(nsindexpath *)indexpath forchangetype:(nsfetchedresultschangetype)type newindexpath:(nsindexpath *)newindexpath { nslog(@"didchangeobject: %@", anobject); uitableview *tableview; tableview = self.tableview; switch(type) { case nsfetchedresultschangeinsert: nslog(@"resultschangeinsert:"); insert = yes; [tableview insertrowsatindexpaths:[nsarray arraywithobject:newindexpath] withrowanimation:uitableviewrowanimationfade]; break; case nsfetchedresultschangedelete: nslog(@"resultschangedelete:"); delete = yes; [tableview deleterowsatindexpaths:[nsarray arraywithobject:indexpath] withrowanimation:uitableviewrowanimationfade]; break; case nsfetchedresultschangemove: nslog(@"resultschangemove:"); move = yes; [tableview deleterowsatindexpaths:[nsarray arraywithobject:indexpath] withrowanimation:uitableviewrowanimationnone]; [tableview insertrowsatindexpaths:[nsarray arraywithobject:newindexpath] withrowanimation:uitableviewrowanimationnone]; break; default: nslog(@"switch problem - default"); } } - (void)controller:(nsfetchedresultscontroller *)controller didchangesection:(id <nsfetchedresultssectioninfo>)sectioninfo atindex:(nsuinteger)sectionindex forchangetype:(nsfetchedresultschangetype)type { nslog(@"didchangesection:"); switch(type) { case nsfetchedresultschangeinsert: [self.tableview insertsections:[nsindexset indexsetwithindex:sectionindex] withrowanimation:uitableviewrowanimationfade]; break; case nsfetchedresultschangedelete: [self.tableview deletesections:[nsindexset indexsetwithindex:sectionindex] withrowanimation:uitableviewrowanimationfade]; break; } } - (void)controllerdidchangecontent:(nsfetchedresultscontroller *)controller { nslog(@"didchangecontent"); // fetch controller has sent current change notifications, tell table view process updates. nslog(@"almost endupdates=============================================="); if(delete){ nslog(@"endupdates delete"); delete = no; } if(move){ nslog(@"endupdates move"); move = no; } if(insert){ nslog(@"endupdates insert"); insert = no; } [self.tableview endupdates]; endupdatescount++; nslog(@"====================endupdates incremented"); nslog(@"endupdatescount %d", endupdatescount); nslog(@"beginupdatescount %d", beginupdatescount); nslog(@"endupdates finished"); } #pragma mark - #pragma mark memory management - (void)dealloc { nslog(@"dealloc sentence"); //[fliteengine stoptalking]; //[fliteengine release]; addbuttonitem = nil; playbuttonitem = nil; if (ui_user_interface_idiom() == uiuserinterfaceidiompad) { //it doesn't seem allocated because it's not set on iphone. [addbuttonitem release]; [playbuttonitem release]; } [story release]; [fetchedresultscontroller release]; [super dealloc]; } @end
update 18/3 #3 read this question fetchedresultscontroller needing have delegate set nil before creating new one. put
detailviewcontroller.fetchedresultscontroller = nil; detailviewcontroller.fetchedresultscontroller.delegate = nil;
in svc master view on didselectrowatindexpath , problem has stopped. refreshtables created new fetchedresultscontroller every time row selected in master view, , think still interfering when new story object selected.
Comments
Post a Comment