I have built an iOS app using Parse.com.
In my app i have displayed a lot of data from network.
To load the data speedily i used cache kPFCachePolicyCacheThenNetwork. It loads data fine but while scrolling images are takes some amount of time to load.
First white image and then image loads.
Is there any solution like cache to display images in imageviews with out interruption.
Thanks.
You can use SDWebImage for doing this.
Also you can use NSCache for caching purpose.
Yes you can use AFNetworking UIImageview category and you are sorted
you can also disable caching of a particular image usually needed if you edit an existing image and then dwnload it from same URL, by manipulagting its
-(void)setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage
as mentioned in this blog
An other option could be ASImageView with facility to refresh cache.
I write the code with the help of gcd , you just have to pass the object of imageView and pass the url , it manages all the things, like cache.
-(void)downloadingServerImageFromUrl:(UIImageView*)imgView AndUrl:(NSString*)strUrl{
strUrl = [strUrl encodeUrl];
NSString* theFileName = [NSString stringWithFormat:#"%#.png",[[strUrl lastPathComponent] stringByDeletingPathExtension]];
NSFileManager *fileManager =[NSFileManager defaultManager];
NSString *fileName = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"tmp/%#",theFileName]];
imgView.backgroundColor = [UIColor darkGrayColor];
UIActivityIndicatorView *actView = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[imgView addSubview:actView];
[actView startAnimating];
CGSize boundsSize = imgView.bounds.size;
CGRect frameToCenter = actView.frame;
// center horizontally
if (frameToCenter.size.width < boundsSize.width)
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
else
frameToCenter.origin.x = 0;
// center vertically
if (frameToCenter.size.height < boundsSize.height)
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
else
frameToCenter.origin.y = 0;
actView.frame = frameToCenter;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSData *dataFromFile = nil;
NSData *dataFromUrl = nil;
dataFromFile = [fileManager contentsAtPath:fileName];
if(dataFromFile==nil){
dataFromUrl=[[[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:strUrl]] autorelease];
}
dispatch_sync(dispatch_get_main_queue(), ^{
if(dataFromFile!=nil){
imgView.image = [UIImage imageWithData:dataFromFile];
}else if(dataFromUrl!=nil){
imgView.image = [UIImage imageWithData:dataFromUrl];
NSString *fileName = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"tmp/%#",theFileName]];
BOOL filecreationSuccess = [fileManager createFileAtPath:fileName contents:dataFromUrl attributes:nil];
if(filecreationSuccess == NO){
NSLog(#"Failed to create the html file");
}
}else{
imgView.image = [UIImage imageNamed:#"NO_Image.png"];
}
[actView removeFromSuperview];
[actView release];
[imgView setBackgroundColor:[UIColor clearColor]];
});
});
}
Here is the code for loading image in background . check this Code
you should use EGOImageLoading(EGOImageLoading) for lazy loading of images and can save images using NSUserDefaults.
It is best way i advice as i have used it in many projects.
Sorry to say it, but Parse is already doing all these things. Parse uses AFNetworking to get the images, puts them in an NSCache, and uses disk caching very aggressively so that redundant network trips are completely avoided (the version is part of the URL, so Parse doesn't even need to use an if-modified-since GET).
The biggest issue you might be encountering is that PFFile throttles downloads to only download 3 images at a time. After experimentation this was the best number because it ensured incremental feedback to the user and avoided a nasty issue where some iOS devices would get very poor performance after too many threads. The best advice I could offer you would be to prefetch some images if possible. As mentioned earlier, PFFile images are very aggressively cached. The kPFCachePolicy... stuff only affects queries which might grow stale; the images those queries point to are always cached (though only initially downloaded on demand).
Related
I have an application which donwloads several images and stores them on the phone. In total it will probably required around 20 images tops. I need to be able to retrieve any of these images at will depending on what screen the user is on. These images will be stored indefinitely, so I don't want to use temp directory.
At present I have a class named Images with these methods
- (void) cacheImage: (NSString *) ImageURLString : (NSString *)imageName
{
NSURL *ImageURL = [NSURL URLWithString: ImageURLString];
// Generate a unique path to a resource representing the image you want
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex: 0];
NSString *docFile = [docDir stringByAppendingPathComponent: imageName];
// Check for file existence
if(![[NSFileManager defaultManager] fileExistsAtPath: docFile])
{
// The file doesn't exist, we should get a copy of it
// Fetch image
NSData *data = [[NSData alloc] initWithContentsOfURL: ImageURL];
UIImage *image = [[UIImage alloc] initWithData: data];
// Is it PNG or JPG/JPEG?
// Running the image representation function writes the data from the image to a file
if([ImageURLString rangeOfString: #".png" options: NSCaseInsensitiveSearch].location != NSNotFound)
{
[UIImagePNGRepresentation(image) writeToFile: docFile atomically: YES];
}
else if([ImageURLString rangeOfString: #".jpg" options: NSCaseInsensitiveSearch].location != NSNotFound ||
[ImageURLString rangeOfString: #".jpeg" options: NSCaseInsensitiveSearch].location != NSNotFound)
{
[UIImageJPEGRepresentation(image, 100) writeToFile: docFile atomically: YES];
}
}
}
- (UIImage *) getCachedImage : (NSString *)imageName
{
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* cachedPath = [documentsDirectory stringByAppendingPathComponent:imageName];
UIImage *image;
// Check for a cached version
if([[NSFileManager defaultManager] fileExistsAtPath: cachedPath])
{
image = [UIImage imageWithContentsOfFile: cachedPath]; // this is the cached image
}
else
{
NSLog(#"Error getting image %#", imageName);
}
return image;
}
-(void)getImages
{
//example
NSString *image1URL = #"http://test/image1.png";
NSString *image2URL = #"http://test/image2.png";
NSString *image3URL = #"http://test/image3.png";
[self cacheImage:sLogo: #"Image1"];
[self cacheImage:sBlankNav: #"Image2"];
[self cacheImage:buttonLarge :#"Image3"];
}
-(void) storeImages
{
image1 = [self getCachedImage:#"Image1"];
image2 = [self getCachedImage:#"Image2"];
image3 = [self getCachedImage:#"Image3"];
}
So I use the code like this
Images *cache = [[Images alloc]init];
[cache storeImages];
The get images method is called once when the app first starts to get the images, it isn't called again after that, unless the images on the server are updated and I need to retrieve the updated ones.
The code works, but the problem is when I navigate to a screen that uses it, there is a very slight delay before the screen loads as it is loading the images.
My application is a tabbed application, so it begins on tab 1, I click tab 2 which implements the code, there will be a slight pause the first time it loads. It doesn't last very long, but it is noticeable and is very annoying. After that it is fine, as it is already loaded. However with navigation controller, every time you move from the first VC to the second VC, the method will be called again, so each time you navigate the delay will be there.
The images are not very big, biggest one is 68kb, others are much smaller than that. At present I am just testing with 5 images. Is there a more efficient way of storing and retrieving images, or am I doing something wrong with my code? I need to be able to retrieve these images without any noticeable delay in order for my application to remain fluid and not jerky or clunky.
Thanks in advance!!
You have two options to do the image loading work on a background thread - use Grand Central Dispatch or NSInvocationOperation. GCD might be considered the cleaner of the two:
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(q, ^{
//load images here
dispatch_async(main, ^{
// show on main thread here
});
});
you have delay because you're downloading data synchronously
// NSData *data = [[NSData alloc] initWithContentsOfURL: ImageURL];
Try some smart library like SDWebImage:
it lets you download image asynchronously while you still can display a local image (a proxy image). By the way, you still get cache image for free. So even if u are on local, you can still catch previously downloaded images
https://github.com/rs/SDWebImage
A must have
the screen image link,the savePic image link,You can find the different between screenImg and savePic.
I set the backgroundImageView a very small png. and i want save the backgroundimageview to a Picture。
In the iPhone Simulator Screen, the edge of image is normal, but the edge of savePic is not clear. Anybody can tell me how to save a high definition picture.
- (void)viewDidLoad
{
[super viewDidLoad];
//_backImgView 320*480
self.backImgView.image = [UIImage imageNamed:#"Purple.png"];
[self performSelector:#selector(savePic) withObject:nil afterDelay:0.1];
}
- (void)savePic
{
BOOL isDir;
NSString *dirPath = [NSHomeDirectory() stringByAppendingPathComponent:#"/Documents/Pic"];
NSFileManager *fm = [NSFileManager defaultManager];
if(![fm fileExistsAtPath:dirPath isDirectory:&isDir]){
BOOL bo = [fm createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
if(!bo){
NSLog(#"-->Create CardSlide Dir Fail!!!");
return;
}
}
UIGraphicsBeginImageContext(_backImgView.bounds.size);
//UIGraphicsBeginImageContextWithOptions(_backImgView.bounds.size, _backImgView.opaque, 2.0);
CGContextSetAllowsAntialiasing(UIGraphicsGetCurrentContext(), YES);
[_backImgView drawRect:_backImgView.bounds];
//[_backImgView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *bgImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSString *path = [NSString stringWithFormat:#"save_%#.jpg", #"demo"];
NSString *jpgPath = [dirPath stringByAppendingPathComponent:path];
BOOL isSucc = [UIImageJPEGRepresentation(bgImg, 1.0) writeToFile:jpgPath atomically:YES];
NSLog(#"%# write photo %#!", jpgPath, isSucc?#"SUCC":#"FAIL");
}
Using a small image, is impossible to scale it the correct way to a bigger image, you will always go through blurry, noisy images BUT if you image could have stretchable repetitive parts, you can stretch the interior of the image without creating a new one. There are two API in UIImage called:
– resizableImageWithCapInsets: (Only ios5)
– stretchableImageWithLeftCapWidth:topCapHeight: (Deprecated in iOS 5.0)
If you find the correct inset you can avoid to create a new image, basically is the same system used to create the bubble in message application. The balloon is really small but it has a stretchable area that maintain the aspect ratio.
Since the image is really simple you could also think to draw it using Quartz functions creating a rect path, paths are "almost" res independent just need to scale them the correct size.
I am new to this and have searched a lot this week. I am trying to set a background image from a file I have downloaded from a url. I need to save the image for later. Specially if I am not connected to the internet I can still show this image.
I have validated by printing out the contents of the directory that the file exists. This is one of many code that I have tried to make the image appear in the background.
nNSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *documentPath = [docDir stringByAppendingFormat:#"/mainback.png"];
// If you go to the folder below, you will find those pictures
NSLog(#"%#",documentPath);
if ([[NSFileManager defaultManager]fileExistsAtPath:documentPath]) {
NSData *data = [NSData dataWithContentsOfFile:documentPath];
UIImage *image = [[UIImage alloc] initWithData:data];
NSLog(#"WHAT - %f,%f",image.size.width,image.size.height);
UIColor *background = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:documentPath]];
self.view.backgroundColor = background;
[background release];
}
The code finds the image in the directory but I cannot set the image to the background. Please help.
Thanks
Use a UIImageView (Apple documentation linked) instead of doing what you are trying to do with UIColor.
It'll be a lot more straightforward and less problematic.
I am loading images in UIWebview using the following code. For each image (while scrolling my scrollview) a function is called to load the image. The problem is that when I am continuously scrolling up to 30 images the application crashes. What may be the reason? My images are 1300 by 1200 pixels. Please help me find a solution.
- (void) loadCatalogImage
{
#try{
// if(imgView.image != nil)
// return;
// [global_imgProgress startAnimating];
//NSLog(#"image loading at = %#, %d", baseURL, 2 + pageNo);
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
//NSArray *array = [global_ContentString componentsSeparatedByString:#"###"];
NSArray *array1 = [catalogURL componentsSeparatedByString:#"&"];
//NSLog(#"baseURL = %#",baseURL);
NSLog(#"loading catalog image(method: loadCatalogImage).......%#%#",baseURL, [[[array1 objectAtIndex:0] componentsSeparatedByString:#"##"] objectAtIndex:0]);
zoomedImageURL = [NSString stringWithFormat:#"%#%#", baseURL, [[[array1 objectAtIndex:0] componentsSeparatedByString:#"##"] objectAtIndex:1]];
// NSLog(#"Catalog ZOOM URL = %#", zoomedImageURL);//[[array1 objectAtIndex:0] componentsSeparatedByString:#"##"]);//[[[array objectAtIndex:[[global_CatalogRef objectAtIndex:pageNo] intValue]] componentsSeparatedByString:#"##"] objectAtIndex:3]);
[zoomedImageURL retain];
NSLog(#"aaaaaaa = %#",zoomedImageURL);
[webView loadRequest:
[NSURLRequest requestWithURL:
[NSURL URLWithString:
[NSString stringWithFormat:#"%#%#",baseURL, [[[array1 objectAtIndex:0] componentsSeparatedByString:#"##"] objectAtIndex:1]
]
]]
];
}
}
Any app that loads up enough resources to occupy so much memory will crash. Apple has several demo projects that deal specifically with loading a large image by breaking it down into smaller bits ci suggest you take a look at them.
Your images are 1300 x 1200 and you're loading 30 of them? You may WILL be running out of memory. If your app is ignoring memory warnings, and hogging up memory, the iOS system may be forcing your app to quit. As I mention in my comment below, that many images, that size, would be about 180megs. Far too big.
Do you really need to deliver such large images to an iphone app, and so many of them?
Can you put a crash log please? If possible Enable the Environment variable 'NSZombieEnabled' and then run the application and have a look at the crash log. This will help us to get the exact problem. By the way why are you creating and retaining "zoomedImageURL"?
I have an application, in which the user will select an image from a UIImagePickerView.
After selecting an image from it, I want to save it in my application.
How can this be achieved?
Thanks in advance for helping me.
Assuming you're using SDK 3.0, here is some code to save the image into your application's documents folder:
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
// Dismiss the picker
[[picker parentViewController] dismissModalViewControllerAnimated:YES];
// Get the image from the result
UIImage* image = [info valueForKey:#"UIImagePickerControllerOriginalImage"];
// Get the data for the image as a PNG
NSData* imageData = UIImagePNGRepresentation(image);
// Give a name to the file
NSString* imageName = "MyImage.png";
// Now, we have to find the documents directory so we can save it
// Note that you might want to save it elsewhere, like the cache directory,
// or something similar.
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentsDirectory = [paths objectAtIndex:0];
// Now we get the full path to the file
NSString* fullPathToFile = [documentsDirectory stringByAppendingPathComponent:imageName];
// and then we write it out
[imageData writeToFile:fullPathToFile atomically:NO];
return;
}
I would say something like this:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo {
self.resumePreviousSettingAfterEditing = true;
[self.topImageView setImage:image];
[cubeModel setImage:image forFace:[cubeModel.validFaces objectAtIndex:selectedRowInFacePicker]];
[self dismissImagePickerAnimated:true];
}
You register an event in your controller to handle the image selection. In that event handler, call a method somewhere, say in your model to set the new image. That function would look something like this:
(void)saveImage:(UIImage *)image withName:(NSString *)imageName {
// get the image path
NSString *filename = [self determineImagePath:imageName];
// make sure the file is removed if it exists
NSFileManager *fileManager = [NSFileManager defaultManager];
if([fileManager fileExistsAtPath:filename]) {
if(NO == [fileManager removeItemAtPath:filename error:NULL]) {
}
}
// Now, save the image to a file.
if(NO == [UIImagePNGRepresentation(image) writeToFile:filename atomically:YES]) {
[NSException raise:#"Image Save Failed" format:#"Unable to store image %s", filename];
}
}
When you want to load the image again, you would so something like:
- (UIImage *)loadImage:(NSString *)imageName {
NSString *filename = [self determineImagePath:imageName];
NSFileManager *fileManager = [NSFileManager defaultManager];
self.currentImage = nil;
if([fileManager fileExistsAtPath:filename]) {
NSData *imageData = [[NSData alloc] initWithContentsOfFile:filename];
UIImage *image = [UIImage imageWithData:imageData];
self.currentImage = image;
}
return self.currentImage;
}
And don't get me started on transforming which is way harder than it should be.
Enjoy,
Jacob
One thing you will need to address when saving images returned by UIImagePickerVIewController is that writing the data to disk will almost always be unacceptably slow. Your UI will hang while the writing is occurring. So, you should always execute these types of operations in an asynchronous queue. Even if the performance seems good enough for your application when testing, you should still do it an asynch queue -- you never know what other processes the device might have going on which might slow the save down once your app is in the hands of users.
Newer versions of iOS make saving photos asynchronously really, really easy using Grand Central Dispatch (GCD). The steps are:
Create an NSBlockOperation which saves the image
In the block operation's completion block, read the image from disk & display it (the only caveat here is that you must use the main queue to display the image: all UI operations must occur on the main thread).
Add the block operation to an operation queue and watch it go!
That's it. And here's the code:
// Grab the image
UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage];
// Create a block operation with our saves
NSBlockOperation* saveOp = [NSBlockOperation blockOperationWithBlock: ^{
[UIImagePNGRepresentation(image) writeToFile:file atomically:YES];
}];
// Use the completion block to update our UI from the main queue
[saveOp setCompletionBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIImage *image = [UIImage imageWithContentsOfFile:file];
// TODO: Assign image to imageview
}];
}];
// Kick off the operation, sit back, and relax. Go answer some stackoverflow
// questions or something.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:saveOp];
Once you are comfortable with this code pattern, you will find yourself using it a lot. It's incredibly useful when generating large datasets, long operations on load, etc. Essentially, any operation that makes your UI laggy in the least is a good candidate for this code. Just remember, you can't do anything to the UI while you aren't in the main queue and everything else is cake.