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.
Related
I am curious if it is possible to easily assign and retain __block objects inside asynchronous blocks in MRC and ARC? I obtained the following code while rewriting a piece of code with minimal changes. I got stuck when trying to return the image from an asynchronous block. A concern is that the code would one day be converted to ARC. I do not want to have hidden memory crash after the conversion. My options were
Using a GCD object holder if such a thing exists
Using a homebrew object holder, an array or others
Directly adding the image to the array (which I am using)
Rewriting the code to another structure
Basically the code load multiple images in a background thread. Searching // image deallocated :( will give you the location.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
ALAssetsLibrary *assetsLibrary = nil;
for (int i = 0; i < sources.count; ++i) {
__block UIImage *image = nil;
id source = sources[i];
if ([source isKindOfClass:[NSString class]]) {
image = something
} else if ([source isKindOfClass:[NSURL class]]) {
NSURL *url = source;
if ([url.scheme isEqualToString:#"assets-library"]) {
dispatch_group_t group = dispatch_group_create();
if (!assetsLibrary)
assetsLibrary = [[[ALAssetsLibrary alloc] init] autorelease];
dispatch_group_enter(group);
[assetsLibrary assetForURL:url resultBlock:^(ALAsset *asset) {
image = something
dispatch_group_leave(group);
} failureBlock:^(NSError *error) {
dispatch_group_leave(group);
}];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// image deallocated :(
} else if (url.isFileURL) {
image = something
}
}
// add image to an array
}
dispatch_async(dispatch_get_main_queue(), ^{
// notify
});
});
I think moving the image variable declaration would be the first right step to think about this. You can't expect to have one image variable for multiple concurrent operations.
so to get started, let's do this-
ALAssetsLibrary *assetsLibrary = nil;
for (int i = 0; i < sources.count; ++i) {
__block UIImage *image = nil;
After that, you are currently keeping UIImage instances in an array? This is not recommended. Please find out a way to write these images to documents directory and later read from there when needed. Keeping UIImage instances in memory will lead to severe memory overuse issues.
You need to come up with a naming convention to write your images into documents directory. Whenever an image load completes, just save it to your local folder and later read from there.
You could also call the notify completion part as soon as one image is loaded and keep notifying about others as you get them. Not sure how your design is working.
Also, I noticed you are creating multiple groups. One new group with each iteration inside for loop here-
dispatch_group_t group = dispatch_group_create();
Moving it outside for loop would be a good call.
Hope this helps.
I am newbiee to the backend. I have been working on an app collaborating with a newbiee backend developer. I am working on an application where a user sells/buys a product. I could able to send product the image which taken from the device camera and send it via POST method to database.
Taken image is stored in the database. The question that I have is as follows:
In order to get back this image to the app what method needs to be used? Is it better to get data chunk or image URL in JSON?
I would recommend getting the image URL and then using the following code to load the image:
dispatch_async(dispatch_get_global_queue(0,0), ^{
NSData * data = [[NSData alloc] initWithContentsOfURL:url];
if (data == nil) {
NSLog(#"Uh oh! Couldn't retrieve the image");
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *image = [UIImage imageWithData:data];
//do something with the image such as:
UIImageView *myImageView.image = image;
});
});
I am creating an iphone app, where I am trying to fetch multiple (5-7) hi-res images from backend server using URL. All fetched images I need to set in a slideshow, now the problem is that very first time if I am fetching images from server then my images are not displaying. Instead of images, am getting white background. Again if I go back and fetching the same images then it's displaying correctly.
Here is my code :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
for (int i=0; i<[end.slideShowArray count]; i++) {
SlideShow *pro = [[SlideShow alloc] init];
pro = [end.slideShowArray objectAtIndex:i];
NSURL *url = [NSURL URLWithString:pro.imageUrl];
NSData *imageData = [NSData dataWithContentsOfURL:url];
UIImage *img = [UIImage imageWithData:imageData];
dispatch_async (dispatch_get_main_queue (),
^{
[slideshow addImage:img];
});
}
});
You're creating a new SlideShow every time around the loop. I think you intended to create a single slide show then add all the images to it.
I have an application that uploads videos into a server and now what I am trying to create is a news feed to display those video post but by a thumbnail or frame of the video. So that once the user clicks the thumbnail the video will play.
I have the following code used to grab the video from the server and play it:
- (void)openVideo {
NSString *videoURLString = #"http://myite.com/dev/iphone/uploads/t69566.mov";
NSURL *videoURL = [NSURL URLWithString:videoURLString];
MPMoviePlayerViewController *moviePlayerView = [[[MPMoviePlayerViewController alloc] initWithContentURL:videoURL] autorelease];
[self presentMoviePlayerViewControllerAnimated:moviePlayerView];
}
Now of coarse I am gong to make openVideo dynamic with the population of video post but I am stuck on taking this video and grabbing a frame or thumbnail and displaying it.
Suggestions? thoughts?
UPDATE:
I want to take a thumbnail from a video taken from server. which actually brings me to a question.
When the user first uploads the video, would it be better to just create a thumbnail there and upload it to my server and just associate that with the video on grabbing them to populate a news feed type?
You can do it using the AVAssetImageGenerator. Rough code, copied and pasted from a working project (but might not be properly isolated):
_thumbnailView is an UIImageView to display the thumbnail.
_activityView is a UIActivityIndicatorView that gets hidden when the thumbnail finishes loading.
AVPlayerItem *playerItem = [[AVPlayerItem playerItemWithURL:movieURL] retain];
AVAssetImageGenerator *_generator;
_generator = [[AVAssetImageGenerator assetImageGeneratorWithAsset:playerItem.asset] retain];
[playerItem release];
AVAssetImageGeneratorCompletionHandler handler = ^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) {
if (result == AVAssetImageGeneratorSucceeded) {
UIImage *thumb = [UIImage imageWithCGImage:image];
[_thumbnailView setImage:thumb forState:UIControlStateNormal];
} else {
DLog(#"%s - error: %#", __PRETTY_FUNCTION__, error);
}
dispatch_async(dispatch_get_main_queue(), ^{
_thumbnailView.hidden = NO;
_playButton.hidden = NO;
[_activityView stopAnimating];
});
};
[_generator generateCGImagesAsynchronouslyForTimes:[NSArray arrayWithObject:[NSValue valueWithCMTime:CMTimeMakeWithSeconds(30,30)]] completionHandler:handler];
Back to your question, in my opinion, a server side generated thumbnail is usually better, as servers have way more processing power. Also, in order to get this thumbnail you need to start downloading the actual video from the server.
Finally, not that you pass a CMTime parameter including a call to CMTimeMakeWithSeconds(). This might prove problematic, as you can easily hit a blank frame. This is why we are using 30 as the parameter, so we avoid that situation in our videos.
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