Hide image while loading iOS - ios

I'm picking images with UIPicker and when user picks, the download will start. I'm downloading 78 images from my server to create an animation. How can I make it while it's downloading images to hide current image(imageview), and when download finishes to show image(imageview). I tried this but it's not working.
- (void)viewDidLoad
{
[super viewDidLoad]
// Load starting image, otherwise screen is blank
self.radar_1 = [[UIImageView alloc]initWithFrame:CGRectMake(0, 65, self.view.frame.size.width, self.view.frame.size.width-70)];
radar_1.image = [UIImage animatedImageWithAnimatedGIFURL:[NSURL URLWithString:#"<|SERVER LINK|>"]];
[self.view addSubview:radar_1];
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
if (row == 2) {
self.radar_1 = [[UIImageView alloc] initWithFrame:CGRectMake(0, 65, self.view.frame.size.width, self.view.frame.size.width-70)];
self.radar_1.hidden = YES;
radar_1.animationImages = [NSArray arrayWithObjects:
[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"<|SERVER LINK|> "]]],
[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"<|SERVER LINK|> "]]],
[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"<|SERVER LINK|>"]]],
[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"<|SERVER LINK|>"]]],
{...} // Doing the same 77 times...
radar_1.hidden = NO;
radar_1.animationDuration = 20.0f;
radar_1.animationRepeatCount = 0;
[radar_1 startAnimating];
[self.view addSubview: radar_1];
}

Downloading 70 images from a NSURL synchronously is probably not the best idea, from a user experience standpoint. Is there a reason you cannot put all the images together in a Sprite Sheet and download a single image, and then process it on the client end to display your animation?
If you really do need to go grab 70 images, it would be best to do that asynchronously so the UI remains responsive. Here is an example of simple Async Image load that could be used:
- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error )
{
UIImage *image = [[UIImage alloc] initWithData:data];
completionBlock(YES,image);
} else{
completionBlock(NO,nil);
}
}];
}
You can hold a counter variable and iterate through your image requests calling the method above. When each is complete you can tick off another successful image request. When you have them all, then you can construct that animation you are making.
While that is occurring, you can add an ActivityIndicator to your view to let the user know stuff is happening while they are waiting.
Hope that helps some.

Related

How do I check what URL an image has been set with?

I set an image using an url like this:
[self.imageView setMyImageWithURL:[NSURL URLWithString:[trip.destination getHeaderImageUrl] ]];
- (void)setMYImageWithURL:(NSURL *)url {
[self setBackground];
if(url.absoluteString.length >0){
__block UIImageView *safeSelf = self;
UIImage *placeholderImage = [UIImage imageNamed:[NSString stringWithFormat:#"placeholderpic_%#.png", NSLocalizedString(#"LanguageCode" , #"")]];
[self setImageWithURLRequest:[NSURLRequest requestWithURL:url]
placeholderImage:placeholderImage
success:nil
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error){
safeSelf.image = [UIImage imageNamed:[NSString stringWithFormat:#"placeholderpic_%#_missing.png", NSLocalizedString(#"LanguageCode" , #"")]];
}
];
}else{
self.image = [self missingImage];
}
}
Now, is there a way to check the imageView url after it has been set. Like, NSLog(#"%#", self.imageView.usingURL). I would like to check if it's the same as another url using a conditional. Something like this:
if(self.imageView.usingURL == [NSURL URLWithString:[trip.destination getHeaderImageUrl]])
Use this category to add property in UIImageView
#interface UIImageView (URLImageView)
#property NSURL* usingURL;
#end
#implementation UIImageView (URLImageView)]
#end
Now when image downloading and set to image view assign url to image view like
self.imageView.usingURL = yourURL
Now you can access this url to compare.

Load remote server image in UIScrollView with NSOperatoinQueue

I want to load some "image" (In remote server) in a UIScrollView with NSOperatoinQueue. Because If I load it with normal NSURL, NSData or with NSMutableURLRequest it takes too much time to load for all the images. After that I show those images in UIButton. Here is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
[self startAnimation:nil];
self.imageDownloadingQueue = [[NSOperationQueue alloc] init];
self.imageDownloadingQueue.maxConcurrentOperationCount = 4; // many servers limit how many concurrent requests they'll accept from a device, so make sure to set this accordingly
self.imageCache = [[NSCache alloc] init];
[self performSelector:#selector(loadData) withObject:nil afterDelay:0.5];
}
-(void) loadData
{
adParser = [[AdParser alloc] loadXMLByURL:getXMLURL];
adsListArray = [adParser ads];
displayArray = [[NSMutableArray alloc] init];
for (AdInfo *adInfo1 in adsListArray)
{
AdInfo *adInfo2 = [[AdInfo alloc] init];
[adInfo2 setBannerIconURL:adInfo1.bannerIconURL];
[adInfo2 setBannerIconLink:adInfo1.bannerIconLink];
[displayArray addObject:adInfo2];
}
[self loadScrollView];
[activityIndicator stopAnimating];
}
-(void) loadScrollView
{
[self.scrollView setScrollEnabled:YES];
[self.scrollView setContentSize:CGSizeMake([displayArray count] * ScrollerWidth, ScrollerHight)];
for (int i = 0; i < [displayArray count]; i++)
{
adButtonOutLet = [[UIButton alloc] initWithFrame:CGRectMake(i*320, 0, ButtonWidth, ButtonHight)];
currentAd = [displayArray objectAtIndex:i];
NSString *imageUrlString = [currentAd bannerIconURL];
UIImage *cachedImage = [self.imageCache objectForKey:imageUrlString];
if (cachedImage)
{
[adButtonOutLet setImage:cachedImage forState:UIControlStateNormal];
}
else
{
[self.imageDownloadingQueue addOperationWithBlock:^
{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrlString]];
UIImage *image = nil;
image = [UIImage imageWithData:imageData];
// add the image to your cache
[self.imageCache setObject:image forKey:imageUrlString];
// finally, update the user interface in the main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^
{
[adButtonOutLet setImage:image forState:UIControlStateNormal];
}];
}];
}
adButtonOutLet.userInteractionEnabled= YES;
[adButtonOutLet setTag:i];
[adButtonOutLet addTarget:self action:#selector(goToURL:) forControlEvents:UIControlEventTouchUpInside];
[self.scrollView addSubview:adButtonOutLet];
}
}
Can anyone tell me what's wrong with the above code? There is no problem of parsing or retrieving data from Remote server. I check it by NSLog. I think the NSOperationQueue have some problem, which I can't manage properly. Thanks in advance. If you needed more information, I will attach here.
Have a nice day.
Not sure if this is your problem or your solution, its hard to tell without testing myself.
Taken from RayWenderlich
addOperationWithBlock: if you have a simple operation that does not
need to be subclassed, you can create an operation using the block
API. If you want to reference any object from outside in the block,
remember that you should pass in a weak reference. Also, if you want
to do something that is related to the UI in the block, you must do it
on the main thread:
// Create a weak reference
__weak MyViewController *weakSelf = self;
// Add an operation as a block to a queue
[myQueue addOperationWithBlock: ^ {
NSURL *aURL = [NSURL URLWithString:#"http://www.somewhere.com/image.png"];
NSError *error = nil;
NSData *data = [NSData dataWithContentsOfURL:aURL options:nil error:&error];
UIImage *image = nil;
If (data)
image = [UIImage imageWithData:data];
// Update UI on the main thread.
[[NSOperationQueue mainQueue] addOperationWithBlock: ^ {
weakSelf.imageView.image = image;
}];
}];

ios: image cache and retrieval

I wish to implement caching feature for an image in iOS.
I wish to create a sample app for that, just for my understanding.
Here is what I want to do:
Get a high resolution image from internet and cache it, display it.
Now next time when this ViewController loads up, the image should be loaded from cache and not from internet.
I know how to cache an image using SDWebImage framework. I wish to try it with NSCache.
How can I implement it using NSCache? Examples are welcomed.
EDITED :
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSCache *cache = [self imageCache];
NSURL *url = [NSURL URLWithString:#"http://i1.ytimg.com/vi/YbT0xy_Jai0/maxresdefault.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [[UIImage alloc] initWithData:data];
NSString *imagePath = #"http://i1.ytimg.com/vi/YbT0xy_Jai0/maxresdefault.jpg";
// UIImage *image;
if (!(image = [cache objectForKey:imagePath])) { // always goes inside condition
// Cache miss, recreate image
image = [[UIImage alloc] initWithData:data];
if (image) { // Insert image in cache
[cache setObject:image forKey:imagePath]; // cache is always nil
}
}
_imgCache.image = image;
You can add image to cache using and Id:
[imageCache setObject:image forKey:#"myImage"];
Later you can retreive the image by using:
UIImage *image = [imageCache objectForKey:#"myImage"];
Entire flow will be like:
UIImage *image = [imageCache objectForKey:#"myImage"];
if (!image)
{
// download image
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:yourURL]];
if (imageData)
{
// Set image to cache
[imageCache setObject: [UIImage imageWithData:imageData] forKey:#"myImage"];
dispatch_async(dispatch_get_main_queue(), ^{
[yourImageView setImage:[UIImage imageWithData:imageData]];
});
}
});
}
else
{
// Use image from cache
[yourImageView setImage:image];
}
Refer the sample code to load image asynchronously and image caching,
https://developer.apple.com/library/ios/samplecode/LazyTableImages/Introduction/Intro.html

Caching with UIImage and downloaded images

I have a class method which fetches images with a completion block. This fetched UIImage is added to an NSCache with a relevant key. This seems to work as expected, however, in the method which fetches images I am using a UIImage's imageWithData: method, which I have discovered does not cache it's data, only imageNamed: does.
I am understandably getting memory warnings because of this, how do I make sure the images loaded with UIImage's imageWithData: method are removed from memory when not needed anymore?
EDIT
Here is the code for the method which downloads the images.
- (void)imageForFootageSize:(FootageSize)footageSize withCompletionHandler:(void (^)(UIImage *image))completionBlock
{
if (completionBlock) {
__block UIImage *image;
// Try getting local image from disk.
//
__block NSURL *imageURL = [self localURLForFootageSize:footageSize];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
dispatch_async(dispatch_get_main_queue(), ^{
if (image) {
completionBlock(image);
} else {
//
// Otherwise try getting remote image.
//
imageURL = [self remoteURLForFootageSize:footageSize];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
dispatch_async(dispatch_get_main_queue(), ^{
image = [UIImage imageWithData:imageData];
if (image) {
//
// Save remote image to disk
//
NSURL *photoDirectoryURL = [Footage localURLForDirectory];
// Create the folder(s) where the photos are stored.
//
[[NSFileManager defaultManager] createDirectoryAtPath:[photoDirectoryURL path] withIntermediateDirectories:YES attributes:nil error:nil];
// Save photo
//
NSString *localPath = [[self localURLForFootageSize:footageSize] path];
[imageData writeToFile:localPath atomically:YES];
}
completionBlock(image);
});
});
}
});
});
}
}
EDIT 2
Methods which use the above class method to fetch and process the UIImage in the completionHandler.
Method inside UICollectionViewCell subclass.
- (void)setPhoto:(Photo *)photo withImage:(UIImage *)image
{
[self setBackgroundColor:[UIColor blackColor]];
[self.imageView setBackgroundColor:[UIColor clearColor]];
if (photo && !image) {
[photo imageForFootageSize:[Footage footageSizeThatBestFitsRect:self.bounds]
withCompletionHandler:^(UIImage *image) {
if ([self.delegate respondsToSelector:#selector(galleryPhotoCollectionViewCell:didLoadImage:)]) {
[self.delegate galleryPhotoCollectionViewCell:self didLoadImage:image];
}
image = nil;
}];
}
[self.imageView setImage:image];
BOOL isPhotoAvailable = (BOOL)(image);
[self.imageView setHidden:!isPhotoAvailable];
[self.activityIndicatorView setHidden:isPhotoAvailable];
}
Method in UICollectionView data source delegate
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
DIGalleryPhotoCollectionViewCell *photoCell = [collectionView dequeueReusableCellWithReuseIdentifier:photoCellIdentifier forIndexPath:indexPath];
[photoCell setDelegate:self];
Footage *footage = [self footageForIndexPath:indexPath];
Photo *photo = ([footage isKindOfClass:[Photo class]]) ? (Photo *)footage : nil;
if (photo) {
//
// Photo
//
[photoCell setPhoto:photo withImage:[self.galleryCache objectForKey:photo.footageID]];
}
return photoCell;
}
Here are the other relevant methods:
- (void)galleryPhotoCollectionViewCell:(DIGalleryPhotoCollectionViewCell *)cell didLoadImage:(UIImage *)image
{
NSIndexPath *indexPath = [self.galleryCollectionView indexPathForCell:cell];
Footage *footage = [self footageForIndexPath:indexPath];
if ([footage isKindOfClass:[Footage class]]) {
Photo *photo = (Photo *)footage;
UIImage *cachedImage = [self.galleryCache objectForKey:photo.footageID];
if (!cachedImage) {
cachedImage = image;
[self.galleryCache setObject:image forKey:photo.footageID];
}
[cell setPhoto:photo withImage:image];
}
}
And also my getter method for the NSCache property galleryCache
- (NSCache *)galleryCache
{
if (!_galleryCache) {
_galleryCache = [[NSCache alloc] init];
}
return _galleryCache;
}
Instead of rolling your own image downloading and caching solution you might be better off using SDWebImage. Then you don't have to worry about the downloading, caching or anything. SDWebImage also using disk caching so you don't have to worry about freeing memory.
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:imageURL options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize)
{
// progression tracking code
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished)
{
if (image)
{
// do something with image
}
}];
I'm not sure but you also might have a retain cycle:
__weak typeof(self) weakSelf = self;
[photo imageForFootageSize:[Footage footageSizeThatBestFitsRect:self.bounds] withCompletionHandler:^(UIImage *image) {
if ([weakSelf.delegate respondsToSelector:#selector(galleryPhotoCollectionViewCell:didLoadImage:)])
{
[weakSelf.delegate galleryPhotoCollectionViewCell:weakSelf didLoadImage:image];
}
image = nil;
}];

Is there a way to load a text string first and then the image using AFNetworking?

I'm using AFNetworking to parse JSON to my app (using Rails as my backend). Right now my app is very slow so I'm trying to figure out a way to make it smoother. When I first load the app it takes a few seconds for it to populate (it shows the Nav items and a white page, then a few seconds later my "posts" appear).
Collection View Controller
- (void)viewDidLoad
{
[super viewDidLoad];
self.upcomingReleases = [[NSMutableArray alloc] init];
[self makeReleasesRequests];
[self.collectionView registerClass:[ReleaseCell class] forCellWithReuseIdentifier:#"ReleaseCell"];
}
-(void)makeReleasesRequests
{
NSURL *url = [NSURL URLWithString:#"http://www.soleresource.com/upcoming.json"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"#");
self.upcomingReleases = [responseObject objectForKey:#"upcoming_releases"];
[self.collectionView reloadData];
} failure:nil];
[operation start];
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [self.upcomingReleases count];
}
#pragma mark - Show upcoming release shoe
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"Cell";
ReleaseCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
NSDictionary *upcomingReleaseDictionary = [self.upcomingReleases objectAtIndex:indexPath.row];
NSString *thumbURL = nil;
cell.release_name.text = [NSString stringWithFormat:#"%# — $%#",[upcomingReleaseDictionary objectForKey:#"release_name"], [upcomingReleaseDictionary objectForKey:#"release_price"]];
if ([upcomingReleaseDictionary[#"images"] isKindOfClass:[NSArray class]] && [upcomingReleaseDictionary[#"images"] count]) {
thumbURL = upcomingReleaseDictionary[#"images"][0][#"image_file"][#"image_file"][#"thumb"][#"url"];
if (thumbURL)
{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:thumbURL]];
UIImage *image = [UIImage imageWithData:imageData];
cell.thumb.image = image;
}
}
else {
cell.thumb.image = [UIImage imageNamed:#"air-jordan-5-fear.png"];
}
return cell;
}
Each of my posts has a text string and a image. Is there a way to load the text so that it appears right away and then load my image? Or is there another way to speed up my app load speed (Maybe loadin a certain of posts first and then loading the rest - the ones that the user cant see until they scroll down).
Thanks.
You should load your image lazily and asynchronously (DON'T block main thread) when coming from server. (AFNetworking already has caching category method on UIImageView. (Check out this for more)
if (thumbURL)
{
[cell.thumb setImageWithURL:[NSURL URLWithString:thumbURL] placeholderImage:[UIImage imageNamed:#"air-jordan-5-fear.png"]];
}
EDIT -
Ensure to pull UIKit+AFNetworking folder into your project and #import "UIKit+AFNetworking.h" into your .m file. The link to download complete AFNetworking can be found here and documentation specific to this question here.
Your problem is this:
if (thumbURL)
{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:thumbURL]];
UIImage *image = [UIImage imageWithData:imageData];
cell.thumb.image = image;
}
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:thumbURL]];
UIImage *image = [UIImage imageWithData:imageData];
You should never be getting data in cellForItemAtIndexPath:. You should only be displaying what you have already. Your code makes it so no cell is returned until a thumbnail is downloaded. You can measure this using the Time Profiler in Instruments.
I'm assuming thumb is a UIImageView. Try this:
if (thumb) {
[thumb setImageWithURL:[NSURL URLWithString:#"http://i.imgur.com/r4uwx.jpg"]];
}
This method, also included with AFNetworking, will download the image, and update it in the cell once it's done downloading. Documentation and other similar methods are here.

Resources