Image in UITableView using too much memory - objective-c

I created a class for download images from URLs for UITableViewCells (in this project I cannot use SDWebImageView or other codes from internet) but it looks like it's using a lot of memory and my tableview is not loading so fast. Can anybody point what is the problem?
Code:
//MyHelper class
+(NSString *)pathForImage:(NSString *)urlImageString{
if ([urlImageString class] == [NSNull class] || [urlImageString isEqualToString:#"<null>"] || [urlImageString isEqualToString:#""]) {
return #"";
}
NSArray *pathsInString = [urlImageString componentsSeparatedByString:#"/"];
NSString *eventCodeString = [pathsInString objectAtIndex:[pathsInString count] - 2];
NSString *imageNameString = [pathsInString lastObject];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachePath = [paths objectAtIndex:0];
cachePath = [MyHelper validateString:[cachePath stringByAppendingString:eventCodeString]];
[cachePath stringByAppendingString:#"/"];
return [cachePath stringByAppendingString:imageNameString];
}
+(BOOL)imageExistsForURL:(NSString *)urlString{
if (!([urlString class] == [NSNull class]))
{
NSString *filePath = [MyHelper pathForImage:urlString];
NSFileManager *fileManager = [NSFileManager defaultManager];
return [fileManager fileExistsAtPath:filePath];
}
return false;
}
+(void)setAsyncImage:(UIImageView *)imageView forDownloadImage:(NSString *)urlString{
CGRect activityFrame = CGRectMake(0, 0, 60, 60);
UIActivityIndicatorView *activity = [[UIActivityIndicatorView alloc] initWithFrame:activityFrame];
activity.layer.cornerRadius = activity.frame.size.width / 2;
activity.clipsToBounds = YES;
activity.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
[imageView addSubview:activity];
[activity startAnimating];
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
NSData *image;
if ([urlString class] == [NSNull class]) {
image = nil;
} else {
image = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:urlString]];
}
dispatch_async(dispatch_get_main_queue(), ^{
[activity stopAnimating];
[activity removeFromSuperview];
if (image)
{
[UIView animateWithDuration:0.9 animations:^{
imageView.alpha = 0;
imageView.image = [UIImage imageWithData:image];
imageView.alpha = 1;
}];
NSString *filePath = [MyHelper pathForImage:urlString];
NSError *error;
[image writeToFile:filePath options:NSDataWritingAtomic error:&error];
}
else
{
imageView.image = [UIImage imageNamed:#"icn_male.png"];
}
});
});
}
+(NSString *)validateString:(NSString *)string{
if (string == (id)[NSNull null] || string.length == 0 )
return #"";
return string;
}
+ (UIImage*)imageWithImage:(UIImage*)image
scaledToSize:(CGSize)newSize;
{
float proportion;
if (image.size.height > image.size.width) {
proportion = image.size.height / newSize.height;
} else {
proportion = image.size.width / newSize.width;
}
UIGraphicsBeginImageContext( newSize );
[image drawInRect:CGRectMake(newSize.width - (image.size.width/proportion),
newSize.height/2 - (image.size.height/proportion)/2,
image.size.width/proportion,
image.size.height/proportion)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
Using this code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier = #"MyCell";
MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
if ([MyHelper imageExistsForURL:photoURLString ]) {
UIImage *image = [UIImage imageWithContentsOfFile:[MyHelper pathForImage:photoURLString]];
eventImageView.image = [MyHelper imageWithImage:image scaledToSize:CGSizeMake(60, 60)];
} else {
[MyHelper setAsyncImage:eventImageView forDownloadImage:photoURLString ];
}
}

Since it is now clear that you are using oversized images, the solution is to figure out how big your images need to be in order to look good in your app.
There are several courses of action depending on how much you can change the server side portion of your system.
Use an image that is optimally sized for the highest-res case (3x) and let 2x and 1x devices do the scaling. This is again a bit wasteful.
Create some scheme whereby you will be able to get the right size image for your device type (perhaps by appending 2x, 3x etc.) to the image file name. Arguably the best choice.
Do the resizing on the client side. This can be somewhat CPU intensive and is probably the worst approach in my opinion because you will be doing a lot of work unnecessarily. However, if you can't change how your server works, then this is your only option.
Another problem with your code is that you are doing the resizing on the main/UI thread, which is blocking your UI, which is a no-no. Never perform long operations on the main thread.
You should be doing it on a background thread using dispatch_async or perhaps NSOperation and a sequential queue to reduce memory usage. Note that this can create new problems because you have to update your image view when the image is ready and consider things such as whether the cell is still visible or not. I came across a nice blog post on this a while back so I suggest searching the web.
However, if the images are really huge, then maybe you could consider setting up a proxy server and then getting resized images from there instead of the main server. Of course, you would have to consider intellectual property issues in this case.

Related

TableView doesn't show uiimage

I have an app that shows twitter account feed. So I have ImageView, textLabel and detailLabel for the content of the feed. The problem is that when all the data is loaded, the uiimage doesn't appear. When I click on the cell or scroll up-down, images are set. here is some of my code.
-(void)getImageFromUrl:(NSString*)imageUrl asynchronouslyForImageView:(UIImageView*)imageView andKey:(NSString*)key{
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:imageUrl];
__block NSData *imageData;
dispatch_sync(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
imageData =[NSData dataWithContentsOfURL:url];
if(imageData){
[self.imagesDictionary setObject:[UIImage imageWithData:imageData] forKey:key];
dispatch_sync(dispatch_get_main_queue(), ^{
imageView.image = self.imagesDictionary[key];
});
}
});
});
}
- (void)refreshTwitterHomeFeedWithCompletion {
// Request access to the Twitter accounts
ACAccountStore *accountStore = [[ACAccountStore alloc] init];
ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[accountStore requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error){
if (granted) {
NSArray *accounts = [accountStore accountsWithAccountType:accountType];
// Check if the users has setup at least one Twitter account
if (accounts.count > 0)
{
ACAccount *twitterAccount = [accounts objectAtIndex:0];
NSLog(#"request.account ...%#",twitterAccount.username);
NSURL* url = [NSURL URLWithString:#"https://api.twitter.com/1.1/statuses/home_timeline.json"];
NSDictionary* params = #{#"count" : #"50", #"screen_name" : twitterAccount.username};
SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeTwitter
requestMethod:SLRequestMethodGET
URL:url parameters:params];
request.account = twitterAccount;
[request performRequestWithHandler:^(NSData *responseData,
NSHTTPURLResponse *urlResponse, NSError *error) {
if (error)
{
NSString* errorMessage = [NSString stringWithFormat:#"There was an error reading your Twitter feed. %#",
[error localizedDescription]];
NSLog(#"%#",errorMessage);
}
else
{
NSError *jsonError;
NSArray *responseJSON = [NSJSONSerialization
JSONObjectWithData:responseData
options:NSJSONReadingAllowFragments
error:&jsonError];
if (jsonError)
{
NSString* errorMessage = [NSString stringWithFormat:#"There was an error reading your Twitter feed. %#",
[jsonError localizedDescription]];
NSLog(#"%#",errorMessage);
}
else
{
NSLog(#"Home responseJSON..%#",(NSDictionary*)responseJSON.description);
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadData:responseJSON];
});
}
}
}];
}
}
}];
}
-(void)reloadData:(NSArray*)jsonResponse
{
self.tweets = jsonResponse;
[self.tableView reloadData];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (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 self.tweets.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
SNTwitterCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell)
{
cell = [[SNTwitterCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
NSDictionary *tweetDictionary = self.tweets[indexPath.row];
NSDictionary *user = tweetDictionary[#"user"];
NSString *userName = user[#"name"];
NSString *tweetContaint = tweetDictionary[#"text"];
NSString* imageUrl = [user objectForKey:#"profile_image_url"];
[self getImageFromUrl:imageUrl asynchronouslyForImageView:cell.imageView andKey:userName];
cell.profileImage.image = [UIImage imageNamed:#"images.png"];
NSArray *days = [NSArray arrayWithObjects:#"Mon ", #"Tue ", #"Wed ", #"Thu ", #"Fri ", #"Sat ", #"Sun ", nil];
NSArray *calendarMonths = [NSArray arrayWithObjects:#"Jan", #"Feb", #"Mar",#"Apr", #"May", #"Jun", #"Jul", #"Aug", #"Sep", #"Oct", #"Nov", #"Dec", nil];
NSString *dateStr = [tweetDictionary objectForKey:#"created_at"];
for (NSString *day in days) {
if ([dateStr rangeOfString:day].location == 0) {
dateStr = [dateStr stringByReplacingOccurrencesOfString:day withString:#""];
break;
}
}
NSArray *dateArray = [dateStr componentsSeparatedByString:#" "];
NSArray *hourArray = [[dateArray objectAtIndex:2] componentsSeparatedByString:#":"];
NSDateComponents *components = [[NSDateComponents alloc] init];
NSString *aux = [dateArray objectAtIndex:0];
int month = 0;
for (NSString *m in calendarMonths) {
month++;
if ([m isEqualToString:aux]) {
break;
}
}
components.month = month;
components.day = [[dateArray objectAtIndex:1] intValue];
components.hour = [[hourArray objectAtIndex:0] intValue];
components.minute = [[hourArray objectAtIndex:1] intValue];
components.second = [[hourArray objectAtIndex:2] intValue];
components.year = [[dateArray objectAtIndex:4] intValue];
NSTimeZone *gmt = [NSTimeZone timeZoneForSecondsFromGMT:2];
[components setTimeZone:gmt];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
[calendar setTimeZone:[NSTimeZone systemTimeZone]];
NSDate *date = [calendar dateFromComponents:components];
NSString *tweetDate = [self getTimeAsString:date];
NSString *tweetValues = [NSString stringWithFormat:#"%# :%#",userName,tweetDate];
cell.textLabel.text = [NSString stringWithFormat:#"%#",tweetValues];
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#",tweetContaint];
[cell.detailTextLabel setFont:[UIFont fontWithName:#"Helvetica" size:20]];
return cell;
}
- (NSString*)getTimeAsString:(NSDate *)lastDate {
NSTimeInterval dateDiff = [[NSDate date] timeIntervalSinceDate:lastDate];
int nrSeconds = dateDiff;//components.second;
int nrMinutes = nrSeconds / 60;
int nrHours = nrSeconds / 3600;
int nrDays = dateDiff / 86400; //components.day;
NSString *time;
if (nrDays > 5){
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateStyle:NSDateFormatterShortStyle];
[dateFormat setTimeStyle:NSDateFormatterNoStyle];
time = [NSString stringWithFormat:#"%#", [dateFormat stringFromDate:lastDate]];
} else {
// days=1-5
if (nrDays > 0) {
if (nrDays == 1) {
time = #"1 day ago";
} else {
time = [NSString stringWithFormat:#"%d days ago", nrDays];
}
} else {
if (nrHours == 0) {
if (nrMinutes < 2) {
time = #"just now";
} else {
time = [NSString stringWithFormat:#"%d minutes ago", nrMinutes];
}
} else { // days=0 hours!=0
if (nrHours == 1) {
time = #"1 hour ago";
} else {
time = [NSString stringWithFormat:#"%d hours ago", nrHours];
}
}
}
}
return [NSString stringWithFormat:NSLocalizedString(#"%#", #"label"), time];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 100;
}
The fundamental problem is that the standard imageView property of the standard table view cell will automatically resize itself based upon the image that is present when cellForRowAtIndexPath finishes. But since there is no image yet when you first present the table, the cell is laid out as if there's no image. And when you asynchronously update the image view's image, it won't resize the image view.
There are a couple of ways of solving this:
Don't use the default imageView provided by UITableViewCell, but rather define your own custom cell subclass with an IBOutlet to its own UIImageView property. Make sure that this UIImageView has a fixed layout (i.e., it doesn't use the intrinsic size derived from the underlying image).
If you do that, you can asynchronously update the image property for your custom UIImageView outlet, and because the layout was not contingent upon the presence of the image, any asynchronous updates of that image should appear correctly.
When you receive the image, don't just set the image view's image property, but rather reload the whole row associated with that NSIndexPath using reloadRowsAtIndexPaths.
If you do this, the cell will be laid out correctly assuming that you retrieve the image from the cache correctly, and do so before cellForRowAtIndexPath finishes.
Note, if you do this, you will need to fix your getImageFromUrl to actually try to retrieve the image from the cache first (and do this from the main queue, before to dispatch to the background queue), or else you'll end up in an endless loop.
Having said that, there are deeper problems here.
As I mentioned above, you're caching your images, but never using the cache when retrieving the images.
You are asynchronously updating the image view.
You should initialize the image property of the UIImageView before you initiate the new asynchronous fetch, otherwise when a cell is reused, you'll see the old image there until the new image is retrieved.
What if the cell was reused in the intervening period between calling getImageFromUrl and when the asynchronous request finishes? You'll be updating the image view for the wrong cell. (This problem will be more apparent when doing this over a slow connection. Run your code using the network link conditioner to simulate slow connections and you'll see the problem I'm describing.)
What if the user rapidly scrolls down to the 100th row in the table? The network requests for the visible cells will be backlogged behind the other 99 image requests. You could even get timeout errors on slow connections.
There are a bunch of tactical little issues in getImageFromUrl.
Why dispatching synchronously from global queue to another global queue? That's unnecessary. Why dispatching UI update synchronously to main thread? That's inefficient.
Why define imageData as __block outside of the block; just define it within the block and you don't need __block qualifier.
What if you didn't receive a valid UIImage from the network request (e.g. you got a 404 error message); the existing code would crash. There are all sorts of responses the server might provide which are not a valid image, and you really must identify that situation (i.e. make sure that not only was NSData you received not nil, but also that the UIImage that you created from it was not nil, too).
I'd probably use NSCache rather than NSMutableDictionary for the cache. Also, regardless of whether you use NSCache or NSMutableDictionary, you want to make sure that you respond to memory pressure events and empty that cache if needed.
We can go through all of these individual problems, but it's a non-trivial amount of work to fix all of this. I might therefore suggest you consider the UIImageView categories of SDWebImage or AFNetworking. They take care of most of these issues, plus others. It will make your life much, much easier.

defaultRepresentation fullScreenImage on ALAsset does not return full screen image

In my application I save images to an album as assets. I want also to retrieve them and display them in full screen. I use the following code :
ALAsset *lastPicture = [scrollArray objectAtIndex:iAsset];
ALAssetRepresentation *defaultRep = [lastPicture defaultRepresentation];
UIImage *image = [UIImage imageWithCGImage:[defaultRep fullScreenImage]
scale:[defaultRep scale] orientation:
(UIImageOrientation)[defaultRep orientation]];
The problem is that the image returned is nil. I have read at the ALAssetRepresentation reference that when the image does not fit it is returned nil.
I put this image to an UIImageView which has the size of the iPad screen. I was wondering if you could help me with this issue?
Thank you in advance.
I'm not a fan of fullScreenImage or fullResolutionImage. I found that when you do this on multiple assets in a queue, even if you release the UIImage immediately, memory usage will increase dramatically while it shouldn't. Also when using fullScreenImage or fullResolutionImage, the UIImage returned is still compressed, meaning that it will be decompressed before being drawn for the first time, thus on the main thread which will block your UI.
I prefer to use this method.
-(UIImage *)fullSizeImageForAssetRepresentation:(ALAssetRepresentation *)assetRepresentation
{
UIImage *result = nil;
NSData *data = nil;
uint8_t *buffer = (uint8_t *)malloc(sizeof(uint8_t)*[assetRepresentation size]);
if (buffer != NULL) {
NSError *error = nil;
NSUInteger bytesRead = [assetRepresentation getBytes:buffer fromOffset:0 length:[assetRepresentation size] error:&error];
data = [NSData dataWithBytes:buffer length:bytesRead];
free(buffer);
}
if ([data length])
{
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceShouldAllowFloat];
[options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceCreateThumbnailFromImageAlways];
[options setObject:(id)[NSNumber numberWithFloat:640.0f] forKey:(id)kCGImageSourceThumbnailMaxPixelSize];
//[options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceCreateThumbnailWithTransform];
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
if (imageRef) {
result = [UIImage imageWithCGImage:imageRef scale:[assetRepresentation scale] orientation:(UIImageOrientation)[assetRepresentation orientation]];
CGImageRelease(imageRef);
}
if (sourceRef)
CFRelease(sourceRef);
}
return result;
}
You can use it like this:
// Get the full image in a background thread
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage* image = [self fullSizeImageForAssetRepresentation:asset.defaultRepresentation];
dispatch_async(dispatch_get_main_queue(), ^{
// Do something with the UIImage
});
});

Cacheing UIImage as NSData and getting it back

I'm trying to cache images I load from Flickr. If I load the same image, I'm hoping to use the cached version instead. For some reason, when I download an image from the internet, it works, but if I load it from my cache, it displays a blank image. I checked cacheData, and it has the same amount of bits as the image I put in, so it appears loading the file is working.
Here is how I cache images:
+ (void)cachePhoto:(NSData *)photo withKey:(NSString *)key {
if (photo) {
NSArray * urlArray = [fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask];
NSURL * targetDirectory = (NSURL *)[urlArray objectAtIndex:0];
targetDirectory = [targetDirectory URLByAppendingPathComponent:key];
[photo writeToURL:targetDirectory atomically:YES];
[cachedPhotos addObject:key];
NSLog(#"target url %#", targetDirectory);
}
}
+ (NSData *)photoInCache:(NSString *)key {
if ([cachedPhotos containsObject:key]) {
NSString * path = [[cacheDirectory URLByAppendingPathComponent:key] path];
NSLog(#"path: %#", path);
return [fileManager contentsAtPath:path];
} else {
return nil;
}
}
And my code to get it back:
NSData * cacheData = [PhotoCache photoInCache:key];
if (cacheData) {
self.imageView.image = [UIImage imageWithData:cacheData];
[spinner stopAnimating];
NSLog(#"used cached image");
} else {
dispatch_queue_t downloadQueue = dispatch_queue_create("get photo from flickr", NULL);
dispatch_async(downloadQueue, ^{
NSData * imageData = [[NSData alloc] initWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^{
[spinner stopAnimating];
self.imageView.image = [UIImage imageWithData:imageData];
[PhotoCache cachePhoto:imageData
withKey:key];
});
});
}
I figured it out - I was loading the image into the imageView in my prepareForSegue, where the ImageView was not yet loaded. I loaded the image in viewDidLoad and it worked. I also had to use UIImagePNGRepresentation, like suggested in the comments.

How Can I Save This Array of Images?

I'm very new to programming, and I jumped right into a project (I know thats not the smartest thing to do, but I'm learning as I go). The app that I'm writing has 10 UIImageViews that display a picture from the users camera roll. The code I'm using needs each of the UIImageViews to have tags. I'm currently using NSData to save the array images, and it works great, but I can't use this method anymore because NSData doesn't support the use of tags. I also can't use NSUserDefaults, because I can't save images to a plist. Here is how I'm attempting to do this (using the NSData method, which works but I have to edit this so that my tags work.)
This is my current code:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)img editingInfo:(NSDictionary *)editInfo {
if (imageView.image == nil) {
imageView.image = img;
[self.array addObject:imageView.image];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView2.image == nil) {
imageView2.image = img;
NSLog(#"The image is a %#", imageView);
[self.array addObject:imageView2.image];
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
...
- (void)applicationDidEnterBackground:(UIApplication*)application {
NSLog(#"Image on didenterbackground: %#", imageView);
[self.array addObject:imageView.image];
[self.array addObject:imageView2.image];
[self.user setObject:self.array forKey:#"images"];
[user synchronize];
}
- (void)viewDidLoad
{
self.user = [NSUserDefaults standardUserDefaults];
NSLog(#"It is %#", self.user);
self.array = [[self.user objectForKey:#"images"]mutableCopy];
imageView.image = [[self.array objectAtIndex:0] copy];
imageView2.image = [[self.array objectAtIndex:1] copy];
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:app];
[super viewDidLoad];
}
Any help or suggestions on how to edit this code so that I can save the images, while using tags is much appreciated, thanks!
EDIT: Here is my updated code:
-(IBAction)saveButtonPressed:(id)sender {
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) objectAtIndex:0];
for (UIImageView *imageView in self.array) {
NSInteger tag = self.imageView.tag;
UIImage *image = self.imageView.image;
NSString *imageName = [NSString stringWithFormat:#"Image%i.png",tag];
NSString *imagePath = [docsDir stringByAppendingPathComponent:imageName];
[UIImagePNGRepresentation(image) writeToFile:imagePath atomically:YES];
}
NSLog(#"Saved Button Pressed");
}
- (void)applicationDidEnterBackground:(UIApplication*)application {
}
-(void)viewDidLoad {
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0];
NSArray *docFiles = [[NSFileManager defaultManager]contentsOfDirectoryAtPath:docsDir error:NULL];
for (NSString *fileName in docFiles) {
if ([fileName hasSuffix:#".png"]) {
NSString *fullPath = [docsDir stringByAppendingPathComponent:fileName];
UIImage *loadedImage = [UIImage imageWithContentsOfFile:fullPath];
if (!imageView.image) {
imageView.image = loadedImage;
} else {
imageView2.image = loadedImage;
}
}
}
}
You need to use "Fast Enumeration" to parse the array's objects, and write each object to disk sequentially. First, you're going to need to add the UIImageView objects to the array instead of the UIImage property of the UIImageView, so you can recover the tag. So instead of writing
[self.array addObject:imageView.image];
It will be
[self.array addObject:imageView];
Try to follow along with my code. I inserted comments on each line to help.
-(void)applicationDidEnterBackground:(UIApplication *)application {
//Obtain the documents directory
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainmask,YES) objectAtIndex:0];
//begin fast enumeration
//this is special to ObjC: it will iterate over any array one object at a time
//it's easier than using for (i=0;i<array.count;i++)
for (UIImageView *imageView in self.array) {
//get the imageView's tag to append to the filename
NSInteger tag = imageView.tag;
//get the image from the imageView;
UIImage *image = imageView.image;
//create a filename, in this case "ImageTAGNUM.png"
NSString *imageName = [NSString stringWithFormat:#"Image%i.png",tag];
//concatenate the docsDirectory and the filename
NSString *imagePath = [docsDir stringByAppendingPathComponent:imageName];
[UIImagePNGRepresentation(image) writeToFile:imagePath atomically:YES];
}
}
To load the images from disk, you'll have to look at your viewDidLoad method
-(void)viewDidLoad {
//get the contents of the docs directory
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainmask,YES) objectAtIndex:0];
//Get the list of files from the file manager
NSArray *docFiles = [[NSFileManager defaultManager]contentsOfDirectoryAtPath:docsDir error:NULL]);
//use fast enumeration to iterate the list of files searching for .png extensions and load those
for (NSString *fileName in docFiles) {
//check to see if the file is a .png file
if ([fileName hasSuffix:#".png"]) {
NSString *fullPath = [docsDir stringByAppendingPathComponent:fileName];
UIImage *loadedImage = [UIImage imageWithContentsOfFile:fullPath];
//you'll have to sort out how to put these images in their proper place
if (!imageView1.image) {
imageView1.image = loadedImage;
} else {
imageView2.image = loadedImage;
}
}
}
}
Hope this helps
One thing you need to be aware of is that when an app enters the background it has about 5 seconds to clean up its act before it's suspended. The UIPNGRepresentation() function takes a significant amount of time and is not instantaneous. You should be aware of this. It would probably be better to write some of this code in other places and do it earlier than at app backgrounding. FWIW
You can use the [NSbundle Mainbundel] to store that images.
To get path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
First, there's still a problem in your for loop.
for (UIImageView *imageView in self.array) {
NSInteger tag = self.imageView.tag;
UIImage *image = self.imageView.image;
// ...
}
Before you make any other changes, you must understand why. imageView is your for loop control variable, which changes on each iteration through the loop. self.imageView is a different thing. It is the first of the 10 imageViews attached to your viewController. Every time this loop cycles, it looks at the first imageView, and only the first.
As for why saving doesn't work, it's probably because the arrays elsewhere aren't working. Add some logging to make sure there's something in the array, and that it has as many elements as you expect.
-(IBAction)saveButtonPressed:(id)sender {
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) objectAtIndex:0];
// Log to make sure the views expected have previously been stored.
// If the array is empty, or shorter than expected, the problem is elsewhere.
NSLog(#"Image view array before saving = %#", self.array);
for (UIImageView *imageViewToSave in self.array) {
NSInteger tag = imageViewToSave.tag;
UIImage *image = imageViewToSave.image;
NSString *imageName = [NSString stringWithFormat:#"Image%i.png",tag];
NSString *imagePath = [docsDir stringByAppendingPathComponent:imageName];
// Log the image and path being saved. If either of these are nil, nothing will be written.
NSLog(#"Saving %# to %#", image, imagePath);
[UIImagePNGRepresentation(image) writeToFile:imagePath atomically:NO];
}
NSLog(#"Save Button Pressed");
}

Array with UIImageView elements - setImageWithURL

I have such code
arrayWithImages = [[NSMutableArray alloc] init];
NSEnumerator *enumForNames = [arrayWithNames objectEnumerator];
NSEnumerator *enumForURLs = [arrayWithURLs objectEnumerator];
id objName, objURL;
while(objName = [enumForNames nextObject]) {
objURL = [enumForURLs nextObject];
UIImageView *anImage = nil;
[anImage setImageWithURL:[NSURL URLWithString:objURL]];
(...)
[arrayWithImages addObject:anImage];
}
And each time I got SIGABRT in line with "[arrayWithImages addObject:anImage];"
What's here wrong?
I don’t see an setImageWithURL method on UIImageView. Where is this from?
Is there any output from the SIGABRT crash?
Try this code:
// Download the image
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:objURL]];
// Make an UIImage out of it
UIImage *anImage = [UIImage imageWithData:imageData];
// Create an image view with the image
UIImageView *imageView = [[UIImageView alloc] initWithImage:anImage];
// Check to make sure it exists
if (imageView != nil) {
// Add it to your array
[arrayWithImages addObject:imageView];
else {
NSLog(#"Image view is nil");
}
Note that you should download the images asynchronously to avoid hanging the loop. This blog post discussing asynchronous image downloading.
Also if you know that [enumForURLs nextObject]; will return an NSString (or even better, NSURL) you should assign objURL to type NSString (or NSURL).