Make fetching of images faster from facebook in iOS - ios

Hi, I'm trying to fetch the user's friends profile pic from facebook and load it in my table and it works fine, but however it takes a long time based on number of friends you have I have noticed that in my fetchPeopleimages function the part of [NSData dataWithContentsOfURL:imageURL] is making the delay. I've searched through stackoverflow and it seems that I may have to implement the NSURLConnection sendAsynchronousRequest
method or cache. But there is no proper example. Can anyone please provide a solution to this? If I have to implement those methods please do give a example on how should I implement it in my code.
-(void) fetchPeopleimages
{
if ([listType isEqualToString:#"Facebook"])
{
int count=0;
NSMutableArray *imageArray=[[NSMutableArray alloc]initWithCapacity:[_peopleImageList count]];
for(NSString *imageUrl in _peopleImageList)
{
NSData *imageData = nil;
NSString *imageURLString = imageUrl;
count++;
NSLog(#"URL->%#,count->%d", imageURLString,count);
if (imageURLString)
{ //This block takes time to complete
NSURL *imageURL = [NSURL URLWithString:imageURLString];
imageData = [NSData dataWithContentsOfURL:imageURL];
}
if (imageData)
{
[imageArray addObject:imageData];
}
else
{
[imageArray addObject:[NSNull null]];
}
}
_peopleImageList=imageArray;
NSLog(#"%#",_peopleImageList);
}
}

Never ever use __withContentsOfURL in an iOS app. As you can see, it blocks the thread it runs on, making your app appear jerky or unresponsive. If you're really lucky, the iOS watchdog timer will kill your app if one of those operations takes too long.
You must use an asynchronous network operation for something like this.
Apple has a sample app that does exactly this:
https://developer.apple.com/library/ios/samplecode/LazyTableImages/Introduction/Intro.html

Related

Loading UIImage from disk sometimes fails with permission error in background state?

I have this strange issue that I am having trouble resolving. I am creating an App which allows music to be played back. When the screen is locked (and there is a currently playing song), the lock screen will populate with a bunch of data. One piece is the album art.
The problem is that after the phone is locked and I skip a few tracks (forwards or backwards), the UIImages are no longer being loaded. If I test out the functionality and quickly skip forward and backwards in my playback queue, the album art will appear for the first 4-5 songs. After that, the images stop appearing because I get a NSFileReadNoPermissionError from my code that grabs the image. I understand that I apparently do not have permission to access the png image files, but I do not understand why. My application created them, saved them on disk, and is now trying to load them from disk while my app is running in a background state.
The relevant code snippet:
+ (void)updateLockScreenInfoAndArtForSong:(Song *)song
{
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *artDirPath = [documentsPath stringByAppendingPathComponent:#"Album Art"];
NSString *path = artDirPath;
//-----> LIST ALL FILES for debugging <-----//
NSLog(#"LISTING ALL FILES FOUND");
int count;
NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path
error:NULL];
for (count = 0; count < (int)[directoryContent count]; count++)
{
NSLog(#"File %d: %#", (count + 1), [directoryContent objectAtIndex:count]);
}
//-----> END DEBUG CODE <-----//
Song *nowPlayingSong = [MusicPlaybackController nowPlayingSong];
Class playingInfoCenter = NSClassFromString(#"MPNowPlayingInfoCenter");
if (playingInfoCenter) {
NSMutableDictionary *songInfo = [[NSMutableDictionary alloc] init];
NSError *error;
NSData *data = [NSData dataWithContentsOfURL:
[AlbumArtUtilities albumArtFileNameToNSURL:nowPlayingSong.albumArtFileName] options:NSDataReadingUncached error:&error];
NSInteger code = error.code;
NSLog(#"Error code: %li", (long)code); //prints 257 sometimes, which is NSFileReadNoPermissionError
UIImage *albumArtImage = [UIImage imageWithData:data];
if(albumArtImage == nil){ //song has no album art, check if its album does
Album *songsAlbum = song.album;
if(songsAlbum){
albumArtImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:
[AlbumArtUtilities albumArtFileNameToNSURL:songsAlbum.albumArtFileName]]];
}
}
[songInfo setObject:nowPlayingSong.songName forKey:MPMediaItemPropertyTitle];
NSInteger duration = [nowPlayingSong.duration integerValue];
[songInfo setObject:[NSNumber numberWithInteger:duration]
forKey:MPMediaItemPropertyPlaybackDuration];
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:songInfo];
}
}
Any help would be immensely appreciated! I have tried so many thing that I am at a loss for what to even try next. Note the above code snippet is called in
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
when event.subtype is UIEventSubtypeRemoteControlNextTrack or UIEventSubtypeRemoteControlPreviousTrack.
Figured it out after trying everything all day lol. Turns out that by default in iOS 8, much of the system is encrypted (files cannot be accessed after the phone is locked)...with a small delay of course. This is why a few album art images were loading but they stopped working after a few seconds. The delay between when the phone locked and the encryption enabled itself made it seem like my issue was "random".
Anyway for anyone reading this, my solution involved setting the file protection of all folders and subfolder and files leading to the album art directory.
Hint:
[attributes setValue:NSFileProtectionCompleteUntilFirstUserAuthentication forKey:NSFileProtectionKey];
Set that attribute on a file when it is being created (provide the information as an NSDictionary). If an existing directory or file is to be modified, gather the attributes of the thing using NSFileManager, and then set the values as shown above.

UICollectionView: get profile pictures from Instagram instead of recent media pictures to display

I am a newbie iOS learner. Couldn't find answer to following question after searching for a while. Hence, here it is.
Building on a first app that displays recent pictures from a user's instagram feed, I am trying to display pictures from the follows of that users instead.
To call recent pictures from the Instagram feed, which worked well, I had created the following method "imageForPhoto"
+ (void)imageForPhoto:(NSDictionary *)photo size:(NSString *)size completion:(void(^)(UIImage *image))completion {
if (photo == nil || size == nil || completion == nil) {
return;
}
NSString *key = [[NSString alloc] initWithFormat:#"%#-%#", photo[#"id"], size];
NSURL *url = [[NSURL alloc] initWithString:photo[#"images"][size][#"url"]];
[self downloadURL:url key:key completion:completion];
}
Therefore, I first modified my code to get the data related to the "follows" in my PhotosViewController instead of recent media pictures ( v1/users/3/follows ):
NSURLSession *session = [NSURLSession sharedSession];
NSString *urlString = [[NSString alloc] initWithFormat:#"https://api.instagram.com/v1/users/3/follows?count=99&access_token=%#", self.accessToken];
Then, I created a new method that I called friendAvatarForPhoto to get the follows profile pictures from a photo NSDictionary that is passed in as the only method parameter. I placed this method in my PhotoController class:
+ (void)friendAvatarForPhoto:(NSDictionary *)photo completion:(void(^)(UIImage *image))completion {
if (photo == nil || completion == nil) {
return;
}
NSString *key = [[NSString alloc] initWithFormat:#"avatar-%#", photo[#"user"][#"id"]];
NSURL *url = [[NSURL alloc] initWithString:photo[#"profile_picture"]];
[self downloadURL:url key:key completion:completion];
}
It seems to work. I have manually checked that the pictures that are rendered on my UICollectionView are actually coming from the "profile_picture" key of the responseDictionary I get back from Instagram.
Here is the structure of this dictionary: https://www.dropbox.com/s/ldjqupadqg0j3nq/friends_response_dictionary.png
Specifically, as you can see, I modified both the key and the url:
NSString *key = [[NSString alloc] initWithFormat:#"avatar-%#", photo[#"user"][#"id"]];
NSURL *url = [[NSURL alloc] initWithString:photo[#"profile_picture"]];
but I am not really understanding what I need to initial the *key string with... I kept "avatar" for example but why should I? I am pretty sure it's wrong although the result seems to be fine from what is returned on my UICollectionView.
What should I initiate this string with instead?
What is the function of this key in the overall NSURLSession process?
I'd love to better understand the overall process and better connect the dots with Instagram's API so I can query the right key and be sure I am getting what I am looking for. In a reliable way, not an intuitive one as I have just done app-arently.
Any help from experienced developers in the community would be welcome, I've just started to explore iOS dev a few weeks ago based on rusty C skills from a long time back.
Thank you!
:) Arsene

How do I preload images from an array of urls in iOS?

I have a music app that I want to preload album arts for. Right now, when you hit next song it loads the album art from a URL but it would be nice if it was already preloaded and in memory. I've tried a few things but nothing seems to really work. Does anyone know of a technique that where I can preload images in iOS and then look for that image later and if its not there, then download it?
Edit with some more detail...
When I go to play a 'station' on my app, I make a request to a server that returns JSON for the playlist. Inside of this JSON is each song as well as data including the album art URL. When a user goes to play a 'station' I would like to preload images for either the next album art URL in the stack or all album art URLs in the entire playlist.
Anyways, I've tried loading the next album art URL in a UIImage in the background as like a temporaryAlbumArt and then when the next song is played simply do albumArt = temporaryAlbumArt and then repeat the process setting the next songs album art to temp and so on..
I've tried something like this:
if (temporaryAlbumArt) {
albumArt = temporaryAlbumArt
} else {
albumArt = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:firstAlbumArtURL]]];
}
temporaryAlbumArt = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:nextAlbumArtURL]]];
I guess my question is, what is the best method of:
loading an image or multiple images from a URL(s) into memory
using those images later on without any lag time
You can use this "must have" tool APSmartStorage. It supports everything you need.
This library does what you need: JMImageCache
Loop through your URLs, download them through JMImageCache and when you need to show an image the library will fetch the stored image if it's finished downloading.
//You can do this when you get the array of urls to download the images in the background
for(NSURL *url in urls) {
[[JMImageCache sharedCache] imageForURL:url];
}
//and then when you need an image:
[imageView setImageWithURL:url placeholder:[UIImage imageNamed:#"placeholder.png"]];
I'm not for sure what you are asking but if you know what the next song is going to be couldn't you just down load it in a background thread then have it ready for display when the next song starts?
If you want the app to store all the downloaded images locally you may eventually run into space issues but its do able. You could use the downloaded image then store it in the caches directory (not document dir or Apple will get mad!). When a song comes just do a quick check for the image and if its not local download it. Songs take a few minutes so there should be time to do all this in the background.
#define GET_QUEUE dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
// while song is playing...
dispatch_async(GET_QUEUE, ^{
// On a separate thread look for the image locally in the cache and then
// download it if necessary
});
I'm not sure if this answers your question or not...
Ok so here is a bit more code - basically it lets me ask hopefully helpful questions in context.
// Inside some loop/method that plays songs...
if (temporaryAlbumArt)
albumArt = temporaryAlbumArt
else
albumArt = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:firstAlbumArtURL]]];
// If this keeps executing then perhaps temporaryAlbumArt is getting released somehow?
// Are you persisting it correctly through each iteration/method call?
// show art...
// start music
// Call art update
dispatch_async(GET_QUEUE, ^{
temporaryAlbumArt = nil; // In case the data hasn't been obtained yet on the next iteration???
temporaryAlbumArt = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:nextAlbumArtURL]]];
// This works ok then? Can you make sure this is completing correctly before the next iteration/method call?
});
Sorry I can't be of more help.
I have written this code to download data from server and save that data into document directory so that next time if anybody hit the same url,it is fetched from document directory without any server hit.
-(void)downloadImageForProductsFromArray:(NSArray *)inventoryArr {
BOOL isAllDataDownloaded = YES;
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Data Sync...";
[[NSOperationQueue mainQueue] addObserver:self forKeyPath:#"operations" options:0 context:nil];
for (int i = 0; i < inventoryArr.count; i++) {
NSString *productUrlStr = [inventoryArr objectAtIndex:i];
NSData *dirData = [CommonUtils fetchImageDataFromDirectoryWithImageName: productUrlStr];/*****Check whether Image data is already downloaded and saved in document directory*****/
if (!dirData) {
isAllDataDownloaded = NO;
NSBlockOperation *downloadImgOpr = [[NSBlockOperation alloc] init];
downloadImgOpr.queuePriority = NSOperationQueuePriorityVeryHigh;
downloadImgOpr.qualityOfService = NSQualityOfServiceUserInitiated;
[downloadImgOpr addExecutionBlock:^{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString: productUrlStr]];
if (imageData) {
/*****Save Image Data in Document Directory*****/
}
}];
}
}
if (isAllDataDownloaded) {
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
}}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context{
if (object == [NSOperationQueue mainQueue] && [keyPath isEqualToString:#"operations"]) {
if ([[NSOperationQueue mainQueue].operations count] == 0) {
// Do something here when your queue has completed
[MBProgressHUD hideAllHUDsForView:self.view animated:YES];
NSLog(#"queue has completed");
}
}
else {
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
}
}
In This code i have all the url strings in the inventory array.In for loop i have made several operations and added those operations into the queue(I have used main queue for holding UI and syncing data.Choose your queue appropriately).Also I have added the KVO to the queue for keypath value "operations" so that if count of operations is 0,we can do the useful operation.

iPhone app flipbook animation crashing on device

The app uses a series of jpg's and a timer that steps through them to make an animation.
During the animation on the device, it crashes (didReceiveMemoryWarningError.)
I'm new to iPhone programming and Objective-C. How can I optimize this flipbook for the iPhone?
I can imagine simply compressing the jpeg's and perhaps losing some quality would help, but my understanding is the iPhone does its own image compression/decompression on the device and I may be wasting my time.
Tried different things, eventually hit on storing an array of NSData objects and converting to UIImage on the fly.
for (NSString *imageFile in directoryList) {
NSString *fileLocation = [imageFolder stringByAppendingPathComponent:imageFile];
NSData *imageData = [NSData dataWithContentsOfFile:fileLocation];
if (imageData) {
[images addObject:imageData];
} else {
[Util trace:#"cannot get image data for image named: %#", fileLocation];
}
and then to update your image:
-(void)updateImageView:(id)sender
{
UIImage *anImage = [UIImage imageWithData:[images safeObjectAtIndex:nextImage] ];
[imageView setImage:anImage];
nextImage++;
}

xcode gdb pauses on ipad device

I'm try to create some images and store it inside the Documents folder. When I run it on the simulator it is fine. However when I run it through the ipad device, the gdb just pauses at a certain point and doesn't give me much information to work with. I used the analyser to check what items to release to check memory leaks. Im running 4.3 SDK.
I'm not sure what the actual issue is. Sometimes looping through 100 images and storing them is ok but then after a while it just pauses. Where can I look further for debug or clues on how to fix this. I have provided some code.
for(int i = 0; i < totalpages; i++)
{
NSString *imagePath = [NSString stringWithFormat:#"%#/%d.jpg",
imageFullPathFolder, i+1];
if(![manager fileExistsAtPath:imagePath])
{
NSString *urlParams = [NSString stringWithFormat:#"SOMEURL",
fileSourceId, i+1];
NSURL *imageUrl = [NSURL URLWithString:urlParams];
UIImage *image = [UIImage imageWithData:[NSData
dataWithContentsOfURL:imageUrl]];
NSData *imageData = UIImageJPEGRepresentation(image, 1.0);
[manager createFileAtPath:imagePath contents:imageData
attributes:nil];
[imageData release];
}
}
You don't need to release the image data returned by UIImageJPEGRepresentation (you don't own it; you're over-releasing it).

Resources