iOS showing Image from ALAsset in UITableView - iphone

I have a db where items are stored. These items can be different kinds of type like text, videos and images. When the View is loaded I fetch these items from the DB and I show them in a UITableView. The problem I am facing is related to show the images in the table view. Basically in the DB I store the ALAsset link related to the picture and from it I try to get its image using this code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//...Other Code
else if ([object isMemberOfClass:[Picture class]]){
//Get a reusable cell
cell = [tableView dequeueReusableCellWithIdentifier:#"pictureCellIdentifier"];
// [self performSelectorInBackground:#selector(performAsset:) withObject:dict];
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
{
ALAssetRepresentation *rep = [myasset defaultRepresentation];
CGImageRef iref = [rep fullResolutionImage];
if (iref) {
((PictureViewCell *)cell).placeHolderImageView.hidden = YES;
((PictureViewCell *)cell).pictureImageView.image = [UIImage imageWithCGImage:[rep fullResolutionImage] scale:[rep scale] orientation:(UIImageOrientation)[rep orientation]];
}
};
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror)
{
[Utility showAlertViewWithTitle:#"Location Error" message:#"You must activate Location Services to access the photo" cancelButtonTitle:#"Dismiss"];
};
ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
[assetslibrary assetForURL:[NSURL URLWithString:((Picture *)objectInArray).imagePath]
resultBlock:resultblock
failureBlock:failureblock];
//Set the background for the cell
cell.backgroundView = iv;
}
//...Other code
}
The problem is that when you slide through the cell the method gets called and the app is really slow. So I guess there are better ways to achieve what I'm trying to do. I also tried to perform that code using performselectorInBackground:. Performance seem better but It takes more time to fetch the image.
Any helps would be really appreciated.
Thanks!

There are a few things that can be improved here.
The first is as you figured out: Do the loading of the assets in background thread and then add the image to the cell when it is ready on the main thread. Next, you are creating an object of ALAssetsLibrary for every cell that you show. Ideally, an app should have just one object of ALAssetsLibrary that you retain as long as you need it. Create a ALAssetsLibrary the first time you need and then reuse it.
- (ALAssetsLibrary *)defaultAssetsLibrary {
if (_library == nil) {
_library = [[ALAssetsLibrary alloc] init];
}
return _library;
}
And the last, you are using the fullResolutionImage in a tableview cell. If you really just need to display the image, a thumbnailImage or at least a fullScreenImage should be good enough.
- (void) loadImage:(NSNumber *)indexPath url:(NSURL*)url
{
int index = [indexPath intValue];
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
{
CGImageRef iref = [[myasset defaultRepresentation] fullScreenImage];
if (iref) {
// TODO: Create a dictionary with UIImage and cell indexPath
// Show the image on main thread
[self performSelectorOnMainThread:#selector(imageReady:) withObject:result waitUntilDone:NO];
}
};
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror)
{
[Utility showAlertViewWithTitle:#"Location Error" message:#"You must activate Location Services to access the photo" cancelButtonTitle:#"Dismiss"];
};
[[self defaultAssetsLibrary] assetForURL:url
resultBlock:resultblock
failureBlock:failureblock];
}
-(void) imageReady:(NSDictionary *) result
{
// Get the cell using index path
// Show the image in the cell
}

Related

Memory issue when working with large array of UIImage

I'm storing about 100 UIImage at one single array. I know there is a memory usage issue that eventually crashes the app, specifically on older devices(iPhone 4s). In terms of User Experience storing all the UIImages on DocumentsDirectory - is not an option(takes too long). So i was thinking about "merging" this two methods. Wait until i'll receive a memory usage warning,stop saving images to my array, and then start storing over the disk. I can't find the right way to handle Memory leak/warning/usage call
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
print("memory warning")
}
When i'm testing on real device, it's just crashes - without call the method. Any suggestions?
Try to use image cache library.
The comparison of the most popular is here:
https://bpoplauschi.wordpress.com/2014/03/21/ios-image-caching-sdwebimage-vs-fastimage/
My experience: SDWebImage is the best for URL-sourced images usually from internet and Haneke is good for ID-based images for example thumbnails generated from video.
Both available in CocoaPods.
SDWebImage uses CoreData SQLite DB for URL caching. It hasn't methods for "hand make" images but worldwide popular in ~REST applications downloading images from internet. I'm using it in just published in the AppStore MyHairDressers app. FastCache uses files for URL caching. But it also like SDWebImage not suited to cache "hand make" images. Both well suited for images downloaded by URLs. Haneke can store images by custom IDs not only by URLs. But like FastCache it requires some configuration. Here is code for some configurations:
``
HNKCacheFormat *cacheFormatThumbnail = [[HNKCache sharedCache] formats][CACHE_FORMAT_THUMBNAIL];
if (cacheFormatThumbnail == nil)
{
cacheFormatThumbnail = [[HNKCacheFormat alloc] initWithName:CACHE_FORMAT_THUMBNAIL];
cacheFormatThumbnail.size = CGSizeMake(100.0f, 56.0f);
cacheFormatThumbnail.scaleMode = HNKScaleModeAspectFit;
cacheFormatThumbnail.compressionQuality = 0.5f;
cacheFormatThumbnail.diskCapacity = 10 * 1024 * 1024; // 10MB
cacheFormatThumbnail.preloadPolicy = HNKPreloadPolicyLastSession;
[[HNKCache sharedCache] registerFormat:cacheFormatThumbnail];
}
HNKCacheFormat *cacheFormatPhoto = [[HNKCache sharedCache] formats][CACHE_FORMAT_PHOTO];
if (cacheFormatPhoto == nil)
{
cacheFormatPhoto = [[HNKCacheFormat alloc] initWithName:CACHE_FORMAT_PHOTO];
CGFloat scale = [[UIScreen mainScreen] scale];
cacheFormatPhoto.size = CGSizeMake(1280.0f * scale, 720.0f * scale);
cacheFormatPhoto.scaleMode = HNKScaleModeAspectFit;
cacheFormatPhoto.compressionQuality = 0.5f;
cacheFormatPhoto.diskCapacity = 50 * 1024 * 1024; // 50MB
cacheFormatPhoto.preloadPolicy = HNKPreloadPolicyNone;
[[HNKCache sharedCache] registerFormat:cacheFormatPhoto];
}
``
and here is example for creating cached images (TableViewCell contains CollectionView with thumbnails):
``
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
VideoCell *cell = (VideoCell *)[super tableView:tableView cellForRowAtIndexPath:indexPath];
VideoAsset *asset = (VideoAsset *)[self.fetchedResultsController objectAtIndexPath:indexPath];
if ([asset thumbnails] == 0)
{
MBProgressHUD *hud = [[MBProgressHUD alloc] initWithView:[cell thumbnails]];
hud.removeFromSuperViewOnHide = YES;
[[cell thumbnails] addSubview:hud];
hud.labelText = NSLocalizedString(#"H11",nil);
[hud show:YES];
CGFloat scale = [[UIScreen mainScreen] scale];
CGSize size = CGSizeMake(100.0f * scale, 56.0f *scale);
__weak typeof(cell) weakCell = cell;
[asset generateThumbnails:self->thumbnailsCount offset:self->thumbnailsOffset size:size completion:^(NSArray *thumbnails) {
dispatch_async(dispatch_get_main_queue(), ^{
[hud hide:YES];
});
if ((thumbnails != nil) && ([thumbnails count] > 0))
{
HNKCache *cache = [HNKCache sharedCache];
NSUInteger n = 0;
NSUInteger keyHash = [[[asset assetURL] absoluteString] hash];
for (UIImage *image in thumbnails)
{
[cache setImage:image forKey:[NSString stringWithFormat:#"%lu#%i",(unsigned long)keyHash,(int)(n++)] formatName:CACHE_FORMAT_THUMBNAIL];
dispatch_async(dispatch_get_main_queue(), ^{
if (weakCell != nil)
{
__strong typeof(cell) strongCell = weakCell;
[[strongCell thumbnails] reloadData];
}
});
formatName:CACHE_FORMAT_PHOTO];
}
}
}];
}
return (UITableViewCell *)cell;
}
``
and using (collection view cell of the collection in the table view cell):
``
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
ThumbnailCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
NSString *key = [NSString stringWithFormat:#"%lu#%i",(unsigned long)[[[(VideoAsset *)self->_selectedObject assetURL] absoluteString] hash],(int)[indexPath item]];
[cell setKey:key];
[cell setTag:[indexPath item]];
__weak typeof(cell) weakCell = cell;
[[HNKCache sharedCache] fetchImageForKey:key formatName:CACHE_FORMAT_THUMBNAIL success:^(UIImage *image) {
[[weakCell image] setImage:image];
} failure:^(NSError *error) {
if ([[error domain] isEqualToString:HNKErrorDomain] && ([error code] == HNKErrorImageNotFound))
{
[[weakCell image] setImage:[UIImage imageNamed:#"movieplaceholder"]];
}
else [error reportError];
}];
return cell;
}
``

NSBlockOperation or NSOperation with ALAsset Block to display photo-library images using ALAsset URL

I am asking this question regarding my questions Display photolibrary images in an effectual way iPhone and Highly efficient UITableView "cellForRowIndexPath" method to bind the PhotoLibrary images.
So I would like to request that answers are not duplicated to this one without reading the below details :)
Let's come to the issue,
I have researched detailed about my above mentioned issue, and I have found the document about operation queues from here.
So I have created one sample application to display seven photo-library images using operation queues through ALAsset blocks.
Here are the sample application details.
Step 1:
In the NSOperationalQueueViewController viewDidLoad method, I have retrieved all the photo-gallery ALAsset URLs in to an array named urlArray.
Step 2:
After all the URLs are added to the urlArray, the if(group != nil) condition will be false in assetGroupEnumerator, so I have created a NSOperationQueue, and then created seven UIImageView's through a for loop and created my NSOperation subclass object with the corresponding image-view and URL for each one and added them in to the NSOperationQueue.
See my NSOperation subclass here.
See my implementation (VierwController) class here.
Let's come to the issue.
It not displaying all the seven images consistently. Some of the images are missing. The missing order is changing multiple times (one time it doesn't display the sixth and seventh, and another time it doesn't display only the second and third). The console log displays Could not find photo pic number. However, the URLs are logged properly.
You can see the log details here.
Are there any mistakes in my classes?
Also, when I go through the above mentioned operational queue documentation, I have read about NSBlockOperation. Do I need to implement NSBlockOperation instead of NSOperation while dealing with ALAsset blocks?
The NSBlockOperation description says
A class you use as-is to execute one or more block objects
concurrently. Because it can execute more than one block, a block
operation object operates using a group semantic; only when all of the
associated blocks have finished executing is the operation itself
considered finished.
How can I implement the NSBlockOperation with ALAsset block regarding my sample application?
I have gone through Stack Overflow question Learning NSBlockOperation. However, I didn't get any idea to implement the NSBlockOperation with ALAsset block!!
This is the tutorial about "How to access all images from iPhonePhoto Library using ALAsset Library and show them on UIScrollView like iPhoneSimulator" .
First of all add AssetsLibrary.framework to your project.
Then in your viewController.h file import #import <AssetsLibrary/AssetsLibrary.h> header file.
This is your viewController.h file
#import <UIKit/UIKit.h>
#import <AssetsLibrary/AssetsLibrary.h>
#import "AppDelegate.h"
#interface ViewController : UIViewController <UIScrollViewDelegate>
{
ALAssetsLibrary *assetsLibrary;
NSMutableArray *groups;
ALAssetsGroup *assetsGroup;
// I will show all images on `UIScrollView`
UIScrollView *myScrollView;
UIActivityIndicatorView *activityIndicator;
NSMutableArray *assetsArray;
// Will handle thumbnail of images
NSMutableArray *imageThumbnailArray;
// Will handle original images
NSMutableArray *imageOriginalArray;
UIButton *buttonImage;
}
-(void)displayImages;
-(void)loadScrollView;
#end
And this is your viewController.m file -
viewWillAppear:
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#implementation ViewController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
assetsArray = [[NSMutableArray alloc]init];
imageThumbnailArray = [[NSMutableArray alloc]init];
imageOriginalArray = [[NSMutableArray alloc]init];
myScrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0.0, 0.0, 320.0, 416.0)];
myScrollView.delegate = self;
myScrollView.contentSize = CGSizeMake(320.0, 416.0);
myScrollView.backgroundColor = [UIColor whiteColor];
activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.center = myScrollView.center;
[myScrollView addSubview:activityIndicator];
[self.view addSubview:myScrollView];
[activityIndicator startAnimating];
}
viewDidAppear:
-(void)viewDidAppear:(BOOL)animated
{
if (!assetsLibrary) {
assetsLibrary = [[ALAssetsLibrary alloc] init];
}
if (!groups) {
groups = [[NSMutableArray alloc] init];
}
else {
[groups removeAllObjects];
}
ALAssetsLibraryGroupsEnumerationResultsBlock listGroupBlock = ^(ALAssetsGroup *group, BOOL *stop) {
//NSLog(#"group %#",group);
if (group) {
[groups addObject:group];
//NSLog(#"groups %#",groups);
} else {
//Call display Images method here.
[self displayImages];
}
};
ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError *error) {
NSString *errorMessage = nil;
switch ([error code]) {
case ALAssetsLibraryAccessUserDeniedError:
case ALAssetsLibraryAccessGloballyDeniedError:
errorMessage = #"The user has declined access to it.";
break;
default:
errorMessage = #"Reason unknown.";
break;
}
};
[assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:listGroupBlock failureBlock:failureBlock];
}
And this is displayImages: method body
-(void)displayImages
{
// NSLog(#"groups %d",[groups count]);
for (int i = 0 ; i< [groups count]; i++) {
assetsGroup = [groups objectAtIndex:i];
if (!assetsArray) {
assetsArray = [[NSMutableArray alloc] init];
}
else {
[assetsArray removeAllObjects];
}
ALAssetsGroupEnumerationResultsBlock assetsEnumerationBlock = ^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result) {
[assetsArray addObject:result];
}
};
ALAssetsFilter *onlyPhotosFilter = [ALAssetsFilter allPhotos];
[assetsGroup setAssetsFilter:onlyPhotosFilter];
[assetsGroup enumerateAssetsUsingBlock:assetsEnumerationBlock];
}
//Seprate the thumbnail and original images
for(int i=0;i<[assetsArray count]; i++)
{
ALAsset *asset = [assetsArray objectAtIndex:i];
CGImageRef thumbnailImageRef = [asset thumbnail];
UIImage *thumbnail = [UIImage imageWithCGImage:thumbnailImageRef];
[imageThumbnailArray addObject:thumbnail];
ALAssetRepresentation *representation = [asset defaultRepresentation];
CGImageRef originalImage = [representation fullResolutionImage];
UIImage *original = [UIImage imageWithCGImage:originalImage];
[imageOriginalArray addObject:original];
}
[self loadScrollView];
}
Now you have two array one is imageThumbnailArray and another is imageOriginalArray.
Use imageThumbnailArray for showing on UIScrollView for which your scrolling will not be slow.... And use imageOriginalArray for an enlarged preview of image.
'loadScrollView:' method, This is how to images on UIScrollView like iPhoneSimulator
#pragma mark - LoadImages on UIScrollView
-(void)loadScrollView
{
float horizontal = 8.0;
float vertical = 8.0;
for(int i=0; i<[imageThumbnailArray count]; i++)
{
if((i%4) == 0 && i!=0)
{
horizontal = 8.0;
vertical = vertical + 70.0 + 8.0;
}
buttonImage = [UIButton buttonWithType:UIButtonTypeCustom];
[buttonImage setFrame:CGRectMake(horizontal, vertical, 70.0, 70.0)];
[buttonImage setTag:i];
[ buttonImage setImage:[imageThumbnailArray objectAtIndex:i] forState:UIControlStateNormal];
[buttonImage addTarget:self action:#selector(buttonImagePressed:) forControlEvents:UIControlEventTouchUpInside];
[myScrollView addSubview:buttonImage];
horizontal = horizontal + 70.0 + 8.0;
}
[myScrollView setContentSize:CGSizeMake(320.0, vertical + 78.0)];
[activityIndicator stopAnimating];
[activityIndicator removeFromSuperview];
}
And here you can find which image button has been clicked -
#pragma mark - Button Pressed method
-(void)buttonImagePressed:(id)sender
{
NSLog(#"you have pressed : %d button",[sender tag]);
}
Hope this tutorial will help you and many users who search for the same.. Thank you!
You have a line in your DisplayImages NSOperation subclass where you update the UI (DisplayImages.m line 54):
self.imageView.image = topicImage;
This operation queue is running on a background thread, and we know that you should only update the state of the UI on the main thread. Since updating the view of an image view is definitely updating the UI, this can be simply fixed by wrapping the call with:
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = topicImage;
});
This puts an asynchronous call on the main queue to update the UIImageView with the image. It's asynchronous so your other tasks can be scheduled in the background, and it's safe as it is running on the main queue - which is the main thread.

Image storage in iOS devices, the appropriate behavior?

I'm kinda puzzeled about image storage in iOS devices for an app i'm making.
My requirement is to load an Image onto a tableViewCell, lets say in the default Image space of a UITableViewCell and hence isnt a background image.
Now The user can either add an Image either via the PhotoDirectory or take an entirely new image.
If a new image is taken, where should that image be stored preferebly? In the default photo directory ? or in the documents folder of the app sandbox?
Because these are image files, I'm afraid that store images within the app bundle can make it pretty big, I'm afraid I dont wana cross the size limit.
Performance wise though... what would be a better option?
I have an app that also does some of the things you describe. My solutions was to create a singleton that I call my imageStore. You can find information about a singleton here
In this imageStore, I store all my "full size" images; however, like you I am concerned about the size of these images, so instead of using them directly, I use thumbnails. What I do is this. For each object that I want to represent in the table, I make sure the object has a UIImage defined that is about thumnail size (64x64 or any size you desire). Then an object is created, I create a thumbnail that I store along with the object. I use this thumbnail instead of the larger images where I can get a way with it, like on a table cell.
I'm not behind my Mac at the moment, but if you want I can post some code later to demonstrate both the singleton and the creation and usage of the thumbnail.
Here is my header file for the ImageStore
#import <Foundation/Foundation.h>
#interface BPImageStore : NSObject {
NSMutableDictionary *dictionary;
}
+ (BPImageStore *)defaultImageStore;
- (void)setImage:(UIImage *)i forKey:(NSString *)s;
- (UIImage *)imageForKey:(NSString *)s;
- (void)deleteImageForKey:(NSString *)s;
#end
Here is the ImageStore.m file - my Singleton
#import "BPImageStore.h"
static BPImageStore *defaultImageStore = nil;
#implementation BPImageStore
+ (id)allocWithZone:(NSZone *)zone {
return [[self defaultImageStore] retain];
}
+ (BPImageStore *)defaultImageStore {
if(!defaultImageStore) {
defaultImageStore = [[super allocWithZone:NULL] init];
}
return defaultImageStore;
}
- (id)init
{
if(defaultImageStore) {
return defaultImageStore;
}
self = [super init];
if (self) {
dictionary = [[NSMutableDictionary alloc] init];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(clearCach:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
return self;
}
- (void) clearCache:(NSNotification *)note {
[dictionary removeAllObjects];
}
- (oneway void) release {
// no op
}
- (id)retain {
return self;
}
- (NSUInteger)retainCount {
return NSUIntegerMax;
}
- (void)setImage:(UIImage *)i forKey:(NSString *)s {
[dictionary setObject:i forKey:s];
// Create full path for image
NSString *imagePath = pathInDocumentDirectory(s);
// Turn image into JPEG data
NSData *d = UIImageJPEGRepresentation(i, 0.5);
// Write it to full path
[d writeToFile:imagePath atomically:YES];
}
- (UIImage *)imageForKey:(NSString *)s {
// if possible, get it from the dictionary
UIImage *result = [dictionary objectForKey:s];
if(!result) {
// Create UIImage object from file
result = [UIImage imageWithContentsOfFile:pathInDocumentDirectory(s)];
if (result)
[dictionary setObject:result forKey:s];
}
return result;
}
- (void)deleteImageForKey:(NSString *)s {
if(!s) {
return;
}
[dictionary removeObjectForKey:s];
NSString *path = pathInDocumentDirectory(s);
[[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
}
#end
Here is where I use the image store. In my Object "player", I have a UIImage to store the thumbnail and I have an NSString to house a key that I create. Each original image I put into the store has a key. I store the key with my Player. If I ever need the original image, I get by the unique key. It is also worth noting here that I don't even store the original image at full size, I cut it down a bit already. After all in my case, it is a picture of a player and nobody has too look so good as to have a full resolution picture :)
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSString *oldKey = [player imageKey];
// did the player already have an image?
if(oldKey) {
// delete the old image
[[BPImageStore defaultImageStore] deleteImageForKey:oldKey];
}
UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
// Create a CFUUID object it knows how to create unique identifier
CFUUIDRef newUniqueID = CFUUIDCreate(kCFAllocatorDefault);
// Create a string from unique identifier
CFStringRef newUniqueIDString = CFUUIDCreateString(kCFAllocatorDefault, newUniqueID);
// Use that unique ID to set our player imageKey
[player setImageKey:(NSString *)newUniqueIDString];
// we used Create in the functions to make objects, we need to release them
CFRelease(newUniqueIDString);
CFRelease(newUniqueID);
//Scale the images down a bit
UIImage *smallImage = [self scaleImage:image toSize:CGSizeMake(160.0,240.0)];
// Store image in the imageStore with this key
[[BPImageStore defaultImageStore] setImage:smallImage
forKey:[player imageKey]];
// Put that image onto the screen in our image view
[playerView setImage:smallImage];
[player setThumbnailDataFromImage:smallImage];
}
Here is an example of going back to get the original image from the imageStore:
// Go get image
NSString *imageKey = [player imageKey];
if (imageKey) {
// Get image for image key from image store
UIImage *imageToDisplay = [[BPImageStore defaultImageStore] imageForKey:imageKey];
[playerView setImage:imageToDisplay];
} else {
[playerView setImage:nil];
}
Finally, here is how I create a thumbnail from the original image:
- (void)setThumbnailDataFromImage:(UIImage *)image {
CGSize origImageSize = [image size];
CGRect newRect;
newRect.origin = CGPointZero;
newRect.size = [[self class] thumbnailSize]; // just give a size you want here instead
// How do we scale the image
float ratio = MAX(newRect.size.width/origImageSize.width, newRect.size.height/origImageSize.height);
// Create a bitmap image context
UIGraphicsBeginImageContext(newRect.size);
// Round the corners
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:newRect cornerRadius:5.0];
[path addClip];
// Into what rectangle shall I composite the image
CGRect projectRect;
projectRect.size.width = ratio * origImageSize.width;
projectRect.size.height = ratio *origImageSize.height;
projectRect.origin.x = (newRect.size.width - projectRect.size.width) / 2.0;
projectRect.origin.y = (newRect.size.height - projectRect.size.height) / 2.0;
// Draw the image on it
[image drawInRect:projectRect];
// Get the image from the image context, retain it as our thumbnail
UIImage *small = UIGraphicsGetImageFromCurrentImageContext();
[self setThumbnail:small];
// Get the image as a PNG data
NSData *data = UIImagePNGRepresentation(small);
[self setThumbnailData:data];
// Cleanup image context resources
UIGraphicsEndImageContext();
}

Preload an additional image using Apple's PhotoScroller example

I am trying to modify Apple's PhotoScroller example and I encountered a problem that I couldn't solve.
Basically, the PhotoScroller originally loaded a bunch of image in an array locally. I try to modify this and change to it request an image file dynamically from an URL. Once the user scroll to the next page, it will fetch the next image from a new URL.
In order to improve the performance, I wanted to preload the next page so user doesn't need to wait for the image being downloaded while scrolling to the next page. Once the next page is on current page, the page after that will be loaded and so on...
I'm not quite sure how I can achieve this and I hope someone can show me what to do.
Here is my custom code: (Please refer the full code from Apple's PhotoScroller example)
tilePage method: (It will be call at the beginning and every time when user did scroll the scrollView)
- (void)tilePages
{
// Calculate which pages are visible
CGRect visibleBounds = pagingScrollView.bounds;
int firstNeededPageIndex = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
int lastNeededPageIndex = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
firstNeededPageIndex = MAX(firstNeededPageIndex, 0);
lastNeededPageIndex = MIN(lastNeededPageIndex, [self imageCount] - 1);
// Recycle no-longer-visible pages
for (ImageScrollView *page in visiblePages) {
if (page.index < firstNeededPageIndex || page.index > lastNeededPageIndex) {
[recycledPages addObject:page];
[page removeFromSuperview];
}
}
[visiblePages minusSet:recycledPages];
// add missing pages
for (int index = firstNeededPageIndex; index <= lastNeededPageIndex; index++) {
if (![self isDisplayingPageForIndex:index]) {
ImageScrollView *page = [self dequeueRecycledPage];
//ImageScrollView *nextpage = [self dequeueRecycledPage];
if (page == nil) {
page = [[[ImageScrollView alloc] init] autorelease];
}
[self configurePage:page forIndex:index];
[pagingScrollView addSubview:page];
[visiblePages addObject:page];
}
}
}
To configure page index and content:
- (void)configurePage:(ImageScrollView *)page forIndex:(NSUInteger)index
{
//set page index
page.index = index;
//set page frame
page.frame = [self frameForPageAtIndex:index];
//Actual method to call image to display
[page displayImage:[self imageAtIndex:index]];
NSLog(#"index: %i", index);
}
To fetch image from URL:
- (UIImage *)imageAtIndex:(NSUInteger)index {
NSString *string1 = [NSString stringWithString:#"http://abc.com/00"];
NSString *string2 = [NSString stringWithFormat:#"%d",index+1];
NSString *string3 = [NSString stringWithString:#".jpg"];
NSString *finalString = [NSString stringWithFormat:#"%#%#%#",string1,string2,string3];
NSLog(#"final string is: %#", finalString);
NSURL *imgURL = [NSURL URLWithString: finalString];
NSData *imgData = [NSData dataWithContentsOfURL:imgURL];
return [UIImage imageWithData:imgData];
}
Thanks for helping!
Lawrence
You can use concurrent operations to fetch images one after another. Using NSOperations, you can set a dependency chain so images are loaded in a serial fashion in the background, or you can have them all downloaded concurrently.
The problem with large images is that even though you save them in the file system, there is a "startup" time to get the images rendered. Also, in PhotoScroller, Apple "cheats" by having all the images pre tiled for each level of detail. Apple provides no way to render just a Plain ole JPEG in PhotoScroller, so unless you can pre tile it will be of no use to you.
If you are curious, you can see how to both download multiple images and pre tile them into a temporary file by perusing the PhotoScrollerNetwork project on github.

How to check if a view is valid iphone

I am having trouble with my UITableView in the NavigationController.
When I add data to the table I use another class to download images to display in that table, while all that works great but if in the middle of images being download I swtich back to the previous view in the navigationcontroller app crashed.
Here is my code to explain further
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Set appIcon and clear temporary data/image
UIImage *image = [[UIImage alloc] initWithData:self.activeDownload];
UIImage *appIcon;
if (image.size.width != kAppIconHeight && image.size.height != kAppIconHeight)
{
CGSize itemSize = CGSizeMake(125, 85);
UIGraphicsBeginImageContext(itemSize);
CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
[image drawInRect:imageRect];
appIcon = UIGraphicsGetImageFromCurrentImageContext();
///UIGraphicsEndImageContext();
}
else
{
appIcon = image;
//self.appRecord.appIcon = image;
}
self.activeDownload = nil;
// Release the connection now that it's finished
self.imageConnection = nil;
// call our delegate and tell it that our icon is ready for display
if(delegate != nil)
[delegate appImageDidLoad:self.indexPathInTableView imaged:appIcon ];
[image release];
}
The appImageDidLoad is a method that exists in my UITableView view.
Is there a way I can check to see if the UITableView is valid in my imagedownload class so I know not to send the image.
Thanks in advance.
The crash is due to delegate getting release by the time the image was ready!
Try this in ViewWillDisappear
// terminate all pending download connections
NSArray *allDownloads = [self.imageDownloadsInProgress allValues];
[allDownloads performSelector:#selector(cancelDownload)];
This is the solution for this crash.
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
// terminate all pending download connections
NSArray *allDownloads = [self.imageDownloadsInProgress1 allValues];
[allDownloads makeObjectsPerformSelector:#selector(cancelDownload)];
}