I am currently trying to store images I download from the web into an NSManagedObject class so that I don't have to redownload it every single time the application is opened. I currently have these two classes.
Plant.h
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) PlantPicture *picture;
PlantPicture.h
#property (nonatomic, retain) NSString * bucketName;
#property (nonatomic, retain) NSString * creationDate;
#property (nonatomic, retain) NSData * pictureData;
#property (nonatomic, retain) NSString * slug;
#property (nonatomic, retain) NSString * urlWithBucket;
Now I do the following:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
PlantCollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
Plant *plant = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.plantLabel.text = plant.name;
if(plant.picture.pictureData == nil)
{
NSLog(#"Downloading");
NSMutableString *pictureUrl = [[NSMutableString alloc]initWithString:amazonS3BaseURL];
[pictureUrl appendString:plant.picture.urlWithBucket];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:pictureUrl]];
AFImageRequestOperation *imageOperation = [AFImageRequestOperation imageRequestOperationWithRequest:request success:^(UIImage *image) {
cell.plantImageView.image = image;
plant.picture.pictureData = [[NSData alloc]initWithData:UIImageJPEGRepresentation(image, 1.0)];
NSError *error = nil;
if (![_managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}];
[imageOperation start];
}
else
{
NSLog(#"Already");
cell.plantImageView.image = [UIImage imageWithData:plant.picture.pictureData];
}
NSLog(#"%#", plant.name);
return cell;
}
The plant information is present, as well as the picture object. However, the NSData itself is NEVER saved throughout the application opening and closing. I always have to REDOWNLOAD the image! Any ideas!? [Very new to CoreData... sorry if it is easy!]
thanks!
Edit & Update:
The image download is NOT nil and appears just fine after it was downloaded. It looks like the data returned by UIImageJPEGRepresentation is not nil also. My managedObjectContext is not nil as well. The request is done on the main-thread according to my debugger. Anything else I should check?!
Also, if I quit the view and come back, the images won't be downloaded again and the "Already" log will appear. However, if I close and reopen the app, they will have to be redownloaded, which is weird because the rest of the CoreData is still present.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Master"];
Update #2
It appears that all my ManagedObjectContext are the same. Could it have to do with the cache?
First thing to check, is plant or plant.picture nil after fetch? Second, you say that the request is made on the main thread and that your context is not nil. However, the response(inside the block) is it on the main thread? From the code you shared I would say plant.picture is nil, but other things are possible too.
You need to make sure that pictureData should have type transformable
And also in your class you need pictureData to be id not NSData
Like this
#property (nonatomic, retain) id pictureData;
Related
I have a problem in my test app using iCloud Core Data. The specific problem is triggered when a partial entry in a table view is touched which triggers the full table entry to be displayed on a full screen. Kind of standard table view stuff.
The table view cell contains an unique record ID which is passed into the detail display view. Upon starting up the view, the view controller get uses the full record data and paints it up on the screen.
Here is the code used to do that:
- (void)viewDidLoad {
[super viewDidLoad];
NSString *temp_text;
Item *fetchSelectedItem;
NSString *updateUniqueID;
NSArray *tempFetchedObjects;
NSError *error;
int stopper;
localFindItDataController = [[FindItDataController alloc] init];
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Item" inManagedObjectContext: localFindItDataController.managedObjectContext];
[fetch setEntity:entityDescription];
updateUniqueID = [localItem.itemUniqueID stringValue];
[fetch setPredicate:[NSPredicate predicateWithFormat:#"(ANY itemUniqueID = %#)",updateUniqueID]];
tempFetchedObjects = [localFindItDataController.managedObjectContext executeFetchRequest:fetch error:&error];
NSLog(#"did the fetch work %# %lu", updateUniqueID, (unsigned long)[tempFetchedObjects count]);
if ([tempFetchedObjects count] == 0) {
stopper = 1;}
fetchSelectedItem = [tempFetchedObjects objectAtIndex:0];
NSLog(#"only executes if fetch was ok");
localItem = fetchSelectedItem;
temp_text = localItem.itemName;
contentsWhatBox.text = temp_text;
temp_text = localItem.itemLocation;
contentsWhereBox.text = temp_text;
temp_text = localItem.itemDescription;
contentsOtherInfoBox.text = temp_text;
UIImage *tempImage = [UIImage imageWithData:localItem.itemPicture];
CGImageRef imageRef = [self CGImageRotatedByAngle:[tempImage CGImage] angle:270];
UIImage* img = [UIImage imageWithCGImage: imageRef];
contentsImageBox.image = img;
tapImage = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapImageGesture)];
[contentsImageBox setUserInteractionEnabled:YES];
[contentsImageBox addGestureRecognizer:tapImage];
}
Mostly this works just fine but occasionally the fetch comes up empty even though there definitely is data that matches the fetch predicate. Sometimes I can tap on a table view entry, see it come up, go back and forth a few times between table view entries, go back to the original entry and the fetch fails.
I'm at a bit of a loss and any help would be welcome. Thanks in advance.
Edit #1:
This is the object that is being fetched. Might matter so I added it in later:
#interface Item : NSManagedObject {
}
#property (nonatomic, retain) NSString * itemName;
#property (nonatomic, retain) NSString * itemLocation;
#property (nonatomic, retain) NSString * itemDescription;
#property (nonatomic, retain) NSData * itemPicture;
#property (nonatomic, retain) NSNumber * itemUniqueID;
#property (nonatomic, retain) NSNumber * itemNumber1;
#property (nonatomic, retain) NSNumber * itemNumber2;
#property (nonatomic, retain) NSString * itemAttribute1;
#property (nonatomic, retain) NSString * itemAttribute2;
#property (nonatomic, retain) NSString * itemAttribute3;
#property (nonatomic, retain) NSString * itemTimestamp;
#end
Edit #2:
I forgot to mention that the error object from the fetch is nil after the 'bad' fetch.
Edit #3:
Added a loop to check if I got a result and retry a certain number of times before quitting and that worked ok. I'm not sure why it's going on but I suspect the image in the iCloud data might be part of the problem even though the data at this point for sure is replicated to the iOS test device(s).
Added a loop to check if I got a result and retry a certain number of times before quitting and that worked ok. I'm not sure why it's going on but I suspect the image in the iCloud data might be part of the problem even though the data at this point for sure is replicated to the iOS test device(s).
What I'm doing
Using RestKit, I'm making a GET request to get a JSON object that contains an array of User objects that populate a UITableView. I pass that array into a private NSArray called users which becomes _users (I'm still fuzzy on this). This works, and the table populates fine. I can access the individual objects in the _users array from my other methods, such as [UITableViewCell cellForRowAtIndex].
However, at the same time I pull the data down, and before I call [self.tableView reloadData] from inside the success block of [RKObjectManager getObjectsAtPath...], I want to process the individual objects a little bit.
My problem
Using [RKObjectManager getObjectsAtPath parameters success failure], success returns the RKMappingResult as expected, and I pass its array to a _users, which populates my UITableView. This works, but in the same success block, I try NSLog'ing _users[i] and it returns *nil description*. I know the values are being set at some point, because I populate my UITableViewCells by calling _users[i] in another method.
Hopefully more helpful info
When I NSLog(#"%#", _users) from inside the success block, and know for a fact there are 3 objects in the array, I see:
(
(null),
(null),
(null)
).
I can provide more info, I'm just not sure what to put. I can also show my code, but it's basically out of the book from the RestKit docs.
User object
#interface User : NSObject
#property (nonatomic, strong) NSString *id;
#property (nonatomic, strong) NSString *email;
#property (nonatomic, strong) NSString *username;
#property (nonatomic, strong) NSString *fullName;
#property (nonatomic, strong) NSString *bio;
#property (nonatomic) NSDate *dob;
#property (nonatomic, strong) NSString *avatar;
#property (nonatomic, strong) NSString *avatarMeta;
#property (nonatomic, strong) NSString *location;
#property (nonatomic, strong) NSURL *url;
#property (nonatomic, strong) NSString *enabled;
#end
RKObjectManager example
*note - some pieces have been removed for security reasons
- (void)loadUsers {
NSMutableDictionary *params = [[NSMutableDictionary alloc] initWithDictionary:#{#"token": self.token}];
NSString *path = #"/api/v1/g/feed";
if ( self.cursor ) {
path = [NSString stringWithFormat:#"%#/%#", path, self.cursor];
}
[[RKObjectManager sharedManager] getObjectsAtPath:path
parameters:params
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
_users = mappingResult.array;
NSLog(#"Last User: %#",_users[0]); // *nil description*
NSLog(#"Array: %#", _users); // ((null),(null),(null),(null),(null),(null),(null),(null),(null),(null))
[self.tableView reloadData]; // table gets populated correctly
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No feed available: %#", error);
}];
}
Most likely you're overriding the description method, probably by adding your own property with that name, in the user class and setting that to an empty string. This breaks everything. You should rename that property to overview or something like that and update your usage.
I want to insert 50 values in coredata ta
Question.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface Question : NSManagedObject
#property (nonatomic, retain) NSString * question;
#property (nonatomic, retain) NSString * answer;
#property (nonatomic, retain) NSString * mcqsa;
#property (nonatomic, retain) NSString * mcqsb;
#property (nonatomic, retain) NSString * mcqsc;
#property (nonatomic, retain) NSString * mcqsd;
Question.m
#import "Question.h"
#implementation Question
#dynamic question;
#dynamic answer;
#dynamic mcqsa;
#dynamic mcqsb;
#dynamic mcqsc;
#dynamic mcqsd;
#end
Insert Code
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSManagedObjectContext *context = [self managedObjectContext];
Question *question = [NSEntityDescription
insertNewObjectForEntityForName:#"Question"
inManagedObjectContext:context];
question.question = #"What is capital of Australia?";
question.answer = #"Testville";
question.mcqsa = #"Sydney";
question.mcqsb = #"Canbera";
question.mcqsc = #"Berlin";
question.mcqsd = #"Bern";
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
.......
How can I insert multiple data, 50 questions with their mcqs (50 Question object)?
Is there any manual way using something else?
MagicalRecord a wrapper for CoreData provides some nice import features. You need to include the data that you need to import as plist/json in the bundle.
On the initial run you import data to coreData.
You can find a great tutorial on the same in the following link Importing data made easy
You need to create subclasses of NSManagedObject
on them you can call importFromObject: or importFromObject:. If you have full control over the structuring of the data this will happen out of the box without writing a single code for mapping data stored to core data entities.
You need a seed store, Pre-fill core data database then fetch it from device or simulator document directory and than add it to your bundle. At first run, you would copy database from bundle to Document directory and use that database onwards
You can use for loop to insert data.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
for(i=0; i<50; i++) {
NSManagedObjectContext *context = [self managedObjectContext];
Question *question = [NSEntityDescription
insertNewObjectForEntityForName:#"Question"
inManagedObjectContext:context];
question.question = #"What is capital of Australia?";
question.answer = #"Testville";
question.mcqsa = #"Sydney";
question.mcqsb = #"Canbera";
question.mcqsc = #"Berlin";
question.mcqsd = #"Bern";
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
.......
}
Here is the code:
NSString* plistPath = [[NSBundle mainBundle] pathForResource:#"QuesList" ofType:#"plist"];
NSArray *quesList = [[NSArray alloc]initWithContentsOfFile:plistPath];
for(NSDictionary *object in quesList){
NSString *str1 = [object objectForKey:#"question"];
NSString *str2 = [object objectForKey:#"answer"];
NSString *str3 = [object objectForKey:#"mcqsa"];
NSString *str4 = [object objectForKey:#"mcqsb"];
NSString *str5 = [object objectForKey:#"mcqsc"];
Question *question = [NSEntityDescription
insertNewObjectForEntityForName:#"Question"
inManagedObjectContext:context];
question.question = str1;
question.answer =str2;
question.mcqsa =str3;
question.mcqsb =str4;
question.mcqsc = str5;
}
And
Also make sure that you run this code only once, otherwise it woudld create duplicate objects
I am getting a EXC_BAC_ACCESS error when attempting to save a TRUE value into a managed object that contains a Boolean attribute.
id delegate = [[UIApplication sharedApplication] delegate];
self.managedObjectContext = [delegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TrafficCameraInfo"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"code=%#",self.selectedTrafficCamera.code]];
NSError *error;
TrafficCameraInfo *cgTrafficCamera;
cgTrafficCamera = [[self.managedObjectContext executeFetchRequest:fetchRequest error:&error] lastObject];
NSString *alertMessage;
if (cgTrafficCamera.favourite == NO){
cgTrafficCamera.name = #"TEST"; <-- works ok
cgTrafficCamera.favourite = 1; <-- causes an error
} else {
cgTrafficCamera.favourite = 0;
}
error = nil;
if (![self.managedObjectContext save:&error]) {
The managed object interface looks like this:
#interface TrafficCameraInfo : NSManagedObject
#property (nonatomic, retain) NSString *code;
#property (nonatomic, retain) NSString *postCode;
#property (nonatomic, retain) NSNumber *latitude;
#property (nonatomic, retain) NSNumber *longitude;
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSString *url;
#property (nonatomic) Boolean favourite;
#end
Elsewhere in my app i am updating another Boolean field by passing a 1 to it and am not encountering a problem.
Any ideas what is causing the error?
I think in your core data attribute table you define "favorite" variable to BOOL,well that means it's an NSNumber Type,so you should set the data using NSNumber
A Boolean is a simple, scalar, non-pointer datatype. Core Data properties are always stored as objects. The Objective-C object wrapper for numeric datatypes is NSNumber. So if favourite is a regular stored property, you should declare it as:
#property (nonatomic, retain) NSNumber *favourite;
Assignment would be done like this:
cgTrafficCamera.favourite = [NSNumber numberWithBool:YES]; // Obj-C style is "YES/NO" for BOOL
Or this if you prefer:
cgTrafficCamera.favourite = [NSNumber numberWithBool:1];
If you don't need to store the Boolean, you can leave it as such and make it a transient property. You'll probably need to get rid of the "(nonatomic)" in that case.
I have an entity in my core data model like this:
#interface Selection : NSManagedObject
#property (nonatomic, retain) NSString * book_id;
#property (nonatomic, retain) NSString * contenu;
#property (nonatomic, retain) NSNumber * page_id;
#property (nonatomic, retain) NSNumber * nbrOfOccurences;
#property (nonatomic, retain) NSString * next;
#property (nonatomic, retain) NSString * previous;
I have created many Selections and saved them in Core Data and now I would like to delete some selections with some criteria. For example, I would like to delete a Selection object if matches the following:
content = test
page_id = 5
book_id = 1331313
How I can do this?
What Mike Weller wrote is right. I'll expand the answer a little bit.
First you need to create a NSFetchRequest like the following:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Selection" inManagedObjectContext:context]];
Then you have to set the predicate for that request like the following:
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"content == %# AND page_id == %# AND book_id == %#", contentVal, pageVal, bookVal]];
where
NSString* contentVal = #"test";
NSNumber* pageVal = [NSNumber numberWithInt:5];
NSString* bookVal = #"1331313";
I'm using %# since I'm supposing you are using objects and not scalar values.
Now you perform a fetch in the context with the previous request:
NSError* error = nil;
NSArray* results = [context executeFetchRequest:fetchRequest error:&error];
results contains all the managed objects that match that predicate.
Finally you could grab the objects and call a deletion on them.
[context deleteObject:currentObj];
Once done you need to save the context as per the documentation.
Just as a new object is not saved to the store until the context is saved, a deleted object is not removed from the store until the context is saved.
Hence
NSError* error = nil;
[context save:&error];
Note that save method returns a bool value. So you can use an approach like the following or display an alert to the user. Source NSManagedObjectContext save error.
NSError *error = nil;
if ([context save:&error] == NO) {
NSAssert(NO, #"Save should not fail\n%#", [error localizedDescription]);
abort();
}
You should perform a fetch request using an NSPredicate with the appropriate conditions, and then call the deleteObject: method on NSManagedObjectContext with each object in the result set.
In addition to Mike Weller and flexaddicted, after calling [context deleteObject:currentObj]; you need to save: context:
NSError *error = nil;
[context save:&error];
As from documentation:
Just as a new object is not saved to the store until the context is saved, a deleted object is not removed from the store until the context is saved.
That made matter in my case.