I am trying to make it to where an array loads items into a table view from core data. Some of the values are duplicates though. So, within the array that I am using to fetch the data, I am trying to tell the array to remove any duplicates and then display it in the table view. But for some reason it is not removing the duplicates. Here is the code:
UPDATED
- (void)viewDidLoad
{
fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *weightEntity = [NSEntityDescription entityForName:#"Tracking" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:weightEntity];
result = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
NSMutableArray *cleaningArray= [NSMutableArray new];
NSSet *duplicatesRemover = [NSSet setWithArray:result];
[duplicatesRemover enumerateObjectsUsingBlock: ^(id obj, BOOL* stop)
{
if(![cleaningArray containsObject: obj])
{
[cleaningArray addObject: obj];
}
}];
cleanArray = [cleaningArray copy];
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
mainCell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (mainCell == nil) {
mainCell = [[dictionaryTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Entity *person = [cleanArray objectAtIndex:indexPath.row];
mainCell.nameLabel.text = person.date;
return mainCell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"%#", [cleanArray objectAtIndex:0]);
return cleanArray.count;
}
Thanks!
-containsObject compares strings (as in [string1 isEqual:string2]) so you could do this
NSArray* result = #[#"test",#"string",#"test",#"line"];
NSMutableArray* cleanArray = [[NSMutableArray alloc] init];
for (id object in result) {
if (![cleanArray containsObject:object])
[cleanArray addObject:object];
}
NSLog (#"cleanArray %#",cleanArray);
log:
cleanArray (
test,
string,
line
)
update
#dreamlax and I been chatting with #Zack. It seems the NSSet / isEqual issue was a red herring. The "result" array does not contain NSStrings, it contains fetch requests for Core Data, each of which are of course unique, even if the data returned isn't. This array is tightly coupled with the table view, which executes those fetch requests ... on request.
So what Zack needed to do was to decouple Core Data from his Table View, pre-fetch the strings he wants to compare for uniqueness, and feed that uniqued array to the Table View. NSSet works fine for obtaining a unique set of results.
fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *weightEntity =
[NSEntityDescription entityForName:#"Entity"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:weightEntity];
result = [self.managedObjectContext executeFetchRequest:fetchRequest
error:nil];
NSMutableSet *dateSet = [NSMutableSet alloc] init];
for (id object in result) {
Entity *person = object;
NSString* dateString = person.date;
[dateSet addObject:dateString];
}
self.dateArray = [dateSet allObjects];
Then in his tableView:
mainCell.nameLabel.text = [cleanArray objectAtIndex:indexPath.row];
With credits to dreamlax, It's a matter of overriding the isEqual: method, that's why NSSet doesn't remove your duplicate objects.
In your entity, override the isEqual method and return true if all the fields are equal, for example:
- (BOOL)isEqual:(id)anObject
{
return self.attribute_1 == anObject,attribute_1 && ... && self.attribute_N== anObject.attribute_N; // Primitive types comparison in this example
}
Related
I need some help indexing a UITableView, dividing the data in alphabetic sections. The data model is from a core data graph.
I have the index (A-Z), loaded ok, and the section headers are correct. The issue is that the data is not sorted correctly (i.e. alphabetic).
There is an entity attribute in the model that is an alphabetic index. I've looked at the sqlite file and that is ok.
Here are pieces of relevant code. Please help me understand what I'm missing or messing :)
- (void)viewDidLoad {
[super viewDidLoad];
sectionArray = [[NSArray alloc] init];
self.collation = [UILocalizedIndexedCollation currentCollation];
if (languageKey == 0) {
sectionArray = [NSArray arrayWithArray:[#"|α,ά|β|γ|δ|ε,έ|ζ|η,ή|θ|ι,ί|κ|λ|μ|ν|Ξ|ο,ό|π|ρ|σ|τ|υ,υ|φ|χ|ψ|ω,ώ|#"
componentsSeparatedByString:#"|"]];
} else {
sectionArray = [NSArray arrayWithArray:
[#"A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|#"
componentsSeparatedByString:#"|"]];
}
NSManagedObjectContext *context = [self managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"WordEntity" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
[request setPredicate:[self predicate]];
[request setIncludesSubentities:NO];
filteredWordsArray = [[NSMutableArray alloc] init];
self.searchResults = [NSMutableArray arrayWithCapacity:[[self.fetchedResultsController fetchedObjects] count]];
self.searchDisplayController.searchResultsTableView.rowHeight = wordTable.rowHeight;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.searchDisplayController.searchResultsTableView)
{
return [self.searchResults count];
}
else
{
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"WordCell";
UITableViewCell *cell = (UITableViewCell *)[self.wordTable dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
WordEntity *word = nil;
if (tableView == self.searchDisplayController.searchResultsTableView) {
word = [self.searchResults objectAtIndex:indexPath.row];
count = self.searchResults.count;
self.numberWordsLabel.text = [NSString stringWithFormat:#"%lu", (unsigned long)count];
} else {
word = [self.fetchedResultsController objectAtIndexPath:indexPath];
self.numberWordsLabel.text = [NSString stringWithFormat:#"%lu", (unsigned long)fullCount];
}
if (languageKey == 0) {
cell.textLabel.text = word.greekText;
cell.detailTextLabel.text = word.englishText;
} else {
cell.textLabel.text = word.englishText;
cell.detailTextLabel.text = word.greekText;
}
return cell;
}
/*
Section-related methods: Retrieve the section titles and section index titles from the collation.
*/
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
long languageKey = [defaults integerForKey:DEFAULT_KEY_LANGUAGE_NUMBER];
long count = 0;
if (languageKey == 0) {
count = 24;
} else {
count = 26;
}
return count;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [sectionArray objectAtIndex:section];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return sectionArray;
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return [collation sectionForSectionIndexTitleAtIndex:index];
}
- (NSFetchedResultsController *)fetchedResultsController {
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] init];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
long languageKey = [defaults integerForKey:DEFAULT_KEY_LANGUAGE_NUMBER ];
if (languageKey == 0) {
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"greekKey" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
// sectionTitleString = #"greekKey";
} else {
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"englishKey" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
// sectionTitleString = #"englishKey";
}
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"greekKey" cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
/*
-(NSArray *)partitionObjects:(NSArray *)array collationStringSelector:(SEL)selector
{
collation = [UILocalizedIndexedCollation currentCollation];
NSInteger sectionCount = [[collation sectionTitles] count]; //section count is from sectionTitles and not sectionIndexTitles
NSMutableArray *unsortedSections = [NSMutableArray arrayWithCapacity:sectionCount];
//create an array to hold the data for each section
for(int i = 0; i < sectionCount; i++)
{
[unsortedSections addObject:[NSMutableArray array]];
}
//put each object into a section
for (id object in array)
{
NSInteger index = [collation sectionForObject:object collationStringSelector:selector];
[[unsortedSections objectAtIndex:index] addObject:object];
}
sections = [NSMutableArray arrayWithCapacity:sectionCount];
//sort each section
for (NSMutableArray *section in unsortedSections)
{
[sections addObject:[collation sortedArrayFromArray:section collationStringSelector:selector]];
}
return sections;
}
*/
This is what I'm seeing:
Try adding a sort descriptor to your NSFetchRequest...
NSSortDescriptor *sortDescriptorSecondary = [[NSSortDescriptor alloc]
initWithKey:#"word" ascending:YES];
[request setSortDescriptors:#[sectionArray, sortDescriptorSecondary]];
Note that when you are using sections in your table view, you must always sort by section first and then other criteria.
UPDATE
In a little more detail...
Private properties to include:
#property (nonatomic, strong) NSArray *sectionArray;
Fetch request to include:
// Declare sort descriptors
NSArray *requestSortDescriptors = nil;
NSSortDescriptor *sortDescriptorPrimary = nil;
NSSortDescriptor *sortDescriptorSecondary = nil;
// Set sort descriptors...
// Primary sort descriptor is your section array - sort by sections first.
sortDescriptorPrimary = [NSSortDescriptor sortDescriptorWithKey:self.sectionArray ascending:YES];
// Secondary sort descriptor is your entity attribute `word` - sort by fetched data second.
sortDescriptorSecondary = [NSSortDescriptor sortDescriptorWithKey:#"word" ascending:YES];
// Set sort descriptor array
requestSortDescriptors = #[sortDescriptorPrimary, sortDescriptorSecondary];
// Apply sort descriptors to fetch request
[request setSortDescriptors:requestSortDescriptors];
Hope this assists, let me know if you require further explanation.
SECOND UPDATE
I neglected to mention how to propagate the section data.
Personally, for data that is to be displayed in a TVC with section headers, for each entity I define an entity attribute that I always for simplicity call sectionIdentifier.
Then as a part of my methods to persist data, I ensure "section" data is allocated to this attribute sectionIdentifier (e.g. in your example A, B, C, etc).
In your case however, and for this particular example, you have established a local variable called sectionArray in your viewDidLoad TVC lifecycle method.
Working with your code in mind, I suggest the following (per above)...
Private properties to include:
#property (nonatomic, strong) NSArray *sectionArray;
Also working with your code in mind, I suggest the following (new)...
Alter your TVC lifecycle method viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
self.sectionArray = nil;
self.collation = [UILocalizedIndexedCollation currentCollation];
if (languageKey == 0) {
self.sectionArray = [NSArray arrayWithArray:[#"|α,ά|β|γ|δ|ε,έ|ζ|η,ή|θ|ι,ί|κ|λ|μ|ν|Ξ|ο,ό|π|ρ|σ|τ|υ,υ|φ|χ|ψ|ω,ώ|#"
componentsSeparatedByString:#"|"]];
} else {
self.sectionArray = [NSArray arrayWithArray:[#"A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|#"
componentsSeparatedByString:#"|"]];
}
// I have commented out code following because I cannot see where you are using it.
// Does the compiler not throw up warnings for this fetch request?
//
// NSManagedObjectContext *context = [self managedObjectContext];
// NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"WordEntity" inManagedObjectContext:context];
// NSFetchRequest *request = [[NSFetchRequest alloc] init];
// [request setEntity:entityDescription];
// [request setPredicate:[self predicate]];
// [request setIncludesSubentities:NO];
filteredWordsArray = [[NSMutableArray alloc] init];
self.searchResults = [NSMutableArray arrayWithCapacity:[[self.fetchedResultsController fetchedObjects] count]];
self.searchDisplayController.searchResultsTableView.rowHeight = wordTable.rowHeight;
}
Also working with your code in mind, I suggest the following (new)...
Alter your NSFetchedResultsController getter method:
- (NSFetchedResultsController *)fetchedResultsController {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
long languageKey = [defaults integerForKey:DEFAULT_KEY_LANGUAGE_NUMBER ];
// Set up fetch request
NSFetchRequest *fetchRequest = [[NSFetchRequest fetchRequestWithEntityName:#"WordEntity"];
[fetchRequest setPredicate:[self predicate]];
[fetchRequest setIncludesSubentities:NO];
// Declare sort descriptor variables
NSArray *requestSortDescriptors = nil;
NSSortDescriptor *sortDescriptorPrimary = nil;
NSSortDescriptor *sortDescriptorSecondary = nil;
// Set sort descriptors...
// Primary sort descriptor is your section array - sort by sections first.
sortDescriptorPrimary = [NSSortDescriptor sortDescriptorWithKey:self.sectionArray
ascending:YES];
// Secondary sort descriptor is your entity attribute - sort by fetched data second.
if (languageKey == 0) {
sortDescriptorSecondary = [NSSortDescriptor sortDescriptorWithKey:#"greekKey"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
// sectionTitleString = #"greekKey";
} else {
sortDescriptorSecondary = [NSSortDescriptor sortDescriptorWithKey:#"englishKey"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
// sectionTitleString = #"englishKey";
}
// Set sort descriptor array - section first, then table view data
requestSortDescriptors = #[sortDescriptorPrimary, sortDescriptorSecondary];
// Apply sort descriptors to fetch request
[fetchRequest setSortDescriptors:requestSortDescriptors];
// Your sectionNameKeyPath in your FRC is self.sectionArray (this may be incorrect - try)
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:self.sectionArray
cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
I'm trying to populate my tableView with storaged data in CoreData. When tableView is trying to populate it's cells the name and also date of fields in cell are empty. Size of NSMutableArray created from NSArray like so:
-(void)copyArrayToTableMutableArray:(NSArray *)coreDataArray
{
if(self.fetchedRecordsArray != nil)
{
modelArray = [NSMutableArray arrayWithArray:coreDataArray];
NSLog(#"%d", [modelArray count]);
}
}
shows that there are for example 3 items. When program goes to populate section it creates cell but they are empty. This is code for populating:
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableItem";
CustomTableCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[CustomTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
if([modelArray count] > 0)
{
Kwejki *tmpModel = [modelArray objectAtIndex:indexPath.row];
//Here it is empty NULL
NSLog(#"%#",[tmpModel name]);
cell.titleOfCell.text = [tmpModel name];
cell.dateOfAddedCell.text = [[tmpModel date] stringValue];
}
return cell;
}
And I'm saving new item to the CoreData like this:
-(void)addNewPosition:(ScrollViewViewController *)ScrollController recentlyDownloadedItem:(KwejkModel *)modelTmp
{
NSLog(#"DODAJE NOWA POZYCJE");
NSLog(#"%#",[modelTmp description]);
NSLog(#"%d", [modelArray count]);
//[self.tableView reloadData];
Kwejki * newEntry = [NSEntityDescription insertNewObjectForEntityForName:#"Kwejki" inManagedObjectContext:self.managedObjectContext];
NSLog(#"%#", [modelTmp getNameOfItem]);
newEntry.name = [[NSString alloc] initWithString:[modelTmp getNameOfItem]];
newEntry.rating = [modelTmp getRateOfPosition];
newEntry.urlMain = [modelTmp getUrlAdress];
newEntry.contentUrl = [modelTmp getContentUrl];
newEntry.coverUrl = [modelTmp getCoverImage];
newEntry.date = [modelTmp getDateOfItem];
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
else{
NSLog(#"UDALO SIE!!!");
}
[modelArray insertObject:newEntry atIndex:0];
[self.tableView reloadData];
}
I've searched around but haven't founded why it is empty. Do you know why?
For Core Data populated table views you should be using a NSFetchedResultsController. Then you can retrieve your object reliably with
Kwejki *item = [self.fetchedResultsController objectAtIndexPath:indexPath];
The following is not recommended:
If you really want to stick to your ill-advised array solution, it would help to make sure you have a property or instance variable. Clearly, in cellForRowAtIndexPath your array has already been deallocated.
#property (nonatomic, strong) NSMutableArray *modelArray;
It doesn't look like you are using UIManagedDocuments so you may need to put a [self.managedObjectContext save] in add new position after you populate your newentry fields. I know you shouldn't have to do this that the data is there in memory, but if you are using different contexts in your insert and in your fetches, you wouldn't see the data until a save was done on it. It may not help, but give it a try.
I have a bug in here somewhere and I can not find it so I am hoping your keen eyes will!
I am using a FRC with a tableView. the FRC is section sorted by keyPath and then sorted by "displayOrder" - the usual.
The Details "displayOrder" in each section start at 1 so when I insert an item, in another method, it goes to index 0 of the section.
I want to loop through the affected section(s) and re-assign the "displayOrder" starting at 1.
During re-order, the code works for:
Re-ordering within the any section AS LONG AS the re-ordered cell moves up and not down.
Code does not work for... clicking on a cell but not moving it.. the code changes the order for some reason thus changing the order of the cells. - when I click a cell, it along with the other cells above it in the same section re-order.
I used to have this working and I don't know what happened.
Thanks for any help.
-Edited-
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
NSError *error = nil;
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
TheDetail *fromThing = [self.fetchedResultsController objectAtIndexPath:fromIndexPath];
TheDetail *toThing = [self.fetchedResultsController objectAtIndexPath:toIndexPath];
NSPredicate *catetgoryPredicate = [NSPredicate predicateWithFormat:#"relationshipToTheCategory.name == %#", fromThing.relationshipToTheCategory.name];
NSMutableArray *allThings = [[[self.fetchedResultsController fetchedObjects] filteredArrayUsingPredicate:catetgoryPredicate] mutableCopy];
NSPredicate *fromPredicate = [NSPredicate predicateWithFormat:#"relationshipToTheSection.name == %#", fromThing.relationshipToTheSection.name];
NSPredicate *toPredicate = [NSPredicate predicateWithFormat:#"relationshipToTheSection.name == %#", toThing.relationshipToTheSection.name];
[allThings removeObject:fromThing];
[allThings insertObject:fromThing atIndex:toIndexPath.row];
//if the sections are NOT the same, reorder by section otherwise reorder the one section
if (![fromThing.relationshipToTheSection.name isEqual:toThing.relationshipToTheSection.name]) {
//Change the from index section's relationship and save, then grab all objects in sections and re-order
[fromThing setRelationshipToTheSection:toThing.relationshipToTheSection];
if ([context save:&error]) {
NSLog(#"The setting section save was successful!");
} else {
NSLog(#"The setting section save was not successful: %#", [error localizedDescription]);
}
NSMutableArray *fromThings = [[allThings filteredArrayUsingPredicate:fromPredicate]mutableCopy];
NSInteger i = 1;
for (TheDetail *fromD in fromThings) {
[fromD setValue:[NSNumber numberWithInteger:i] forKey:#"displayOrder"];
i++;
}
//reset displayOrder Count, the re-order the other section
i = 1;
NSMutableArray *toThings = [[allThings filteredArrayUsingPredicate:toPredicate]mutableCopy];
for (TheDetail *toD in toThings) {
[toD setValue:[NSNumber numberWithInteger:i] forKey:#"displayOrder"];
i++;
}
} else {
NSMutableArray *fromThings = [[allThings filteredArrayUsingPredicate:fromPredicate]mutableCopy];
NSInteger i = 1;
for (TheDetail *fromD in fromThings) {
[fromD setValue:[NSNumber numberWithInteger:i] forKey:#"displayOrder"];
i++;
}
}
if ([context save:&error]) {
NSLog(#"The save was successful!");
} else {
NSLog(#"The save was not successful: %#", [error localizedDescription]);
}
FRC
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
NSManagedObjectContext *context = [[self appDelegate]managedObjectContext];
//Construct the fetchResquest
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *detail = [NSEntityDescription entityForName:#"TheDetail" inManagedObjectContext:context];
[fetchRequest setEntity:detail];
//Add predicate
NSString *category = #"1";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"relationshipToTheCategory == %#", category];
[fetchRequest setPredicate:predicate];
//Add sort descriptor
NSSortDescriptor *sortDescriptor2 = [NSSortDescriptor sortDescriptorWithKey:#"relationshipToTheSection.displayOrder" ascending:YES];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"displayOrder" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor2, sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
//Set fetchedResultsController
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:#"relationshipToTheSection.name" cacheName:#"Root"];
NSError *error = nil;
self.fetchedResultsController = theFetchedResultsController;
self.fetchedResultsController.delegate = self;
[self.fetchedResultsController performFetch:&error];
return _fetchedResultsController;
New Error
Section *toSection = [[self fetchedResultsController] sections][[toIndexPath section]];
NSString *toSectionName = [[[toSection objects] lastObject] name];
Here I get the error in the IB "No visible #interface for "DSection" declares the selector 'objects'.
Don't remove yourself as the delegate for the NSFetchedResultsController. That is against the intended design of that class. If that is "helping" then it is masking a real problem.
Don't call -performFetch; from this method. The NSFetchedResultsController will detect the changes and tell your delegate about them.
Don't call -reloadData from this method. Let the delegate methods of NSFetchedResultsController do the reordering.
Always, always, always capture the error on a core data save. Even though you really don't need to save here (this is a bad time to block the UI with a save), you should ALWAYS capture the error and then watch for the result otherwise errors are hidden.
It is not clear what the -save: is doing. You haven't changed anything by the point of that save.
So that is a lot of work you are doing that you don't need to do. You are fighting the framework and making things harder.
Your reordering logic is more complicated than it needs to be, I think. It would help to see the NSFetchedResultsController initialization as well. But I am guessing you have sections based on name and then order by displayOrder. If that is the case this code can be a lot cleaner which would then make the issue more apparent.
My question to you is, are you checking this with breakpoints? Is this code firing when a row doesn't get actually moved? Should you check to see if your toIndexPath and fromIndexPath are equal?
Update
You do not need to save your context here. This is a UI method, saving causes delays which will make the UI slow to respond. Save later.
You do not need to run a NSFetchRequest here. That also hits disk and causes delays in the UI. Every piece of information that you need is already in memory inside of your NSFetchedResultsController. Use the existing object relationships to retrieve the data you are needing to make your decisions.
Calling entities The* is against Objective-C naming conventions. Words like "the", "is", "are" do not belong in entity or class names.
Consider this version of your code:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
NSManagedObjectContext *context = [[self fetchedResultsController] managedObjectContext];
TheDetail *fromThing = [[self fetchedResultsController] objectAtIndexPath:fromIndexPath];
Section *toSection = [[self fetchedResultsController] sections][[toIndexPath section]];
NSString *toSectionName = [[[toSection objects] lastObject] name];
NSString *fromSectionName = [[fromThing relationshipToTheSection] name];
if ([toSectionName isEqualToString:fromSectionName]) {
//Same section, easy reorder
//Move the object
NSMutableArray *sectionObjects = [[[[self fetchedResultsController] sections][[fromIndexPath section]] objects] mutableCopy];
[sectionObjects removeObject:fromThing];
[sectionObjects insertObject:fromThing atIndex:[toIndexPath row]];
//Reorder
NSInteger index = 1;
for (TheDetail *thing in sectionObjects) {
[thing setValue:#(index) forKey:#"displayOrder"];
}
return; //Early return to keep code on the left margin
}
NSMutableArray *sectionObjects = [[[[self fetchedResultsController] sections][[fromIndexPath section]] objects] mutableCopy];
[sectionObjects removeObject:fromThing];
//Reorder
NSInteger index = 1;
for (TheDetail *thing in sectionObjects) {
[thing setValue:#(index) forKey:#"displayOrder"];
}
if ([[toSection numberOfObjects] count] == 0) {
[fromThing setValue:#(0) forKey:#"displayOrder"];
//How do you determine the name?
return;
}
sectionObjects = [[toSection objects] mutableCopy];
[sectionObjects insertObject:fromThing atIndex:[toIndexPath row]];
//Reorder
NSInteger index = 1;
for (TheDetail *thing in sectionObjects) {
[thing setValue:#(index) forKey:#"displayOrder"];
}
}
There is no fetching and no saving. We are working with only what is in memory already so it is VERY fast. This should be C&P-able except for one of the comments I left in.
Before adding section headers to one of my tables in my app, I was able to delete rows using the commitEditingStyle function without any issues. I decided to implement section headers to make it easier for the user to view items added to the table by date. This functionality works fine. I was having an issue with deleting rows after implementing the section headers but thanks to help from the good folks on stackoverflow the problem was partially resolved. After some testing I've realized that if the rows are in the same section and I try to delete more than one row in sequence beginning with the top row in the section, the top row deletes fine but trying to delete the second row causes the app to crash. If I delete all rows in sequence other than the first row and then delete the first row last, it works fine. Xcode doesn't indicate why it crashes in the debug log.
Here is the code for the cellForRowAtIndexPath function:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
AgendaCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[AgendaCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
NSString *strDate = [dateArray objectAtIndex:indexPath.section];
NSMutableArray *dateSection = [tempDict objectForKey:strDate];
NSManagedObject *object = [dateSection objectAtIndex:indexPath.row];
cell.sessionNameLabel.text = [object valueForKey:#"sessionname"];
cell.sessionNameLabel.textColor = [UIColor blueColor];
cell.sessionDateLabel.text = [object valueForKey:#"sessiondate"];
cell.sessionDateLabel.textColor = [UIColor brownColor];
cell.sessionTimeLabel.text = [object valueForKey:#"sessiontime"];
cell.sessionTimeLabel.textColor = [UIColor brownColor];
return cell;
}
Here is the code for my table refresh function:
- (void) refreshTable
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Sessnotes" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"agenda == 'Yes'"]];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"sessiondate" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
[self.refreshControl endRefreshing];
self.objects = results;
if (results.count == 0) {
NSString *message = #"You have not added any sessions to your planner.";
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"Notification"
message:message
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil,nil];
[alertView show];
}
else if (results.count > 0){
tempDict = nil;
tempDict = [[NSMutableDictionary alloc] init];
NSString *strPrevDate= [[results objectAtIndex:0] valueForKey:#"sessiondate"];
NSLog(#"strPrevDate value is: %#", strPrevDate);
NSString *strCurrDate = nil;
NSMutableArray *tempArray = [[NSMutableArray alloc] init];
//Add the Similar Date data in An Array then add this array to Dictionary
//With date name as a Key. It helps to easily create section in table.
for(int i=0; i< [results count]; i++)
{
strCurrDate = [[results objectAtIndex:i] valueForKey:#"sessiondate"];
if ([strCurrDate isEqualToString:strPrevDate])
{
[tempArray addObject:[results objectAtIndex:i]];
}
else
{
[tempDict setValue:[tempArray copy] forKey:strPrevDate];
strPrevDate = strCurrDate;
[tempArray removeAllObjects];
[tempArray addObject:[results objectAtIndex:i]];
}
}
//Set the last date array in dictionary
[tempDict setValue:[tempArray copy] forKey:strPrevDate];
NSArray *tArray = [tempDict allKeys];
//Sort the array in ascending order
dateArray = [tArray sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
}
[self.tableView reloadData];
}
Here is the code for the commitEditingStyle function:
- (void)tableView:(UITableView *)tableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
//add code here for when you hit delete
NSManagedObject *object = [self.objects objectAtIndex:indexPath.row];
NSManagedObjectContext *context = [self managedObjectContext];
[context deleteObject:[context objectWithID:[object objectID]]];
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
NSMutableArray *array = [self.objects mutableCopy];
[array removeObjectAtIndex:indexPath.row];
self.objects = array;
[tableView reloadData];
}
}
A couple things up front. I wouldn't make fetch requests everytime you want to reload the tableview. You should look at NSFetchedResultsController. It will automatically bind data to your tableview and do the refreshes for you based on updates coming from either the same NSManagedObjectContexts or messages about updates from other contexts and batch them for you as well.
To answer your original question. I would try to remove the object from the array first and then delete the NSManagedObject and then you can use some tableview trickery:
NSManagedObject *managedObject = self.array[indexPath.row];
[self.array removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation: UITableViewRowAnimationAutomatic];
[context deleteObject:managedObject];
I could be wrong but it's possible you're failing to fullfil a fault. Hope that helps.
I'm trying to display a list of objects from Core Data into a UITableViewController. Here is the viewWillAppear of this UITableViewController:
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
self.landmarks = [[TSDataManager sharedInstance] fetchArrayFromDBWithEntity:#"Landmark" forKey:nil withPredicate:nil];
[self.tableView reloadData];
}
And here is the fetchArrayFromDBWithEntity:forKey:withPredicate: implementation:
- (NSMutableArray*)fetchArrayFromDBWithEntity:(NSString*)entityName forKey:(NSString*)keyName withPredicate:(NSPredicate*)predicate {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
if(keyName != nil){
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:keyName ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
}
if (predicate != nil){
[request setPredicate:predicate];
}
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[self.managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
NSLog(#"%#", error);
}
return mutableFetchResults;
}
When the table first appears, everything is displayed correctly, I have 4 objects coming back from the DB with titles that are correctly displayed in each cell.
But whenever I go to another view and come back, the table has four objects alright, but the value of their "title" property is nil!
Here is the code of my Landmark class:
#interface Landmark : NSManagedObject
#property (nonatomic, strong) NSString * title;
#end
#implementation Landmark
#dynamic title;
#end
And here is how my table cells are constructed, in case it comes from there:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"LandmarkCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Landmark *landmark = (Landmark *) self.landmarks[(NSUInteger) indexPath.row];
cell.textLabel.text = landmark.title;
return cell;
}
Obviously I'm doing something wrong here, but I seriously can't figure out what.
It turned out that the problem came from my implementation of Core Data as a singleton. I replaced it with NLCoreData (https://github.com/jksk/NLCoreData) and everything is working fine now.