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.
Related
I'm attempting to build a game scoring app that utilizes a custom table cell with player photos, names, buttons etc... There are add/subtract buttons directly in the custom cell of the tableview that are hitting my save method, and it's storing it back in Core Data for that specific user.
The problem is with the on-screen score not updating and reflecting the change. After the save action to Core Data is complete, I'm calling the [self.tableView reloadData];... nothing. However, if I restart the app, then the change in score (for any of the players I've clicked on), appears.
Maybe I'm making this harder than it needs to be, either that, or I'm just not grasping the real problem.
Thoughts / comments?
Thanks a load in advance.
:-)
Sorry if this is overkill, but here is the majority of my implementation file:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self resetViews];
}
- (void)viewDidLoad {
[super viewDidLoad];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
[context setUndoManager:nil];
_managedObjectContext = context;
self.tableView.delegate = self;
[self setNeedsStatusBarAppearanceUpdate];
}
-(void)resetViews {
NSLog(#"\n\n\nresetViews()");
[self setupFetchedResultsController];
[self.tableView reloadData];
[self.view setNeedsDisplay];
}
- (void)setupFetchedResultsController {
NSString *entityName = #"Players";
NSLog(#"Setting up a Fetched Results Controller for the Entity named %#", entityName);
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
request.sortDescriptors = [NSArray arrayWithObject:
[NSSortDescriptor
sortDescriptorWithKey:#"playerName"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
NSError *error;
NSArray *results = [_managedObjectContext executeFetchRequest:request error:&error];
_playerArray = [[NSMutableArray alloc]initWithArray:results];
NSLog(#"_playerArray count: %i", [_playerArray count]);
NSLog(#"\n");
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _playerArray.count;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"playerCell";
ScoringCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
// Configure the cell...
Players *player_info = [_playerArray objectAtIndex:indexPath.row];
NSSet *score = player_info.scores;
for (Scoring *perObj in score){
cell.lblPlayerScore.text = [perObj.score stringValue];
NSLog(#"\n\n\n score for %#: %#", player_info.playerName, perObj.score);
}
cell.lblPlayerName.text = player_info.playerName;
cell.lblPlayerNickName.text = player_info.playerNickName;
cell.btnIncreaseScore.tag = indexPath.row;
cell.btnDecreaseScore.tag = indexPath.row;
cell.imgPlayerPhoto.image = [UIImage imageNamed:#"tmp_playerImage"];
return cell;
}
- (IBAction)increaseScore:(id)sender {
NSLog(#"PageContentViewController: increaseScore()");
UIButton* btn=(UIButton*)sender;
int selectedPlayerInt = btn.tag;
//NSLog(#"Selected row is: %d",btn.tag);
Players *player_info = [_playerArray objectAtIndex:selectedPlayerInt];
[self updateRowScore:player_info:#"add"];
}
- (IBAction)decreaseScore:(id)sender {
NSLog(#"PageContentView: decreaseScore()");
UIButton* btn=(UIButton*)sender;
int selectedPlayerInt = btn.tag;
//NSLog(#"Selected row is: %d",btn.tag);
Players *player_info = [_playerArray objectAtIndex:selectedPlayerInt];
[self updateRowScore:player_info:#"subtract"];
}
-(void)updateRowScore: (Players *)player_info :(NSString *)modifier {
NSLog(#"\n\nupdateRowScore()");
NSLog(#"Update score (%#) for: %#\n", modifier, player_info.playerName);
NSArray *scoreDataArray;
if ([self playerScoreCount:player_info] == 0) {
// NEW score... we've never scored before.
Scoring *scoring_data = [NSEntityDescription
insertNewObjectForEntityForName:#"Scoring"
inManagedObjectContext:_managedObjectContext];
//Since this is the first score, always set it to 1
scoring_data.score = [NSNumber numberWithInt:1];
scoring_data.holeNumber = [NSNumber numberWithInt:_pageIndex];
scoring_data.scoredBy = player_info;
} else {
//Update existing player score..
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *BEntity = [NSEntityDescription entityForName:#"Scoring" inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:BEntity];
NSPredicate *predicate = [NSPredicate
predicateWithFormat:#"(scoredBy = %#)", [player_info objectID]];
[fetchRequest setPredicate:predicate];
NSArray *results = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
scoreDataArray = [[NSMutableArray alloc]initWithArray:results];
Scoring *score_update = [scoreDataArray objectAtIndex:0];
int currentScore = [score_update.score intValue];
NSLog(#"current score: %d", currentScore);
if ([modifier isEqual: #"add"]) {
currentScore++;
} else {
// Don't allow negative scores.
if (currentScore >= 1) {
currentScore--;
} else {
currentScore = 0;
}
}
NSLog(#"NEW score: %d", currentScore);
score_update.score = [NSNumber numberWithInt:currentScore];
}
// write to database
[self.managedObjectContext save:nil];
[self resetViews];
}
UPDATE:
Thanks for the tip bbarnhart... I had read through that post before and had used that for a basis from which I had started. Decided to take it a step further and refactor a chunk of code using more of the Ray Wenderlich example.
I've seen some improvements to what's being recorded, and reported back through the NSLog's... but the view just still is not changing.
The action is increasing the score, and then I'm resetting the cell using [self configureCell:cell atIndexPath:path]; In there... the method that is responsible for sending text to the display... the NSLog is showing 2014-12-04 22:40:40.199 appName[7153:150248] Score for Tim: 4 when the display still only shows 3.
I know this is some stupid rookie move... I'm just doing something dead wrong that I can't figure out. Here's a snippet of the amended code.
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Players"
inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"playerName" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:_managedObjectContext
sectionNameKeyPath:nil
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
NSError *error;
NSArray *results = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
_playerArray = [[NSMutableArray alloc]initWithArray:results];
NSLog(#"_playerArray count: %i", [_playerArray count]);
return _fetchedResultsController;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"playerCell";
ScoringCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[ScoringCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:cellIdentifier];
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(ScoringCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Players *player_info = [_fetchedResultsController objectAtIndexPath:indexPath];
NSSet *scoreSet = player_info.scores;
NSString *cell_score;
for (Scoring *scoreObj in scoreSet) {
cell_score = [scoreObj.score stringValue];
}
NSLog(#"Score for %#: %#", player_info.playerName, cell_score);
if (cell_score != nil) {
cell.lblPlayerScore.text = cell_score;
}
cell.lblPlayerName.text = player_info.playerName;
cell.lblPlayerNickName.text = player_info.playerNickName;
cell.btnIncreaseScore.tag = indexPath.row;
cell.btnDecreaseScore.tag = indexPath.row;
cell.imgPlayerPhoto.image = [UIImage imageNamed:#"demo_playerb"];
[self resetViews];
NSLog(#"\n");
}
- (IBAction)increaseScore:(id)sender {
NSLog(#"PageContentViewController: increaseScore()");
UIButton *senderButton = (UIButton *)sender;
int selectedPlayerInt = senderButton.tag;
NSIndexPath *path = [NSIndexPath indexPathForRow:senderButton.tag inSection:0];
Players *player_info = [_playerArray objectAtIndex:selectedPlayerInt];
[self updateRowScore:player_info:#"add":selectedPlayerInt:path];
}
-(void)updateRowScore:(Players *)player_info :(NSString *)modifier :(int)selectedPlayerInt :(NSIndexPath *)path {
NSArray *scoreDataArray;
if ([self playerScoreCount:player_info] == 0) {
// NEW score... we've never scored before.
Scoring *scoring_data = [NSEntityDescription
insertNewObjectForEntityForName:#"Scoring"
inManagedObjectContext:_managedObjectContext];
//Since this is the first score, always set it to 1
scoring_data.score = [NSNumber numberWithInt:1];
scoring_data.holeNumber = [NSNumber numberWithInt:_pageIndex];
scoring_data.scoredBy = player_info;
} else {
//Update existing player score..
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *BEntity = [NSEntityDescription entityForName:#"Scoring"
inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:BEntity];
NSPredicate *predicate = [NSPredicate
predicateWithFormat:#"(scoredBy = %#)", [player_info objectID]];
[fetchRequest setPredicate:predicate];
NSArray *results = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
scoreDataArray = [[NSMutableArray alloc]initWithArray:results];
Scoring *score_update = [scoreDataArray objectAtIndex:0];
int currentScore = [score_update.score intValue];
NSLog(#"current score: %d", currentScore);
if ([modifier isEqual: #"add"]) {
currentScore++;
} else {
// Don't allow negative scores.
if (currentScore >= 1) {
currentScore--;
} else {
currentScore = 0;
}
}
NSLog(#"NEW score: %d", currentScore);
score_update.score = [NSNumber numberWithInt:currentScore];
}
// write to database
[self.managedObjectContext save:nil];
static NSString *cellIdentifier = #"playerCell";
ScoringCell *cell = [_tableView dequeueReusableCellWithIdentifier:cellIdentifier];
[self configureCell:cell atIndexPath:path];
[self resetViews];
}
----------
UPDATE:
Been awhile since I've had a chance to revisit, and just noticed a new problem since enabling your tips. When scrolling down or up in the list and pulling beyond the normal boundaries, the tableview data seems to overwrite the display for the row either above or below the current line. Weird... Not sure if this animated Gif will show up in Stack. Here's an example:
The main reason your table view is not updating dynamically is NSFetchedResultsController uses a delegate for notification when changes occur. You'll need to set that delegate, self.fetchedResultsController.delegate = self and then add the delegate methods.
Here is a link to an example for managing a UITableView with a NSFetchedResultsController.
Update
Implement these NSFetchResultsController delegate methods to allow your table to be dynamically updated.
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath: (NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
Generally, these methods contain boilerplate code for updating your table which you will also find in the link above.
In my app I have a feature that allows the user to create their own table view of ingredients that they are allergic to. Here is my method for creating the array that populates it as well as the methods that are there to populate it:
- (void)viewDidLoad
{
[super viewDidLoad];
badIngredientsArray = [[NSMutableArray alloc] init];
rightButton = [[UIBarButtonItem alloc] initWithTitle:#"Add"
style:UIBarButtonItemStyleDone target:nil action:nil];
self.navigationItem.rightBarButtonItem = rightButton;
rightButton.target = self;
rightButton.action = #selector(addRow);
self.tableView.delegate = self;
self.tableView.dataSource = self;
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSManagedObjectContext *context = [self managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"AllergicIngredient" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *ingredientsArray = [context executeFetchRequest:fetchRequest error:&error];
badIngredientsArray = [NSMutableArray arrayWithArray:ingredientsArray];
}
-(void)addRow
{
UIAlertView *myAlertView = [[UIAlertView alloc] initWithTitle:#"Add a Bad Ingredient" message:#"Type the name of the ingredient" delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:#"Cancel", nil];
myAlertView.alertViewStyle = UIAlertViewStylePlainTextInput;
[myAlertView show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSManagedObjectContext *context = [self managedObjectContext];
AllergicIngredient *allergic = [NSEntityDescription insertNewObjectForEntityForName:#"AllergicIngredient" inManagedObjectContext:context];
NSString *enteredString = [[alertView textFieldAtIndex:0] text];
[allergic setValue:enteredString forKey:#"name"];
NSError *error;
if (![context save:&error])
{
NSLog(#"Couldnt find the save %#", error.localizedDescription);
}
else
{
NSLog(#"It saved properly");
}
[badIngredientsArray addObject:enteredString];
[self.tableView reloadData];
}
It seems as though when I remove the following code:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSManagedObjectContext *context = [self managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"AllergicIngredient" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *ingredientsArray = [context executeFetchRequest:fetchRequest error:&error];
badIngredientsArray = [NSMutableArray arrayWithArray:ingredientsArray];
The functionality works fine, but it loses the core data aspect where if you delete the app and relaunch it the table view is empty. So I can't seem to wrap my head around what is wrong with the above code. For all of you who want to know where the SIGABRT error is happening, it's at this line:
cell.textLabel.text = [badIngredientsArray objectAtIndex:indexPath.row];
UPDATE:
My table view methods:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [badIngredientsArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
if (cell == nil)
{
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
}
cell.textLabel.text = [badIngredientsArray objectAtIndex:indexPath.row];
return cell;
}
[badIngredientsArray objectAtIndex:indexPath.row] does not return a NSString but a NSManagedObject.
In tableView:cellForRowAtIndexPath:, try the following code in order to get your name attribute:
AllergicIngredient *object = (AllergicIngredient *)[badIngredientsArray objectAtIndex:indexPath.row];
cell.textLabel.text = object.name;
//or
//NSManagedObject *object = (NSManagedObject *)[badIngredientsArray objectAtIndex:indexPath.row];
//cell.textLabel.text = [object objectForKey:#"name"];
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.
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
}