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];
}
Related
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.
I have a UICollectionView that needs to be updated when the model has changed.
It is subscribed to the notification center in order to do so:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(modeloActualizado:) name:NSManagedObjectContextObjectsDidChangeNotification object:self.contexto];
}
The UICollectionViewController connects to the model through a NSFetchedResultsController that is meant to return a list of the entities "Plaza" whose "ocupada" value is 1 (it is a boolean).
-(NSFetchedResultsController*) frController{
if(_frController == nil){
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *entidad = [NSEntityDescription entityForName:#"Plaza" inManagedObjectContext:self.contexto];
request.entity = entidad;
request.fetchBatchSize = 10;
// Añadir un predicado para filtrar por plazas ocupadas
request.predicate = [NSPredicate predicateWithFormat:#"ocupada == 1"];//%#",[NSNumber numberWithBool:YES]];
// Ordenar por numero de plaza
NSSortDescriptor *ordenPorNumero = [[NSSortDescriptor alloc] initWithKey:#"numero" ascending:YES];
NSArray *descriptores = [[NSArray alloc] initWithObjects:ordenPorNumero, nil];
[request setSortDescriptors:descriptores];
// Crear el FetchedResultsController
//[NSFetchedResultsController deleteCacheWithName:#"Coleccion"];
_frController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.contexto sectionNameKeyPath:nil cacheName:#"Coleccion"];
_frController.delegate = self;
NSError *error = nil;
if(![self.frController performFetch:&error]){
NSLog(#"Ha ocurrido un error: %# %#",error,[error userInfo]);
abort();
}
}
return _frController;
}
The method called when the model changed does reload the CollectionView data:
-(void)modeloActualizado:(NSNotification *) notificacion{
[self.collectionView reloadData];
}
The problem is, if I change any of the values of an entity, those changes are shown in the CollectionView, but if I change the entity boolean value "ocupada" to NO, it will still appear into the CollectionView until the App is closed and opened again.
Am I doing something wrong? I don't know why the NSFetchedResultsController is returning the same amount of objects even though one of them no longer matches the predicate condition.
Any ideas?
Thanks in advance.
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.
I am developing one app named safekeep.I have to import videos using coredata by converting into data.
if ([[info objectForKey:UIImagePickerControllerMediaType] isEqualToString:#"public.movie"])
{
NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL];
NSLog(#"Q: video path: %#",[videoURL description]);
self.videoData=[NSData dataWithContentsOfURL:videoURL];
if(self.videoData)
{
NSLog(#"data is present");
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *imagetblObj= [NSEntityDescription insertNewObjectForEntityForName:#"VideoData" inManagedObjectContext:context];
[imagetblObj setValue:self.videoData forKey:#"videodata"];
[imagetblObj setValue:str forKey:#"date"];
NSError *err;
if (![context save:&err])
{
NSLog(#"Couldn't save history item into coredata");
}
NSLog(#"data saved");
[self videodatafromdb];
}
}
-(void)videodatafromdb
{
NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"VideoData" inManagedObjectContext:context];
[request setEntity:entity];
NSError *error;
NSArray *recordsData=[context executeFetchRequest:request error:&error];
self.videoarray=[[recordsData reverseObjectEnumerator]allObjects];
NSLog(#"Array count is %d",[self.videoarray count]);
if ([self.videoarray count]>0)
{
[self createScrollViewvideo];
}
}
-(void)createScrollViewvideo
{
NSLog(#"in create scrollviewvideo");
//add views to scrolview
int x=5;
int y=17;
for(int i=0;i<[videoarray count];i++)
{
UIImage *imag=[[UIImage alloc] init];
UIView *videoview=[[UIView alloc] initWithFrame:CGRectMake(x, y, 100, 100)];
videoview.tag=i;
UIButton *userButton=[[UIButton alloc]initWithFrame:CGRectMake(1, 1, 100,100)];
[userButton addTarget:self action:#selector(userVideoClicked:) forControlEvents:UIControlEventTouchUpInside];
userButton.tag=i;
imag=[UIImage imageWithData:videoData];
[userButton setBackgroundImage:imag forState:UIControlStateNormal];
[videoview addSubview:userButton];
[self.scrollview addSubview:videoview];
[userButton release];
[videoview release];
x+=104;
if ((i+1)%3==0)
{
y+=110;
x=5;
}
}
NSLog(#"10000in create scrollview");
if (y+110>self.scrollview.frame.size.height)
{
self.scrollview.contentSize=CGSizeMake(320, y+110);
}
else
{
self.scrollview.contentSize=CGSizeMake(320, self.scrollview.frame.size.height+60);
}
}
}
-(IBAction)userVideoClicked:(id)sender
{
NSLog(#"video clicked");
UIButton *button=(UIButton*)sender;
VideoData *videoobj=(VideoData *)[self.videoarray objectAtIndex:[button tag]];
NSLog(#"video data is %#",videoobj);
VidoesVIewController *videoviewcontroller=[[VidoesVIewController alloc] initWithNibName:#"VidoesVIewController" bundle:nil];
videoviewcontroller.videodata=[videoobj valueForKey:#"videodata"];
[self.navigationController pushViewController:videoviewcontroller animated:YES];
[videoviewcontroller release];
}
Videos are not showing in sroll view.But When click on video file it is navigating. When image is set to button, image is showing in view.Thanks in advance.
See my answer in:
Can I access the files used for external binary storage in Core Data?
I came up with a 'creative' solution (whether it best practice is anyones guess), but it will allow you to use Core Data to manage your files while still being able to access the REAL files by their path.
By using the 'Allows External Storage' option on a Binary field, you can allow core data to manage references to your files for you, abstracting out a fair amount of the busy work.
Now this may sound like my earlier problem/question but I've changed and tried a few things that were answered in my other questions to try to make it work, but I've still got the same problem.
I am observing a core data property from within a NSManagedObject sub-class and the method that gets called when the property changes calls another method but in this method it adds Core Data objects which triggers the KVO method which triggers the method again and so forth. Or so it seems, I'm not too sure about that because something different seems to happen, here is the series of events …
I click a button syncing with iCal (this in an IBAction with the exact same code thats in the method syncKVO). This sync works fine.
I add an object to my outline view. All is well.
I change its name which triggers the KVO Declaration (because I changed the 'name' property) which syncs with iCal. Works fine.
I delete the object I just added and somehow it triggers the KVO declaration (thus triggering the method) and puts me into an infinite loop.
Now for some code.
Code inside the NSManagedObject Subclass (called JGManagedObject) …
- (void) awakeFromFetch {
[self addObserver:[NSApp delegate] forKeyPath:#"name" options:0 context:nil];
}
- (void) awakeFromInsert {
[self addObserver:[NSApp delegate] forKeyPath:#"name" options:0 context:nil];
}
+ (void) addObserver{
[self addObserver:[NSApp delegate] forKeyPath:#"name" options:0 context:nil];
}
+ (void) removeObserver{
[self removeObserver:[NSApp delegate] forKeyPath:#"name"];
}
The KVO Declaration (inside the App Delegate) …
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"name"]) {
[self performSelector:#selector(syncKVO:)];
}
}
The Method (also inside the App Delegate)…
- (void)syncKVO:(id)sender {
NSManagedObjectContext *moc = [self managedObjectContext];
[syncButton setTitle:#"Syncing..."];
NSString *dateText = (#"Last Sync : %d", [NSDate date]);
[syncDate setStringValue:dateText];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"projects" inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSError *error = nil;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (array == nil)
{
NSAlert *anAlert = [NSAlert alertWithError:error];
[anAlert runModal];
}
NSArray *namesArray = [array valueForKey:#"name"];
NSPredicate *predicate = [CalCalendarStore taskPredicateWithCalendars:[[CalCalendarStore defaultCalendarStore] calendars]];
NSArray *tasksNo = [[CalCalendarStore defaultCalendarStore] tasksWithPredicate:predicate];
NSArray *tasks = [tasksNo valueForKey:#"title"];
NSMutableArray *namesNewArray = [NSMutableArray arrayWithArray:namesArray];
[namesNewArray removeObjectsInArray:tasks];
NSLog(#"%d", [namesNewArray count]);
NSInteger *popIndex = [calenderPopup indexOfSelectedItem];
//Load the array
CalCalendarStore *store = [CalCalendarStore defaultCalendarStore];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *supportDirectory = [paths objectAtIndex:0];
NSString *fileName = [supportDirectory stringByAppendingPathComponent:#"oldtasks.plist"];
NSMutableArray *oldTasks = [[NSMutableArray alloc] initWithContentsOfFile:fileName];
[oldTasks removeObjectsInArray:namesArray];
NSLog(#"%d",[oldTasks count]);
//Use the content
NSPredicate* taskPredicate = [CalCalendarStore taskPredicateWithCalendars:[[CalCalendarStore defaultCalendarStore] calendars]];
NSArray* allTasks = [[CalCalendarStore defaultCalendarStore] tasksWithPredicate:taskPredicate];
// Get the calendar
CalCalendar *calendar = [[store calendars] objectAtIndex:popIndex];
// Note: you can change which calendar you're adding to by changing the index or by
// using CalCalendarStore's -calendarWithUID: method
// Loop, adding tasks
for(NSString *title in namesNewArray) {
// Create task
CalTask *task = [CalTask task];
task.title = title;
task.calendar = calendar;
// Save task
if(![[CalCalendarStore defaultCalendarStore] saveTask:task error:&error]) {
NSLog(#"Error");
// Diagnostic error handling
NSAlert *anAlert = [NSAlert alertWithError:error];
[anAlert runModal];
}
}
NSMutableArray *tasksNewArray = [NSMutableArray arrayWithArray:tasks];
[tasksNewArray removeObjectsInArray:namesArray];
NSLog(#"%d", [tasksNewArray count]);
for(NSString *title in tasksNewArray) {
NSManagedObjectContext *moc = [self managedObjectContext];
JGManagedObject *theParent =
[NSEntityDescription insertNewObjectForEntityForName:#"projects"
inManagedObjectContext:moc];
[theParent setValue:nil forKey:#"parent"];
// This is where you add the title from the string array
[theParent setValue:title forKey:#"name"];
[theParent setValue:[NSNumber numberWithInt:0] forKey:#"position"];
}
for(CalTask* task in allTasks)
if([oldTasks containsObject:task.title]) {
[store removeTask:task error:nil];
}
// Create a predicate for an array of names.
NSPredicate *mocPredicate = [NSPredicate predicateWithFormat:#"name IN %#", oldTasks];
[request setPredicate:mocPredicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
// Execute the fetch request put the results into array
NSArray *resultArray = [moc executeFetchRequest:request error:&error];
if (resultArray == nil)
{
// Diagnostic error handling
NSAlert *anAlert = [NSAlert alertWithError:error];
[anAlert runModal];
}
// Enumerate through the array deleting each object.
// WARNING, this will delete everything in the array, so you may want to put more checks in before doing this.
for (JGManagedObject *objectToDelete in resultArray ) {
// Delete the object.
[moc deleteObject:objectToDelete];
}
//Save the array
[namesArray writeToFile:fileName atomically:YES];
[syncButton setTitle:#"Sync Now"];
NSLog(#"Sync Completed");
}
What I've tried …
Filtering the Keypaths that call the KVO Declaration with
if ([keyPath isEqualToString:#"name"]) {
…
}
Detaching and reattaching observers with
[JGManagedObject removeObserver];
//and
[JGManagedObject addObserver];
but with that it works the first time but stops the method the second time saying that it cannot remove the observer because it is not observing, which doesn't make sense because I added the observer again the first time. That is why I left this code out of the actual method else it would stop on the second sync.
I'm not sure whats going on with this, I think I've tried everything. Whats gone wrong?
Any help would be greatly appreciated.
The problem might be here:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"name"]) {
[self performSelector:#selector(syncKVO:)];
}
}
You call syncKVO: everytime something happens to 'name' regardless of what it is that has actually happened to 'name'. I suggest you start using the object, change and context parameters to determine what has just happened and what action, if any, should be undertaken.
BTW, it's not considered good practice to add a lot of stuff to the app delegate. You might want to put all this syncing stuff into a proper controller class and call [NSApp delegate] when you need it.