How to add new cell at the top of the table view? - objective-c

I have a Master and Detail TableViewControllers in my app. If users clicks on + sign on MasterTableViewController then it takes them to DetailTableViewController where they can type/edit "title" and "text". Once they click on back button (<-) it takes them back to MasterTableViewController with a new cell at the bottom of TableView. What changes do I need to do in order to appear newly added cell at the top of TableView ?
Here is the viewWillDisappear method which gets called when they click on back (<-) button.
-(void)viewWillDisappear:(BOOL)animated {
NSManagedObjectContext *context = [self managedObjectContext];
if (self.note) {
// Update existing Notes
[self.note setValue:self.titleField.text forKey:#"title"];
[self.note setValue:self.textView.text forKey:#"text"];
} else {
// Create a new Notes
NSManagedObject *newNote = [NSEntityDescription insertNewObjectForEntityForName:#"Note" inManagedObjectContext:context];
[newNote setValue:self.titleField.text forKey:#"title"];
[newNote setValue:self.textView.text forKey:#"text"];
}
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
//view Did Appear
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// fetch the Note from persistent data store
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Note"];
self.notes = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
// [self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:[self.notes count] inSection:0] animated:YES scrollPosition:UITableViewScrollPositionTop];
[self.tableView reloadData];
}

It seems that you are using CoreData to store the newly created objects. On the Master TableViewController, in viewDidAppear, you will have to write the code to fetch all the CoreData objects into an array and then reload the table data (by calling [tableView reloadData]). This will reload the table with latest data.
EDIT: Showing latest element on top
Add a new attribute to your CoreData Entity, something like modifiedOn, of type NSDate.
Set this attribute as [newNote setValue:[NSDate date] forKey:#"modifiedOn"];
Then you can use this attribute as a sortDescriptor in the fetchRequest to sort the objects based on modifiedOn-date.

Related

Core Data TableView - Multiple Selection During Edit Mode

I have two TableViews using Core Data. I have an ItemTableview with multiple rows of Item listed by the user, and it allows multiple selection during edit mode. During edit mode, it allows user to delete selected items, or delete all of the items at once. I want the items that's been deleted to be added to a TrashTableView.
Here's what I have so far:
- (IBAction)deleteAction:(id)sender
{
NSManagedObjectContext *context = [self managedObjectContext];
NSArray *selectedRows = [self.tableView indexPathsForSelectedRows];
BOOL noItemsAreSelected = selectedRows.count == 0;
BOOL deleteSpecificRows = selectedRows.count > 0;
if (noItemsAreSelected) {
// Delete all objects from the Core Data.
NSFetchRequest *allItems = [[NSFetchRequest alloc] init];
[allItems setEntity:[NSEntityDescription entityForName:#"Item" inManagedObjectContext:context]];
[allItems setIncludesPropertyValues:NO];
NSError *error = nil;
NSArray *items = [context executeFetchRequest:allItems error:&error];
for (NSManagedObject *object in items) {
[context deleteObject:object];
}
// Add to Trash
for (NSManagedObject *trashObject in items) {
Item *selectedItems = trashObject; <-- #warning -Incompatible pointer types initializing "Items" with an expression of type "NSManagedObject"-
Trash *newTrash = [NSEntityDescription insertNewObjectForEntityForName:#"Trash" inManagedObjectContext:context];
newTrash.trashname = selectedItems.itemname;
newTrash.created = [NSDate date];
// Save the context
NSError *saveError = nil;
if (![context save:&saveError]) {
NSLog(#"Save Failed! %# %#", saveError, [saveError localizedDescription]);
}
}
// Delete from the Array
[_item removeAllObjects];
// Tell the tableView that we deleted the objects.
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView setEditing:NO animated:YES];
}
else if (deleteSpecificRows) {
NSMutableIndexSet *indicesOfItemsToDelete = [NSMutableIndexSet new];
for (NSIndexPath *selectionIndex in selectedRows)
{
[indicesOfItemsToDelete addIndex:selectionIndex.row];
}
// Delete from the Array
[_item removeObjectsAtIndexes:indicesOfItemsToDelete];
// Tell the tableView that we deleted the objects
[self.tableView deleteRowsAtIndexPaths:selectedRows withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView setEditing:NO animated:YES];
}
}
When if (noItemsAreSelected) is called, all of the items are deleted and they all get added to the TrashTableView, but only a first row from the ItemTableView gives the string. So in the TrashTableView, first row has a text but the rest of the rows are just blank cells without any text.
In the debugger, blank cells have NSString trashname = nil; but NSDate created = "2015-01-22 03:41:30 +0000"; has a date.
For else if (deleteSpecificRows) I have no idea how to do it in Core Data....
I've spent quiet a lot of time trying to figure this out, so any help would be greatly appreciated. Thanks!
You are doing things in the wrong order: you currently delete all the items from the context, then iterate through the items, creating the corresponding trash items and saving the context each time. This works OK for the first item. But after the first item, the context has already been saved, so the delete operation (for ALL the items) will have happened, which nils out all their properties. Hence your null values.
I would restructure it as follows:
if (noItemsAreSelected) {
NSFetchRequest *allItems = [[NSFetchRequest alloc] init];
[allItems setEntity:[NSEntityDescription entityForName:#"Item" inManagedObjectContext:context]];
[allItems setIncludesPropertyValues:NO];
NSError *error = nil;
NSArray *items = [context executeFetchRequest:allItems error:&error];
for (Item *trashObject in items) {
// Add to Trash
Trash *newTrash = [NSEntityDescription insertNewObjectForEntityForName:#"Trash" inManagedObjectContext:context];
newTrash.trashname = trashObject.itemname;
newTrash.created = [NSDate date];
// Delete
[context deleteObject:trashObject];
}
// Save the context
NSError *saveError = nil;
if (![context save:&saveError]) {
NSLog(#"Save Failed! %# %#", saveError, [saveError localizedDescription]);
}
Note that changing the cast in the for(Item *trashObject ...) should avoid the compiler warning.
EDIT
For the deleteSpecificRows case, you can use similar code, but using your _item (I assume that is a mutable array which is the datasource for your tableView):
else if (deleteSpecificRows) {
NSMutableIndexSet *indicesOfItemsToDelete = [NSMutableIndexSet new];
for (NSIndexPath *selectionIndex in selectedRows)
{
// First, get the trash object...
Item *trashObject = [_item objectAtIndex:selectionIndex.row];
// Add to Trash
Trash *newTrash = [NSEntityDescription insertNewObjectForEntityForName:#"Trash" inManagedObjectContext:context];
newTrash.trashname = trashObject.itemname;
newTrash.created = [NSDate date];
// and delete the object from the context
[context deleteObject:trashObject];
// and update the list of items to delete
[indicesOfItemsToDelete addIndex:selectionIndex.row];
}
// Delete from the Array
[_item removeObjectsAtIndexes:indicesOfItemsToDelete];
// Tell the tableView that we deleted the objects
[self.tableView deleteRowsAtIndexPaths:selectedRows withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView setEditing:NO animated:YES];
}
Note that this is untested, so might need tidying up....
Longer term, you might want to consider a) using a NSFetchedResultsController to act as your datasource for your tableView, and b) rather than creating separate entities for the trash, add flag to the existing entities (inTrash?) and just changing that to true. Your tableView would then have to show only items with inTrash = false.

CoreData and UITableView: display values in cells

I'm working with Core Data and web service, I want to add my data to my table,
but I don't know how should I call them, would you please help me, since when I used this way it's not working.
Here is my method for update database in my HTTP class
- (void)updateLocalCardsDataBase:(NSArray*) cardsArray
{
//check if current user has cards in local database
NSManagedObjectContext* managedObjectContext = [(AppDelegate*) [[UIApplication sharedApplication] delegate] managedObjectContext];
for(NSDictionary *cardDic in cardsArray)
{
Card *card = [NSEntityDescription insertNewObjectForEntityForName:#"Card" inManagedObjectContext:managedObjectContext];
card.remote_id = [NSNumber numberWithInt:[[cardDic objectForKey:#"id"] intValue]];
card.stampNumber = [NSNumber numberWithInt:[[cardDic objectForKey:#"stampNumber"] intValue]];
card.createdAt = [NSDate dateWithTimeIntervalSince1970:[[cardDic objectForKey:#"createdAt"] intValue]];
[managedObjectContext lock];
NSError *error;
if (![managedObjectContext save:&error])
{
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
NSLog(#"Failed to save to data store: %#", [error localizedDescription]);
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(#" DetailedError: %#", [detailedError userInfo]);
}
}
else {
NSLog(#" %#", [error userInfo]);
}
}
[managedObjectContext unlock];
}
Here is my table:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
// NSManagedObjectContext* managedObjectContext = [(AppDelegate*) [[UIApplication sharedApplication] delegate] managedObjectContext];
static NSString *CellIdentifier = #"CardsCell";
CardCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil){
NSArray *objects = [[NSBundle mainBundle] loadNibNamed:#"CardCell" owner:nil options:nil];
for (id currentObject in objects)
{
if([currentObject isKindOfClass:[UITableViewCell class]])
{
cell = (CardCell *) currentObject;
break;
}
}
NSDictionary *f = [_cards objectAtIndex:indexPath.row];
cell.stampId.text = [f objectForKey:#"stampNumber"];
NSLog(#"%#fdssfdfddavds",[f objectForKey:#"stampNumber"]);
cell.createdAt.text = [f objectForKey:#"createdAt"];
cell.CardId.text = [f objectForKey:#"id"];
return cell;
}
Edit:
My problem is how I can show data in a UITableView
Before call [tableView reloadData], you need to get a data source first. You will get back an array of your data models, not an NSDictionary. You can place the my example method (or a variation that suits you best) where ever best suits your needs, but this one will not filter or sort the models, it will only get all of them. Also, I will place the method in your view controller that stores the table view:
-(NSArray*)getMycards {
NSManagedObjectContext *context = [(AppDelegate*) [[UIApplication sharedApplication] delegate] managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Card" inManagedObjectContext:context];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSError *error;
[request setEntity:entityDescription];
NSArray *cards = [context executeFetchRequest:request error:&error];
// now check if there is an error and handle it appropriatelty
// I usually return 'nil' but you don't have if you don't want
if ( error != nil ) {
// investigate error
}
return cards;
}
I recommend creating a property #property NSArray *cards in the view controller where you place your table, it will be easier to manage. One assumption I have made (since I have no other information about your view controller, a property named 'tableView' is declared in your view controller's header file (#property UITableView *tableView;), adjust the naming as needed.
With the above method, when you want to populate your array before loading the table's data:
// you put this block of code anywhere in the view controller that also has your table view
// likely in 'viewDidLoad' or 'viewDidAppear'
// and/or anywhere else where it makes sense to reload the table
self.cards = [self getMyCards];
if ( self.cards.count > 0 )
[self.tableview reloadData];
else {
// maybe display an error
}
Now, your cellForRowAtIndexPath should look like
-(UITableViewCell*tableView:tableView cellForRowAtIndexPath {
UITbaleViewCell *cell = ...;
// creating the type of cell seems fine to me
.
.
.
// keep in mind I don't know the exact make up of your card model
// I don't know what the data types are, so you will have to adjust as necessary
Card *card = self.cards[indexPath.row];
cell.stampId.text = [[NSString alloc] initWithFormat:#"%#",card.stamp];
cell.createdAt.text = [[NSString alloc] initWithFormat:#"%#",card.createdAt];
// you might want format the date property better, this might end being a lot more than what you want
cell.CardId.text = [[NSString alloc] initWithFormat:#"%#",card.id];
return cell;
}
Core Data is extremely powerful, I highly recommend the Core Data overview, followed by the Core Data Programming Guide.

Core Data Objective C update content of Entity

I am first time asking question here, sorry, but I can not find similar one.
So, I need update data in Entity "City" attribute - #"name".
for Example in my Core Data I already have #"New York", #"Boston".
And by parsing XML I have NSMutableArray *Cities = (#"New York", #"Boston", #"Los Angeles", #"Washington");
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
NSString *attributeString = #"name";
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
//save to the TableView
cell.textLabel.text = [[object valueForKey:attributeString] description];
if ((indexPath.row + 1) == numberOfSectionsInTableView && (self.isParsingDone))
[self.insertNewObjectToCities:nil];
//When coredata updating - tableView is also updating automatically
//Here is just adding new data, but I do not know how to update
- (void)insertNewObjectToCities_translation:(id)sender
{
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
NSString *attributeString = #"name";
if (![[self.parseCities.Cities objectAtIndex:i] isEqualToString:[newManagedObject valueForKey:attributeString]])
{
[newManagedObject setValue:[self.parseCities.Cities objectAtIndex:i] forKey:attributeString];
NSLog(#"OBBB %#", [self.parseCities.Cities objectAtIndex:i]);
NSLog(#"dessss %#", [[newManagedObject valueForKey:attributeString] description]);
i++;
if (i==[self.parseCities.Cities count])
{
i = 0;
return;
}
else
{
NSLog(#"valueForKey %#", [newManagedObject valueForKey:attributeString]);
[self insertNewObjectToCities_translation:nil];
}
}
else
{
NSLog(#"else");
return;
}
// Save the context.
NSError *error = nil;
if (![context save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
To update a managed object, you first need to fetch it, make any changes to the fields in the fetched NSManagedObject, and then save the context you used to fetch the object. If you call insertNewObjectForEntityForName again, it will insert a new managed object every time, even if it already exists in Core Data.
It's quite slow to fetch a single object every time you need to check and see if a new one needs to be added. You might want to cache the objects you currently have loaded (or their unique identifying field) into an NSArray or NSSet so you can check that for membership, instead.

Core Data Saving Attributes Of Entities

This question is about Core Data.
I created a Entity called TV with three attributes called name, price and size. I also created a subclass of NSMutableObject with TV.h and TV.m files.
I imported the TV.h to my DetailViewController.h which handles my sliders und UIElements I want to take the values of.
So I did a fetch request, and everything works fine, BUT:
Everytime I update the UISlider (valueDidChange:), Xcode creates a COPY of my entity and adds it to my TV-Object.
All I want Xcode is just to edit and save to the current entity, not to edit and save in a new entity.
Help is very appreciated!
Thank you in advance.
My Code:
DetailViewController.m
- (IBAction)collectSliderValue:(UISlider *)sender {
if (__managedObjectContext == nil) {
NSLog(#"Problem ...");
__managedObjectContext = [(MasterViewController *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSLog(#"... solved!");
}
if (sender == sizeSlider) {
NSError *error = nil;
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TV" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
TV * currentTV = [[TV alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
currentTV.size = [[NSNumber alloc] initWithInt:(sender.value + 0.5f)];
currentTV.name = #"New TV!";
NSError *error11;
[__managedObjectContext save:&error11];
for (NSManagedObject *info in fetchedObjects)
{
NSLog(#"Name = %#", [info valueForKey:#"name"]);
NSLog(#"Size = %#", [info valueForKey:#"size"]);
NSLog(#"Price = %#", [info valueForKey:#"price"]);
}
[fetchRequest release];
}
//Editing begins ...
TV * currentTV = [[TV alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
Editing doesn't begin, you are creating a new object right there. Your view controller needs an instance variable to hold the current TV entity that you are modifying.
From the template project you have created, the variable detailItem contains the managed object that you are currently editing. You should specifically set this as a TV object, and refer to this instead of currentTV in your detailViewController code. You must remove all of the fetch request and managed object context code - this is not relevant in your detail view controller, it should be managed by the master view controller.
So, in DetailViewController.h:
#property (strong, nonatomic) id detailItem;
becomes
#property (strong, nonatomic) TV detailItem;
And in your collectSliderValue method, it should look something much more simple like this:
- (IBAction)collectSliderValue:(UISlider *)sender
{
if (sender == sizeSlider)
self.detailItem.size = [NSNumber numberWithFloat:sender.value];
}
The saving of the managed object context shouldn't occur until back in your detail view controller, this is taken care of in your application delegate.
In your master detail controller .m file you may also need to import the TV.h file so that it knows that TV is a NSManagedObject subclass. Also, cast to TV when you are setting the detail item:
self.detailViewController.detailItem = (TV*)selectedObject;

Updating a managedObject in Core Data

WHAT I HAVE SO FAR:
In one splitview, I have a a tableview as its master, and a UIView as the detail. The tableview has 2 columns: "Days" and then "Sessions". I get the data from the Core Data, the entities called "Sessions". When I click on a "Session" tableviewcell, the detailview gets updated.
In the detailview, I added an "Add" button in the navigation bar. When you click on this, I add a new entity called "NewSession" to the core data.
if ([_sessionData.added isEqualToNumber:[NSNumber numberWithBool:NO]]) {
[_sessionData setValue:[NSNumber numberWithBool:YES] forKey:#"added"];
SessionData *session = (SessionData*) [NSEntityDescription insertNewObjectForEntityForName:#"NewSessions" inManagedObjectContext:[DataSingleton sharedMySingleton].managedObjectContext];
session.startDate = _sessionData.startDate;
session.endDate = _sessionData.endDate;
session.sessionLocation = nil;
session.sessionTitle = _sessionData.sessionTitle;
session.sessionDescription = _sessionData.sessionDescription;
[session setValue: [NSNumber numberWithBool:YES] forKey:#"added"];
_addButton = [[[UIBarButtonItem alloc] initWithTitle:#"Remove" style:UIBarButtonSystemItemAdd target:self action:nil] autorelease];
NSError *error = nil;
if (![[DataSingleton sharedMySingleton].managedObjectContext save:&error]) {
DebugLog(#"Whoops, couldn't save:%#", [error localizedDescription]);
}
}
else {
NSLog(#"SESSION ALREADY ADDED");
}
ANOTHER splitview's tableview fetches the "NewSession" entity, and gets all the data and displays it.
THE PROBLEM:
Whenever I exit the application and relaunch it, the sessions in the other splitview are still there, BUT I can add the SAME session again.
In the "add" code, I have the following:
[_sessionData setValue:[NSNumber numberWithBool:YES] forKey:#"added"];
Now, my sessionData is an NSManagedObject; and I thought that just setting the values will update them in the core data.
Can anyone help?
I had similar problem, but similar doesn't mean the same. I don't know if it works for you but you can try it. This method was described to me by #macbirdie and it works for me.
First of all, import your AppDelegate header file:
#import "YourAppDelegate.h"
Then, update your code:
if ([_sessionData.added isEqualToNumber:[NSNumber numberWithBool:NO]]) {
[_sessionData setValue:[NSNumber numberWithBool:YES] forKey:#"added"];
NSManagedObjectContext *moc = [[DataSingleton sharedMySingleton] managedObjectContext];
SessionData *session = (SessionData*) [NSEntityDescription insertNewObjectForEntityForName:#"NewSessions" inManagedObjectContext:moc];
session.startDate = _sessionData.startDate;
session.endDate = _sessionData.endDate;
session.sessionLocation = nil;
session.sessionTitle = _sessionData.sessionTitle;
session.sessionDescription = _sessionData.sessionDescription;
session.added = [NSNumber numberWithBool:YES];
_addButton = [[[UIBarButtonItem alloc] initWithTitle:#"Remove" style:UIBarButtonSystemItemAdd target:self action:nil] autorelease];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(saveMoc:) name:NSManagedObjectContextDidSaveNotification object:moc];
NSError *error = nil;
if (![moc save:&error]) {
DebugLog(#"Whoops, couldn't save:%#", [error localizedDescription]);
}
} else {
NSLog(#"SESSION ALREADY ADDED");
}
And add this methode somewhere in your file:
- (void)saveMoc:(NSNotification *)notification {
YourAppDelegate *appDel = (YourAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDel.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}