UIImageView shows image on simulator but not on iPhone - objective-c

I've been getting a very weird bug with one of my UIImageViews. I have a UITableView that lists items, and each cell is an item. Each item cell has a thumbnail image, a title, and a subtitle. I am able to show the thumbnail in every cell perfectly fine on the simulator and on the iPhone.
I also have a detail VC that, when a certain cell gets tapped, it segue's to that VC and shows the large image, along with some more info about the item. I've been able to load the image up in the simulator, but on the iPhone, all I get is a blank UIImageView.
This is how it looks in the simulator.
And my detail VC:
The blue view at the bottom is where I will add labels to show more details about the item, and the orange is a UIImageView that will show a custom image just saying 'item details'. One can tap on that, or my big UImageView and it will animate up (and down to dismiss).
Now, the problem is - when I segue from my table VC to my detail VC, the UIImageView is just blank on the iPhone. It works perfectly fine on the simulator, though, so I'm passing the right image and such.
Here is the code to segue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"showItemDetails"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
ItemDetailViewController *itemDetailVC = segue.destinationViewController;
Item *item = [self.fetchedResultsController objectAtIndexPath:indexPath];
if(item)
{
itemDetailVC.pictureImage = [UIImage imageWithContentsOfFile:item.photoURL];
}
}
}
My items gets saved using Core Data. photoURL is the URL of the photo in the documents directory of my app, as I do not save the entire image to Core Data, just a reference to it for performance purposes.
Here is part of the code that gets data from another VC in an "unwind" segue.
- (IBAction)saveItem:(UIStoryboardSegue *)segue
{
ManageItemViewController *manageItemVC = (ManageItemViewController *)segue.sourceViewController;
NSMutableDictionary *itemInfo = [[NSMutableDictionary alloc] init];
...
// write the original photo to a file and keep an URL to it that we pass to Core Data
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(manageItemVC.pictureImage)];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0]; //Get the docs directory
NSString *fileName = [NSString stringWithFormat:#"%lf.png", [[NSDate date] timeIntervalSince1970]];
NSString *filePath = [documentsPath stringByAppendingPathComponent:fileName]; //Add the file name
[imageData writeToFile:filePath atomically:YES]; //Write the file
[itemInfo setObject:filePath forKey:CD_ITEM_PHOTOURL_PROPERTY];
...
[self saveItemToCoreData:[itemInfo copy]];
}
In my detail VC, this is all I do to load the UIImage I send to the big UIImageView.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if(self.pictureImage) {
[self.pictureImageView setImage:self.pictureImage];
}
}
And when the user delete an item (delete row), I also remove the file from my documents directory, as such (this code is in my table VC):
- (void)deleteItemImageFromDevice:(NSString *)filePath
{
if(filePath)
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
if([fileManager removeItemAtPath:filePath error:&error]) {
NSLog(#"Image deleted from documents directory!");
} else {
NSLog(#"%#", error);
}
}
}
I logged this and I do not get any errors in the simulator.
Anyone know what the issue is here? Thanks.

I've managed to fix this issue.
For future reference, here are the fixes I made to my code.
Instead of a time interval, I now get a guaranteed unique string (no need for long float conversions) using NSProcessInfo, as so:
NSString *fileName = [NSString stringWithFormat:#"%#.png", [[NSProcessInfo processInfo] globallyUniqueString]];
Then, in my prepareForSegue: method, I get an UIImage from data that exists in our documents folder (a file). The item.photoURL is our file path.
Item *item = [self.fetchedResultsController objectAtIndexPath:indexPath];
if(item.photoURL)
{
itemDetailVC.pictureImage = [UIImage imageWithData:[NSData dataWithContentsOfFile:item.photoURL]];
}
Lastly, in our detail VC header file, our UIImage (not UIImageView!) needs to be strong reference, not a weak one, as we need to access it even before the VC is on screen:
#property (nonatomic, strong) UIImage *pictureImage;
The UIImageView we assign this UIImage to can be weak because it only needs to be shown when the view is on screen, and that holds a strong pointer to all of its subviews. For an excellent analogy of strong vs weak, check this.

Related

Populate UICollectionView Images From Document Directory AND Update Custom NSMutableArray object?

In my data controller I have book objects and each object contains these properties (among others):
novel.title = #"Book One";
novel.imageArray = [[NSMutableArray alloc] initWithObjects: [UIImage imageNamed: #"book1image1"], nil];
In the app, users can add a book image using UIImagePickerController like this:
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(nonnull NSDictionary<NSString *,id> *)info {
[picker dismissViewControllerAnimated:YES completion:nil];
_addedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
_addedImage = [self scaleImage:_addedImage toSize:CGSizeMake(120, 168)];
[_book.imageArray addObject:_addedImage];
NSString *stringPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0]stringByAppendingPathComponent:_book.imageArrayID];//folder name
NSError *error = nil;
if (![[NSFileManager defaultManager] fileExistsAtPath:stringPath])
[[NSFileManager defaultManager] createDirectoryAtPath:stringPath withIntermediateDirectories:NO attributes:nil error:&error];
int i = 0;
for(_addedImage in _book.imageArray)
{
_book.bookAddedToArray = YES;
NSString *fileName = [stringPath stringByAppendingFormat:#"/image%i.jpg", i++];//image name
NSData *data = UIImageJPEGRepresentation(_addedImage, 1.0);
[data writeToFile:fileName atomically:YES];
}
[self.collectionView reloadData];
}
This works perfectly fine. All images are saved in the right place as expected.
As long as the app stays open, the new images can be viewed in the collectionView. You can navigate anywhere in the app, come back, and view the collectionView some more. When the app is completely exited out of and then reopened, the collectionView is reset and only shows the initial image that was set in the dataController (no matter what code I've implemented so far. It just always resets). All the user generated images are still in their respective folders in the documents directory but I cannot seem to update the cellForItemAtIndexPath: with the stored images presumably because the images are not being saved to the _book.imageArray. Right now I have the cell being populated by the default imagearray so, of course, that is what will show up. How do I update the array and pull from documents directory to show user images?
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
if(_book.bookAddedToArray)
{
//*** HOW TO UPDATE _BOOK.IMAGEARRAY ????
cell.bookImageView.image = [_book.imageArray objectAtIndex:indexPath.row];
}
else
{
cell.bookImageView.image = [_book.imageArray objectAtIndex:indexPath.row];
}
return cell;
}
I've read that you can store an array of directory paths in nsuserdefaults and retrieve it to populate an imagearray but I have yet to find a solution that works for me. None of the answers seem to address loading into a collectionview or tableview. Can anyone point me in the right direction? Please? Anyone? Lol. Let me know if you need to see more code. All suggestions are much appreciated!
Eventually the app will need to be rewritten as it has grown beyond the scope of my original code, but at this time, this is what I'm working with. I'll probably eventually use CoreData but I've even seen where people on S.O. recommended against storing images in CoreData and to use the documents directory. That still leaves me in this same situation then.
Yes! I got it figured out and it works like a charm. It seems that I could not actually update my custom NSMutableArray. I could make it read another array but could not actually change the array itself. (I'm referring to the _book.imageArray.)
Everything stayed the same in the didFinishPickingMediaWithInfo.
I didn't need to add anything to the cellForItemAtIndexPath. It is just simply:
if(_book.bookAddedToArray)
{
cell.bookImageView.image = [_book.imageArray objectAtIndex: indexPath.row];
}
I created a property for my new array:
#property (nonatomic, strong) NSMutableArray *imagePathArray;
All the magic happens in ViewDidLoad.
- (void)viewDidLoad
{
[super viewDidLoad];
//initialize new array here
_imagePathArray = [[NSMutableArray alloc] init];
if (_book.bookAddedToArray)
{
int i = 0;
//this is the path to the folder.
NSString *stringPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0]stringByAppendingPathComponent:_book.imageArrayID];
int z;
NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:stringPath error:NULL];
for (z = 0; z < (int)[directoryContent count]; z++)
{
//this is the path to each image file
NSString *fileName = [stringPath stringByAppendingFormat:#"/image%i.jpg", i++];
NSLog(#"imagefile = %#", fileName);
UIImage *fileNameImage = [UIImage imageWithContentsOfFile:fileName];
//add each image to a new array
[_imagePathArray addObject:fileNameImage];
}
//set my array to the new array
_book.imageArray = _imagePathArray;
}
}
I can thank this post for helping to read the images within each folder:
go to link.
//these are all properties in my Data Class.
_book.imageArrayID
_book.bookAddedToArray
_book.imageArray
I hope this helps someone out!

How to prevent textfields from getting empty when adding/removing UITableView rows?

I'm creating a UITableView in which product information can be added. In each row, the user can add information about a product, and, obviously, the user can set the number of rows himself.
the user can add or remove one row a time by tapping either the "add row" or "remove row" button in the NavigationBar. this is how it works:
- (void)viewDidLoad
{
[super viewDidLoad];
tableRows = [NSNumber numberWithInt:12];
}
-(void) addRow
{
NSNumber *addRow =[NSNumber numberWithInt:1];
tableRows= [NSNumber numberWithInt:(tableRows.intValue + addRow.intValue)];
[self.tableView reloadData];
NSLog(#"%#", tableRows);
}
-(void) removeRow
{
NSNumber *addRow =[NSNumber numberWithInt:1];
tableRows= [NSNumber numberWithInt:(tableRows.intValue - addRow.intValue)];
[self.tableView reloadData];
NSLog(#"%#", tableRows);
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return tableRows.intValue;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CustomCellIdentifier = #"CustomCellIdentifier ";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier: CustomCellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCell"
owner:self options:nil];
for (id oneObject in nib) if ([oneObject isKindOfClass:[CustomCell class]])
cell = (CustomCell *)oneObject;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
NSUInteger *row = [indexPath row];
return cell;
}
The editing works perfect but when I add or remove a row, the text I inserted in the textfields of my tableview disappears.
does anybody know how to prevent this?
A couple things: The table view doesn't have responsibility to remember what's in each of the cells. It throws away cells as the scroll away and asks the datasource to initialize them again via cellForRowAtIndexPath. Reloaddata - which you use in your add/remove methods - will cause the table to refresh all of the visible cells. Don't expect anything to appear in your table that isn't setup in cellForRowAtIndexPath.
Next, your "model" for this table is an NSNumber "tableRows" indicating the number of rows. This is an insufficient model for a table view. Replace it with an NSMutableArray. At the very least, this array should contain strings representing the state of each text field. (and it might need even more elaborate objects, but start with strings).
With that, your view controller class will look more like this...
// this is your table's model
#property (nonatomic, strong) NSMutableArray *rows;
// in init for the class
_rows = [NSMutableArray array];
// somewhere else, put some data in it
[self.rows addObject:#"Foo"];
[self.rows addObject:#"Bar"];
Now your datasource methods:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return self.rows.count;
}
Then, in cellForRowAtIndexPath:
NSUInteger *row = [indexPath row]; // as you have it
NSString *rowText = self.rows[row]; // this is new syntax, the old way is [self.rows objectAtIndex:row];
// your CustomCell needs to provide a way to get at the textField it contains
// it might do this with an outlet or viewWithTag...
cell.myTextField.text = rowText;
return cell;
Finally, text fields in the cells pose a particular challenge. How to save their current state when the view isn't scrolling. This problem has been asked and answered multiply in SO (here, for example). In a nutshell, the most common solution is to make the view controller the delegate of the text fields in the cells. Then, on textFieldDidEndEditing, save the value of the textField in your model like this...
- (void)textFieldDidEndEditing:(UITextField *)textField {
NSIndexPath *indexPath = [self indexPathOfCellWithSubview:textField];
self.rows[indexPath.row] = textField.text;
}
// I think this is the best way to get the index path of a cell, given some subview it contains
- (NSIndexPath *)indexPathOfCellWithSubview:(UIView *)view {
while (view && ![view isKindOfClass:[UITableViewCell self]]) {
view = view.superview;
}
return [self.tableView indexPathForCell:(UITableViewCell *)view];
}
EDIT Say there's more to the model than just a single string. This is where you would apply a custom subclass of NSObject.
// MyModel.h
#interface MyModel : NSObject
#property (strong, nonatomic) NSString *itemName;
#property (assign, nonatomic) CGFloat price;
#property (strong, nonatomic) NSString *imageFileName;
#property (strong, nonatomic) UIImage *image;
- (id)initWithItemName:(NSString *)itemName price:(CGFloat)price imageFileName:(NSString *)imageFileName;
- (NSString *)stringPrice;
- (void)setStringPrice:(NSString *)stringPrice;
#end
// MyModel.m
#implementation MyModel
- (id)initWithItemName:(NSString *)itemName price:(CGFloat)price imageFileName:(NSString *)imageFileName {
self = [self init];
if (self) {
_itemName = itemName;
_price = price;
_imageFileName = imageFileName;
}
return self;
}
// override the image getter to "lazily" create and cache the image
// if the images are on the web, this will require a slighly more elaborate method
// employing NSURLConnection.
- (UIImage *)image {
if (!_image) {
_image = [UIImage imageNamed:self.imageFileName];
}
return _image;
}
// added these to show you how you can conveniently encapsulate other
// behavior, like type conversion or validation, though, real ones of these
// would probably use NSNumberFormatter
- (NSString *)stringPrice {
return [NSString stringWithFormat: #"%.2f", self.price];
}
- (void)setStringPrice:(NSString *)stringPrice {
self.price = [stringPrice floatValue];
}
Now you can create one like this and add it to your table. (Be sure to #import "MyModel.h")
[self.rows addObject:[[MyModel alloc] initWithItemName:#"Toaster" price:39.95 imageFileName:#"toaster.png"]];
The view controller containing the table stays more or less the same (when you change one class a lot and change a closely related class very little, it tells you that your OO design is probably pretty good). For the fancy model replacing the string, we need to change cellForRowAtIndexPath...
NSUInteger *row = [indexPath row];
MyModel *myModel = self.rows[row];
cell.itemNameTextField.text = myModel.itemName;
cell.priceTextField.text = [myModel stringPrice];
cell.imageView.image = myModel.image;
// additional OO idea: teach your cell how to configure itself and move the foregoing code there
// [cell configureWithModel:myModel];
return cell;
ANOTHER EDIT: We can teach this model how to post itself to a remote web service as follows:
- (void)post {
NSString *hostStr = #"http://myhost/create_product.php";
NSURL *url = [NSURL URLWithString:hostStr];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = #"POST";
NSString *post =[NSString stringWithFormat:#"item_name=%#&price=%#",self.itemName, [self stringPrice];
NSString *postEscaped = [post stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSData *postData = [postEscaped dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
[request setHTTPBody:postData];
[request setValue:#"application/x-www-form-urlencoded charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"response %#", string);
} else {
NSLog(#"error %#", error);
}
}];
}
Declare this method in the .h, add other fields to the post as you see fit (e.g. the image file name, etc.)
In your view controller, pick out the action that means the user wants to commit the new row (maybe it's when the text field is finished editing?), and add this...
// text field finished editing
MyModel *myModel = self.rows[indexPath.row];
myModel.itemName = textField.text;
[myModel post];
Since the image will probably come from your remote service, you'll want to change the lazy loading image getter I added earlier. The right way to load this image is asynchronously, but doing so complicates the interaction with the table view too much to discuss here. Refer to apple docs or this SO post to learn more about that. In the meantime, here's the quick -- but basically wrong -- way to get the image synchronously...
- (UIImage *)image {
if (!_image) {
// note - now the file name must be of the form #"http://host/path/filename.png"
NSURL *url = [NSURL URLWithString:self.imageFileName
_image = [UIImage imageWithContentsOfURL:url];
}
return _image;
}
It would be helpful to see your code for cellForRowAtIndexPath, we need to know more about the model you intend to store data in.
When you delete a row from the table, that cell is thrown out, and the tableview will not remember the contents automatically. You must save the changes in a model object as they occur, and then use that to populate the cell's contents when returning a cell from cellForRowAtIndexPath.

Cache Image From URL working, but returns blank image?

I have two methods, first checks if I've already downloaded the image, and if not retrieves the image from a URL and caches it to my docs directory in my app. If it has been, it simply retrieves it, and if I have a internet connection, will re-download it. Here are the two methods:
- (UIImage *) getImageFromUserIMagesFolderInDocsWithName:(NSString *)nameOfFile
{
UIImage *image = [UIImage imageNamed:nameOfFile];
if (!image) // image doesn't exist in bundle...
{
// Get Image
NSString *cleanNameOfFile = [[[nameOfFile stringByReplacingOccurrencesOfString:#"." withString:#""]
stringByReplacingOccurrencesOfString:#":" withString:#""]
stringByReplacingOccurrencesOfString:#"/" withString:#""];
NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"Documents/%#.png", cleanNameOfFile]];
image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfFile:filePath]];
if (!image)
{
// image isn't cached
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:nameOfFile]]];
[self saveImageToUserImagesFolderInDocsWithName:cleanNameOfFile andImage:image];
}
else
{
// if we have a internet connection, update the cached image
/*if (isConnectedToInternet) {
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:nameOfFile]]];
[self saveImageToUserImagesFolderInDocsWithName:cleanNameOfFile andImage:image];
}*/
// otherwise just return it
}
}
return image;
}
Here's to save the image
- (void) saveImageToUserImagesFolderInDocsWithName:(NSString *)nameOfFile andImage:(UIImage *)image
{
NSString *pngPath = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"Documents/%#.png", nameOfFile]];
[UIImagePNGRepresentation(image) writeToFile:pngPath atomically:YES];
NSLog(#"directory: %#", [[UIImage alloc] initWithContentsOfFile:pngPath]);
}
The image has already been successfully downloaded and cached to my documents directory (I know because I can see it in the File system). And it successfully re loads the image the first time I call this method, but once I go to another view, and re-call this method when I come back to the same view, it's blank. Yet, the URL is correct. What's wrong here?
1) You should not be writing into a hardcoded path as you do (ie "Documents/xxx"), but rather ask for the Application Support directory, use it, and also mark files so that they don't get uploaded to iCloud (unless you want that). See this link on the specifics. Create a subfolder in it and mark it as not for iCloud backup.
2) Try changing:
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:nameOfFile]]];
to
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:filePath]]];
Maybe, you should do:
image = [UIImage imageWithContentsOfFile: nameOfFile];

Export image to Instagram - iPhone

I have an image and I want to export it to Instagram, so I can post it.
The code I'm using is:
NSURL *instagramURL = [NSURL URLWithString:#"instagram://location?id=1"];
if ([[UIApplication sharedApplication] canOpenURL:instagramURL]) {
NSString *documentsDirectory = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
NSString *savedImagePath = [documentsDirectory stringByAppendingPathComponent:#"Image.igo"];
UIImage *image = [UIImage imageNamed:#"01.png"];
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:savedImagePath atomically:YES];
NSURL *imageUrl = [NSURL fileURLWithPath:savedImagePath];
NSLog(#"%#",imageUrl);
UIDocumentInteractionController *docController = [[UIDocumentInteractionController alloc] init];
docController.delegate = self;
docController.UTI = #"com.instagram.exclusivegram";
docController.URL = imageUrl;
//[docController setURL:imageUrl];
[docController presentOpenInMenuFromRect:CGRectZero inView:self.view animated:YES];
}
When I run the app, the App shows the button written "Instagram" its icon, but when I touch it, the button disappear, and nothing happens. The app didn't crash, but nothing happen.
What I missed in my code ?
Regards
Bruno
I guess the problem is you do not retain the UIDocumentInteractionController. Make an ivar for it in your class.
Make sure the method documentInteractionController:didEndSendingToApplication: is called on the delegate.
Also check out the Instagram documentation: http://instagram.com/developer/iphone-hooks/
When triggered, Instagram will immediately present the user with our
filter screen. The image is preloaded and sized appropriately for
Instagram. Other than using the appropriate image format, described
above, our only requirement is that the image is at least 612px tall
and/or wide. For best results, Instagram prefers opening a JPEG that
is 612px by 612px square. If the image is larger, it will be resized
dynamically.
To verify that Instagram is installed check for the URL instagram://app. The URL instagram://location?id=LOCATION_ID is intended for location feeds only!

Displaying images in uiwebview from core data record

So I have an app I've written for the iPad, and I'd like to be able to allow users to insert images into their documents by selecting an image from an album or the camera. All that works great. Because the user might keep the document longer than they keep the image in an album, I make a copy of it, scale it down a bit, and store it in a core data table that is just used for this purpose.
I store the image like this:
NSManagedObjectContext* moc=[(ActionNote3AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSString* imageName=[NSString stringWithFormat:#"img%lf.png",[NSDate timeIntervalSinceReferenceDate]];
Image* anImage = [NSEntityDescription insertNewObjectForEntityForName:#"Image" inManagedObjectContext:moc];
anImage.imageName=imageName;
anImage.imageData=UIImagePNGRepresentation(theImage);
NSError* error=nil;
if(![moc save:&error]) {...
I sub-class NSURLCache, as suggested on Cocoa With Love, and ovverride cachedResponseForRequest thusly:
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
NSString *pathString = [[[request URL] absoluteString]lastPathComponent];
NSData* data = [Image dataForImage:pathString];
if (!data) {
return [super cachedResponseForRequest:request];
}
NSURLResponse *response =[[[NSURLResponse alloc]
initWithURL:[request URL]
MIMEType:[NSString stringWithString:#"image/png"]
expectedContentLength:[data length]
textEncodingName:nil]
autorelease];
NSCachedURLResponse* cachedResponse =[[[NSCachedURLResponse alloc] initWithResponse:response data:data] autorelease];
return cachedResponse;
}
I also make sure the app uses the sub-classed NSURLCache by doing this in my app delegate in didFinishLaunchingWithOptions:
ANNSUrlCache* uCache=[[ANNSUrlCache alloc]init];
[NSURLCache setSharedURLCache:uCache];
The method that returns the image data from the core data record looks like this:
+(NSData*)dataForImage:(NSString *)name {
NSData* retval=nil;
NSManagedObjectContext* moc=[(ActionNote3AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Image" inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"imageName==%#", name];
[request setPredicate:predicate];
NSError* error=nil;
NSArray *array = [moc executeFetchRequest:request error:&error];
if ([array count]>0) {
retval=((Image*)[array objectAtIndex:0]).imageData;
}
return retval;
}
To insert the image into the web view, I have an html img tag where the name in src="" relates back to the name in the image table. The point of the NSURLCache code above is to watch for a name we have stored in the image table, intercept it, and send the actual image data for the image requested.
When I run this, I see the image getting requested in my sub-classed NSURLCache object. It is finding the right record, and returning the data as it should. However, I'm still getting the image not found icon in my uiwebview:
So Marcus (below) suggested that I not store the image data in a core data table. So I made changes to accomodate for that:
Storing the image:
NSString* iName=[NSString stringWithFormat:#"img%lf.png",[NSDate timeIntervalSinceReferenceDate]];
NSData* iData=UIImagePNGRepresentation(theImage);
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentsDirectory = [paths objectAtIndex:0];
NSString* fullPathToFile = [documentsDirectory stringByAppendingPathComponent:iName];
[iData writeToFile:fullPathToFile atomically:NO];
Retrieving the image:
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
NSString *pathString = [[[request URL] absoluteString]lastPathComponent];
NSString* iPath = [Image pathForImage:pathString];
if (!iPath) {
return [super cachedResponseForRequest:request];
}
NSData* idata=[NSData dataWithContentsOfFile:iPath];
NSURLResponse *response =[[[NSURLResponse alloc]
initWithURL:[request URL]
MIMEType:#"image/png"
expectedContentLength:[idata length]
textEncodingName:nil]
autorelease];
NSCachedURLResponse* cachedResponse =[[[NSCachedURLResponse alloc] initWithResponse:response data:idata] autorelease];
return cachedResponse;
}
In debug mode, I see that idata does get loaded with the proper image data.
And I still get the image-not-found image! Obviously, I'm doing something wrong here. I just dont know what it is.
So... What am I doing wrong here? How can I get this to work properly?
Thank you.
I would strongly suggest that you do not store the binary data in Core Data. Storing binary data in Core Data, especially on an iOS device, causes severe performance issues with the cache.
The preferred way would be to store the actual binary data on disk in a file and have a reference to the file stored within Core Data. From there it is a simple matter to change the image url to point at the local file instead.
So it turns out I was way overthinking this. When I write the HTML, I just write the path to the image in with the image tag. Works like a charm.
I would love to know why the solution I posed in my question did not work, though.
And, I did wind up not storing the images in a table.