Displaying a photo from url - objective-c

I want to get a photo from my homepage and display it. And it (kind of) works. But sometimes it takes min 10 seconds to load the next scene because of something that happens here. So here is what I do :
NSString *myURL = [PICURL stringByAppendingString:[[[[levelConfig objectForKey:category] objectForKey:lSet] objectForKey:levelString] objectForKey:#"pic"]];
UIImage *dYKPic = [UIImage imageWithData: [NSData dataWithContentsOfURL: [NSURL URLWithString:myURL]]];
if(dYKPic == nil){
NSString *defaultURL = #"http://www.exampleHP.com/exampleFolder/default.jpg";
dYKPic = [UIImage imageWithData: [NSData dataWithContentsOfURL: [NSURL URLWithString:defaultURL]]];
}
CCTexture2D *tex = [[CCTexture2D alloc] initWithImage:dYKPic];
CCSprite *image = [CCSprite spriteWithTexture:tex];
image.anchorPoint = ccp(0,0);
image.position = ccp(32,216);
[self addChild:image z:2];
So, it takes 10 seconds, and additionally, the default.jpg is loaded - even though the picture exists - but that just in the case where it takes so long... 70% of the cases it works perfectly normal... So what is wrong ? Where do I release tex ? Immediately after adding the child ?

It has to load the picture. Thats the issue. You either need to load and cache it, store it, or preload it before you need it.
Or one final option is the load it async and just update your view when its finished.

Related

UITableViewCell Data load error

I am parsing some images and strings from a JSON file, the parsing works fine, but the image loading is very slow. I notized, the UITableView shows the content quicker, when I press on the UITableViewCell. Does anyone know a fix for that?
Here is the code I use, I use a NSOperationQueue to keep the CPU usage low.
NSDictionary *dict;
dict = [application objectAtIndex:indexPath.row];
name = [dict objectForKey:#"name"];
detileName = [dict objectForKey:#"detailName"];
itmsLink = [dict objectForKey:#"itms-serviceLink"];
icon = [dict objectForKey:#"icon"];
developer = [dict objectForKey:#"developer"];
version = [dict objectForKey:#"version"];
category = [dict objectForKey:#"category"];
rating = [dict objectForKey:#"rating"];
ratingNumbers = [dict objectForKey:#"ratingNumber"];
description = [dict objectForKey:#"description"];
developerEmails = [dict objectForKey:#"developerEmail"];
[downloadQueue addOperationWithBlock:^{
cell.AppName.text = name;
cell.category.text = category;
cell.rater.text = [NSString stringWithFormat:#"(%#)", ratingNumbers];
if ([rating intValue] == 1) {
cell.rating.image = [UIImage imageNamed:#"1.png"];
}
if ([rating intValue] == 2) {
cell.rating.image = [UIImage imageNamed:#"2.png"];
}
if ([rating intValue] == 3) {
cell.rating.image = [UIImage imageNamed:#"3.png"];
}
if ([rating intValue] == 4) {
cell.rating.image = [UIImage imageNamed:#"4.png"];
}
cell.itms = itmsLink;
cell.AppIcon.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:icon]]];
cell.number.text = [NSString stringWithFormat:#"%li", (long)indexPath.row + 1];
}];
It looks like you've already got most of the data you need to present the cell, except for image that will go in AppIcon.image. the way things are set up now, the image download is blocking you from presenting the cell immediately. my guess is that the image download does eventually complete, but you do not force the cell to redraw itself after the download has finished. tapping on a cell forces it to redraw itself, which is probably why you're seeing the behavior you described.
I suggest you present the cell immediately using the data that you already have downloaded, and kick off a background download of the image. When the download is complete, you can send an NSNotification and update the appropriate cell. you can do this by creating a subclass of NSOperation that accepts a URL during initialization, and then adding that op to your operation queue
if you don't want to do all that work yourself, there is a category on UIImageView that uses AFNetworking to do the update for you using blocks.
https://github.com/zbowling/AFNetworkingPollTest/blob/master/ServerTest/UIImageView%2BAFNetworking.h
As stated by #Nick Galasso - you don't refresh the cell's and passing the cell pointer to a block is a very bad practice since UITableView actually reuses the cell's and when download is complete you should obtain cell pointer again - you are interested in cell under actual NSIndexPath, not the object.
Under a link:
https://developer.apple.com/library/ios/samplecode/LazyTableImages/Introduction/Intro.html
You can find perfect sample code on lazy image loading, fragment below shows the part which actually brings the view, this code called on download finish set's the image asap:
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
cell.imageView.image = downloadedImage;
You can call it in some completion handler of downloading class (the sample under Apple link covers that).

Save multiple images quickly in iOS 6 (custom album)

I'm writing an application that will take several images from URL's, turn them into a UIImage and then add them to the photo library and then to the custom album. I don't believe its possible to add them to a custom album without having them in the Camera Roll, so I'm accepting it as impossible (but it would be ideal if this is possible).
My problem is that I'm using the code from this site and it does work, but once it's dealing with larger photos it returns a few as 'Write Busy'. I have successfully got them all to save if I copy the function inside its own completion code and then again inside the next one and so on until 6 (the most I saw it take was 3-4 but I don't know the size of the images and I could get some really big ones) - this has lead to the problem that they weren't all included in the custom album as they error'd at this stage too and there was no block in place to get it to repeat.
I understand that the actual image saving is moved to a background thread (although I don't specifically set this) as my code returns as all done before errors start appearing, but ideally I need to queue up images to be saved on a single background thread so they happen synchronously but do not freeze the UI.
My code looks like this:
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:singleImage]]];
[self.library saveImage:image toAlbum:#"Test Album" withCompletionBlock:^(NSError *error) {
if (error!=nil) {
NSLog(#"Error");
}
}];
I've removed the repetition of the code otherwise the code sample would be very long! It was previously where the NSLog code existed.
For my test sample I am dealing with 25 images, but this could easily be 200 or so, and could be very high resolution, so I need something that's able to reliably do this over and over again without missing several images.
thanks
Rob
I've managed to make it work by stripping out the save image code and moving it into its own function which calls itself recursively on an array on objects, if it fails it re-parses the same image back into the function until it works successfully and will display 'Done' when complete. Because I'm using the completedBlock: from the function to complete the loop, its only running one file save per run.
This is the code I used recursively:
- (void)saveImage {
if(self.thisImage)
{
[self.library saveImage:self.thisImage toAlbum:#"Test Album" withCompletionBlock:^(NSError *error) {
if (error!=nil) {
[self saveImage];
}
else
{
[self.imageData removeObject:self.singleImageData];
NSLog(#"Success!");
self.singleImageData = [self.imageData lastObject];
self.thisImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:self.singleImageData]]];
[self saveImage];
}
}];
}
else
{
self.singleImageData = nil;
self.thisImage = nil;
self.imageData = nil;
self.images = nil;
NSLog(#"Done!");
}
}
To set this up, I originally used an array of UIImages's but this used a lot of memory and was very slow (I was testing up to 400 photos). I found a much better way to do it was to store an NSMutableArray of URL's as NSString's and then perform the NSData GET within the function.
The following code is what sets up the NSMutableArray with data and then calls the function. It also sets the first UIImage into memory and stores it under self.thisImage:
NSEnumerator *e = [allDataArray objectEnumerator];
NSDictionary *object;
while (object = [e nextObject]) {
NSArray *imagesArray = [object objectForKey:#"images"];
NSString *singleImage = [[imagesArray objectAtIndex:0] objectForKey:#"source"];
[self.imageData addObject:singleImage];
}
self.singleImageData = [self.imageData lastObject];
self.thisImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:self.singleImageData]]];
[self saveImage];
This means the rest of the getters for UIImage can be contained in the function and the single instance of UIImage can be monitored. I also log the raw URL into self.singleImageData so that I can remove the correct elements from the array to stop duplication.
These are the variables I used:
self.images = [[NSMutableArray alloc] init];
self.thisImage = [[UIImage alloc] init];
self.imageData = [[NSMutableArray alloc] init];
self.singleImageData = [[NSString alloc] init];
This answer should work for anyone using http://www.touch-code-magazine.com/ios5-saving-photos-in-custom-photo-album-category-for-download/ for iOS 6 (tested on iOS 6.1) and should result in all pictures being saved correctly and without errors.
If saveImage:toAlbum:withCompletionBlock it's using dispatch_async i fear that for i/o operations too many threads are spawned: each write task you trigger is blocked by the previous one (bacause is still doing I/O on the same queue), so gcd will create a new thread (usually dispatch_async on the global_queue is optimized by gcd by using an optimized number of threads).
You should either use semaphores to limit the write operation to a fixed number at the same time or use dispatch_io_ functions that are available from iOS 5 if i'm not mistaken.
There are plenty example on how to do this with both methods.
some on the fly code for giving an idea:
dispatch_semaphore_t aSemaphore = dispatch_semaphore_create(4);
dispatch_queue_t ioQueue = dispatch_queue_create("com.customqueue", NULL);
// dispatch the following block to the ioQueue
// ( for loop with all images )
dispatch_semaphore_wait(aSemaphore , DISPATCH_TIME_FOREVER);
[self.library saveImage:image
toAlbum:#"Test Album"
withCompletionBlock:^(NSError *error){
dispatch_semaphore_signal(aSemaphore);
}];
so every time you will have maximum 4 saveImage:toAlbum, as soon as one completes another one will start.
you have to create a custom queue, like above (the ioQueue) where to dispatch the code that does the for loop on the images, so when the semaphore is waiting the main thread is not blocked.

NSImage at app start

I have an image view object. I can get it to load images when a buttun is pressed but I want to know if there is a way to get it to load at the start of the app.
The code im using is
NSURL *lURL = nil;
NSData *lData = nil;
NSImage *lImage = nil;
lData = [lURL resourceDataUsingCache:YES];
lImage = [[NSImage allocWithZone:[self zone]] initWithData:lData];
lURL = [[NSURL alloc] initWithString:#"urltoimage"];
[imageView setImage:lImage];
any help is appreciated
Use -[UIViewController viewDidLoad] method which is called exactly once when the controller is first loaded into the memory.

Use Facebook API with Objective C to find random Facebook user image

I am building an app that returns a random Facebook profile picture. So far I have the code below generating a random profile ID which sometimes does return a real profile but sometimes doesnt and just shows the generic blue Facebook face. When I use the given number on the actual website graph API it just returns false. My question is how would I put the code in so that if the random number generated returns a false profile, it just keeps generating a new random number until a real profile is returned, thus a real picture? Thanks in advance
#implementation FacebookPicturesViewController
- (IBAction) nextImagePush {
NSString *prefix = #"http://graph.facebook.com/";
NSString *profileId = [NSString stringWithFormat:#"%09d", abs(arc4random())];
NSLog(#"profileId: %#", profileId);
NSString *suffix = #"/picture?type=large";
NSString* url= [NSString stringWithFormat:#"%#%#%#", prefix, profileId, suffix];
UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url]]];
[imageView setImage:img];
imageCount++;
NSLog(#"profileId: %#", profileId);
if (imageCount >= [imageArray count]){
imageCount = 0;
}
}
It sounds like your problem is that you can't tell if it's a real image or not because Facebook always returns a default image?
Instead of just requesting the picture?type=large directly (which always returns an image), first make a request to the http://graph.facebook.com/USERID and read that response - which will be false or something if it's not a real user.
Loop through until you find a real user, then request the image URL.
I'm not sure why you would want to do this though... but good luck.

How to get [UIImage imageWithContentsOfFile:] and High Res Images working

As many people are complaining it seems that in the Apple SDK for the Retina Display there's a bug and imageWithContentsOfFile actually does not automatically load the 2x images.
I've stumbled into a nice post how to make a function which detects UIScreen scale factor and properly loads low or high res images ( http://atastypixel.com/blog/uiimage-resolution-independence-and-the-iphone-4s-retina-display/ ), but the solution loads a 2x image and still has the scale factor of the image set to 1.0 and this results to a 2x images scaled 2 times (so, 4 times bigger than what it has to look like)
imageNamed seems to accurately load low and high res images, but is no option for me.
Does anybody have a solution for loading low/high res images not using the automatic loading of imageNamed or imageWithContentsOfFile ? (Or eventually solution how to make imageWithContentsOfFile work correct)
Ok, actual solution found by Michael here :
http://atastypixel.com/blog/uiimage-resolution-independence-and-the-iphone-4s-retina-display/
He figured out that UIImage has the method "initWithCGImage" which also takes a scale factor as input (I guess the only method where you can set yourself the scale factor)
[UIImage initWithCGImage:scale:orientation:]
And this seems to work great, you can custom load your high res images and just set that the scale factor is 2.0
The problem with imageWithContentsOfFile is that since it currently does not work properly, we can't trust it even when it's fixed (because some users will still have an older iOS on their devices)
We just ran into this here at work.
Here is my work-around that seems to hold water:
NSString *imgFile = ...path to your file;
NSData *imgData = [[NSData alloc] initWithContentsOfFile:imgFile];
UIImage *img = [[UIImage alloc] initWithData:imgData];
imageWithContentsOfFile works properly (considering #2x images with correct scale) starting iOS 4.1 and onwards.
Enhancing Lisa Rossellis's answer to keep retina images at desired size (not scaling them up):
NSString *imagePath = ...Path to your image
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfFile:imagePath] scale:[UIScreen mainScreen].scale];
I've developed a drop-in workaround for this problem.
It uses method swizzling to replace the behavior of the "imageWithContentsOfFile:" method of UIImage.
It works fine on iPhones/iPods pre/post retina.
Not sure about the iPad.
Hope this is of help.
#import </usr/include/objc/objc-class.h>
#implementation NSString(LoadHighDef)
/** If self is the path to an image, returns the nominal path to the high-res variant of that image */
-(NSString*) stringByInsertingHighResPathModifier {
NSString *path = [self stringByDeletingPathExtension];
// We determine whether a device modifier is present, and in case it is, where is
// the "split position" at which the "#2x" token is to be added
NSArray *deviceModifiers = [NSArray arrayWithObjects:#"~iphone", #"~ipad", nil];
NSInteger splitIdx = [path length];
for (NSString *modifier in deviceModifiers) {
if ([path hasSuffix:modifier]) {
splitIdx -= [modifier length];
break;
}
}
// We insert the "#2x" token in the string at the proper position; if no
// device modifier is present the token is added at the end of the string
NSString *highDefPath = [NSString stringWithFormat:#"%##2x%#",[path substringToIndex:splitIdx], [path substringFromIndex:splitIdx]];
// We possibly add the extension, if there is any extension at all
NSString *ext = [self pathExtension];
return [ext length]>0? [highDefPath stringByAppendingPathExtension:ext] : highDefPath;
}
#end
#implementation UIImage (LoadHighDef)
/* Upon loading this category, the implementation of "imageWithContentsOfFile:" is exchanged with the implementation
* of our custom "imageWithContentsOfFile_custom:" method, whereby we replace and fix the behavior of the system selector. */
+(void)load {
Method originalMethod = class_getClassMethod([UIImage class], #selector(imageWithContentsOfFile:));
Method replacementMethod = class_getClassMethod([UIImage class], #selector(imageWithContentsOfFile_custom:));
method_exchangeImplementations(replacementMethod, originalMethod);
}
/** This method works just like the system "imageWithContentsOfFile:", but it loads the high-res version of the image
* instead of the default one in case the device's screen is high-res and the high-res variant of the image is present.
*
* We assume that the original "imageWithContentsOfFile:" implementation properly sets the "scale" factor upon
* loading a "#2x" image . (this is its behavior as of OS 4.0.1).
*
* Note: The "imageWithContentsOfFile_custom:" invocations in this code are not recursive calls by virtue of
* method swizzling. In fact, the original UIImage implementation of "imageWithContentsOfFile:" gets called.
*/
+ (UIImage*) imageWithContentsOfFile_custom:(NSString*)imgName {
// If high-res is supported by the device...
UIScreen *screen = [UIScreen mainScreen];
if ([screen respondsToSelector:#selector(scale)] && [screen scale]>=2.0) {
// then we look for the high-res version of the image first
UIImage *hiDefImg = [UIImage imageWithContentsOfFile_custom:[imgName stringByInsertingHighResPathModifier]];
// If such high-res version exists, we return it
// The scale factor will be correctly set because once you give imageWithContentsOfFile:
// the full hi-res path it properly takes it into account
if (hiDefImg!=nil)
return hiDefImg;
}
// If the device does not support high-res of it does but there is
// no high-res variant of imgName, we return the base version
return [UIImage imageWithContentsOfFile_custom:imgName];
}
#end
[UIImage imageWithContentsOfFile:] doesn't load #2x graphics if you specify an absolute path.
Here is a solution:
- (UIImage *)loadRetinaImageIfAvailable:(NSString *)path {
NSString *retinaPath = [[path stringByDeletingLastPathComponent] stringByAppendingPathComponent:[NSString stringWithFormat:#"%##2x.%#", [[path lastPathComponent] stringByDeletingPathExtension], [path pathExtension]]];
if( [UIScreen mainScreen].scale == 2.0 && [[NSFileManager defaultManager] fileExistsAtPath:retinaPath] == YES)
return [[[UIImage alloc] initWithCGImage:[[UIImage imageWithData:[NSData dataWithContentsOfFile:retinaPath]] CGImage] scale:2.0 orientation:UIImageOrientationUp] autorelease];
else
return [UIImage imageWithContentsOfFile:path];
}
Credit goes to Christof Dorner for his simple solution (which I modified and pasted here).