iOS: SDWebImageManager not caching image - objective-c

I'm creating a slideshow using UIImageView, and the image links are in an array , so while I was at it, I learned that SDWebImageManager lets hit the URLs once only and then it caches the images for later use.
But what I'm monitoring in my app is that the 1st image is cached, I believe, but the 2nd image URL is always being hit.
Here's my code:
- (void)viewDidLoad {
[super viewDidLoad];
arry = [[NSMutableArray alloc] init];
[arry addObject:#"http://adjingo.2cimple.com/content/151/Image/6291.jpg"];
[arry addObject:#"http://adjingo.2cimple.com/content/151/Image/6290.jpg"];
NSURL *imageURL = [NSURL URLWithString:[arry objectAtIndex:0]];
__block UIActivityIndicatorView *activityIndicator;
__weak UIImageView *weakImageView = self.imageView;
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:imageURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
if (!activityIndicator) {
[weakImageView addSubview:activityIndicator = [UIActivityIndicatorView.alloc initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]];
//activityIndicator.center = self.imageView.center;
[activityIndicator startAnimating];
}
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
// do something with image
[activityIndicator removeFromSuperview];
activityIndicator = nil;
[self.imageView setImage:image];
}
}];
//Timer to do slideshow for images
timer = [NSTimer scheduledTimerWithTimeInterval: 5.0
target: self
selector: #selector(handleTimer:)
userInfo: nil
repeats: YES];
}
Here's the handleTimer code, to reload image in image view, every 5 seconds:
-(void) handleTimer: (NSTimer *) timer {
currentImage++;
if ( currentImage >= arry.count )
currentImage = 0;
__block UIActivityIndicatorView *activityIndicator;
__weak UIImageView *weakImageView = self.imageView;
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:imageURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
if (!activityIndicator) {
[weakImageView addSubview:activityIndicator = [UIActivityIndicatorView.alloc initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]];
//activityIndicator.center = self.imageView.center;
[activityIndicator startAnimating];
}
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
// do something with image
[activityIndicator removeFromSuperview];
activityIndicator = nil;
[self.imageView setImage:image];
}
}];
}
Here's where I monitor the network usage:
Please guide me if I have used the SDWebImageManagerwrongly.
Thanks

my mother language is Chinese,not English.Maybe I cannot express my thought clearly,while I will try my best to tell my idea.if I confuse you , I feel sorry.
I cannot pod SDWebimage because my country blocks google sometimes,so I cannot reproduce your scenarios.While I still give your some advice which may help you
First of all, your gave us little context.Maybe you can post more information about member variables and properties.When I copy your code to the Xcode.I need add them by myself.
Second,you mean when you use
NSURL *imageURL = [NSURL URLWithString:[arry objectAtIndex:1]];,
sdwebimage hits urls every time , not use cache URLs? you can get the image source by NSLog the cacheType.
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
NSLog(#"%d",cacheType);
[activityIndicator removeFromSuperview];
activityIndicator = nil;
[self.imageView setImage:image];
}
}];`
SDImageCacheTypeNone means image comes from network.
SDImageCacheTypeDisk means image comes from disk.
SDImageCacheTypeMemory means image comes from Memory.
Third,because the downloadWithURL:options:completed: is excuted not on the main thread. I doubt the sequence is the same with your thought.

Related

variable becomes nil in numberOfRowsInTableView after being set in another method

My class looks like this :
#interface ApplicantPickerController : AppPage <NSTableViewDataSource, NSTableViewDelegate>
{
School *school;
__weak IBOutlet NSTableView *tableView;
NSMutableArray *familyList;
__weak IBOutlet NSProgressIndicator *progressIndicator;
}
- (IBAction)alphabetButtonPressed:(id)sender;
#end
In the alphabetButtonPressed method, I'm fetching a json array from a webservice and assigning it to familyList. After doing this, I do [tableView reload];
When the control passes to the - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView method, familyList becomes nil. Why is this happening and how can I fix it?
I'm using ARC for this project.
Cocoa/Objective-C newbie here. Any help would be much appreciated. Thank you!
Updated - Here is the implementation of the class :
#interface ApplicantPickerController ()
#end
#implementation ApplicantPickerController
- (IBAction)alphabetButtonPressed:(id)sender {
[progressIndicator startAnimation:self];
NSString * addy = [[NSString alloc] initWithFormat:#"%#.php?function=applicant_lookup&schoolID=%#&alpha=%#&currentYear=%#&format=json", BASE_URL_SCHOOL, school->recordID, [sender title], school->CurrentYear];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:addy]];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if(data) {
//NSString * resp = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSError *error = nil;
NSObject *json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
if([json isKindOfClass:[NSDictionary class]])
{
familyList = nil;
[tableView reloadData];
}
else if ([json isKindOfClass:[NSArray class]])
{
familyList = [[NSMutableArray alloc] init];
[familyList addObjectsFromArray:(NSArray*)json];
//[_familyList retain];
[tableView reloadData];
}
[progressIndicator stopAnimation:self];
}
}];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [familyList count];
}
- (id)initWithMainView:(NSView *)_theView AndMainController:(NSViewController *)_theViewController AndNibName:(NSString *)nibName AndArgs:(NSArray *)_args
{
self = [super initWithMainView:_theView AndMainController:_theViewController AndNibName:nibName AndArgs:_args];
school = [args objectAtIndex:0];
return self;
}
#end
I don't know if I discovered a bug by apple, but following is how I solved this issue.
I was setting the NSTableView delegate and datasource in the UI builder (by right clicking and making the connections with the mouse). For some reason, if I set the delegate and dataSource in the code (specifically in the awakeFromNib method), the issue gets resolved.
- (void)awakeFromNib {
tableView.delegate = self;
tableView.dataSource = self;
}

I have some remote images I'm loading into an NSMutableArray but the array isn't populated by the time I need it

Well after troubles with swipe gesture and my own image gallery slider I decided to go with a premade one.
I'm using iCarousel and it works fine. I've tested using an array that is populated with non remote images.
Inside init with coder I done this:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
_additionalGarmentImagesArray = [[NSMutableArray alloc] init];
UIImage *image1 = [UIImage imageNamed:#"asos.png"];
UIImage *image2 = [UIImage imageNamed:#"neck.png"];
UIImage *image3 = [UIImage imageNamed:#"diamind.png"];
[_additionalGarmentImagesArray addObject:image1];
[_additionalGarmentImagesArray addObject:image2];
[_additionalGarmentImagesArray addObject:image3];
}
return self;
}
This works fine. However it's not useful to me as my images are remote images. So I've taken the code I used with my own image gallery but the images don't get downloaded on time.
In my viewDidLoad:
PFQuery *query = [PFQuery queryWithClassName:#"Garments"];
[query whereKey:#"title" equalTo:[self garmentTitle]];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
PFFile *additionalImage1 = [object objectForKey:#"image2"];
PFFile *additionalImage2 = [object objectForKey:#"image3"];
PFFile *additionalImage3 = [object objectForKey:#"image4"];
PFFile *additionalImage4 = [object objectForKey:#"image5"];
PFFile *additionalImage5 = [object objectForKey:#"image6"];
// Add main image to array first, the one passed over from previous controller
[_additionalGarmentImagesArray addObject:[self garmentImage]];
if (additionalImage1) {
PFImageView *pfImageView1 = [[PFImageView alloc] init];
[pfImageView1 setFile:additionalImage1];
[pfImageView1 loadInBackground:^(UIImage *image, NSError *error) {
if (!error) {
[_additionalGarmentImagesArray addObject:image];
}
}];
}
if (additionalImage2) {
PFImageView *pfImageView2 = [[PFImageView alloc] init];
[pfImageView2 setFile:additionalImage2];
[pfImageView2 loadInBackground:^(UIImage *image, NSError *error) {
if (!error) {
[_additionalGarmentImagesArray addObject:image];
}
}];
}
if (additionalImage3) {
PFImageView *pfImageView3 = [[PFImageView alloc] init];
[pfImageView3 setFile:additionalImage3];
[pfImageView3 loadInBackground:^(UIImage *image, NSError *error) {
if (!error) {
[_additionalGarmentImagesArray addObject:image];
}
}];
}
} else {
NSLog(#"empty array");
}
}];
This is where the array of images is needed:
- (NSInteger)numberOfItemsInCarousel:(iCarousel *)carousel
{
return [_additionalGarmentImagesArray count];
}
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view
{
PFImageView *imageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, 300.0f, 380)];
view = imageView;
//set image
((PFImageView *)view).image = _additionalGarmentImagesArray[index];
return view;
}
The viewDidAppear method shows array is populated:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"Images Count: %i", [_additionalGarmentImagesArray count]);
}
Please help me solve this issue with solutions. My images are stored remotely and I need a way to have them ready by the time the controller is loaded so they can be used by iCarousel or maybe have iCarousel wait then refresh itself when the array is populated.
Maybe even some how do something in the previous controller which is a UICollectionView. I could tap the cell then get the images I need from the cells object and populate an array then. The only allow the push/segue to happen once the images are in the array. However I don't like that. It means a user would have to wait after they tap to go to the detail view controller where the iCarousel is.
Would appreciate some solutions if possible. It's been a few hours and I've made little progress.
Thanks for your time
Solution was to reloadData at a time where I did have the array. So the best place to do this was inside the viewDidAppear method.
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view
{
PFImageView *imageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, 300.0f, 400)];
view = imageView;
//create new view if no view is available for recycling
if (view == nil)
{
//set image
((PFImageView *)view).image = _additionalGarmentImagesArray[index];
} else {
//set image
((PFImageView *)view).image = _additionalGarmentImagesArray[index];
}
return view;
}
Reload Data:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"Images Count: %i", [_additionalGarmentImagesArray count]);
[[self carousel] reloadData];
}

iCarousel + Parse: Loading Async Images from PFObject

I am trying to populate an iCarousel with a parse (parse.com, pfoject) object without success.
I can't find any example of how to do it, so it why i am asking here...
- (void)viewDidLoad
{
[super viewDidLoad];
_items = [NSMutableArray array];
PFQuery *query = [PFQuery queryWithClassName:#"testUser"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded. The first 100 objects are available in objects
[_items addObjectsFromArray:objects];
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
//configure carousel
_carousel.type = iCarouselTypeRotary;
//_carousel.viewpointOffset = CGSizeMake(0.0f, 100.0f);
[_carousel setContentOffset:CGSizeMake(0.0f, -60.0f)];
}
And at viewForItemAtIndex:
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view
{
//create new view if no view is available for recycling
if (view == nil)
{
view = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 300.0f, 300.0f)];
}
PFObject *eachObject = [_items objectAtIndex:index];
PFFile *theImage = [eachObject objectForKey:#"image"];
NSData *imageData = [theImage getData];
UIImage *image = [UIImage imageWithData:imageData];
((UIImageView *)view).image = image;
view.contentMode = UIViewContentModeCenter;
return view;
}
Please, any help? :-(
In viewDidLoad, After the
[_items addObjectsFromArray:objects];
Put
[_carousel reloadData];
Otherwise the carousel won't be updated after the items have been added to the array, so you'll still be looking at an empty carousel.

svprogresshud not showing in xcode5 ios7

I am trying to use SVProgressHUD with cocoapods. I have install it in my xcode workspace following numerous tutorials online. It seems simple enough to use, but I cannot get it to work. Here my code to load data from rottentomatoes app. I want to show "loading" while the network request is doing its thing. I am wondering if this is ui thread issue ? I added a sleep inside the network request because the results were coming back too fast !
- (void) reload{
[SVProgressHUD showWithStatus:#"Updating" maskType:SVProgressHUDMaskTypeBlack];
NSString *url = #"http://api.rottentomatoes.com/api/public/v1.0/lists/dvds/top_rentals.json?";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
[NSThread sleepForTimeInterval:3];
NSDictionary *object = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSMutableArray *array = [[NSMutableArray alloc]initWithArray:[object objectForKey:#"movies"]];
self.movies = [[NSMutableArray alloc]init];
for(NSDictionary *item in array){
SFMovie *movie = [[SFMovie alloc]initWithDictionary:item];
[self.movies addObject:movie];
}
[SVProgressHUD dismiss];
[self.tableView reloadData];
}];
}
EDIT for comment:
The reload is called on both init methods (since I am using storyboard for this project)
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
[self reload];
}
return self;
}
- (id) initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if(self){
[self reload];
}
return self;
}
Second EDIT:
I added a pull down to refresh and the "updating ..." shows up when I pull the tableview down. But it does not show up on init.
- (void)viewDidLoad
{
[super viewDidLoad];
UIRefreshControl *refreshControl = [UIRefreshControl new];
[refreshControl addTarget:self action:#selector(refresh:) forControlEvents:UIControlEventValueChanged];
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#"Pull to refresh..."];
self.refreshControl = refreshControl;
}
- (void)refresh:(UIRefreshControl *)sender{
[self reload];
[sender endRefreshing];
}
So I guess I cannot run UI stuff on init methods as the view is not ready. I am now calling the reload method on viewdidload and it works file. Thank you John !
In any init methods, messing with the view hierarchy is tricky business -- keep that logic in viewDidLoad to avoid any of those modifications from being wiped (especially from storyboard initialization).

Need assistance regarding downloading data using NSURLConnection

My app is a messaging app, it can also send image files. I am just uploading as image on web server and on other side just sending its url, with NSURLConnection i am trying to download an image with UIProgressView as a download indicator, here is my code:
This method is called when download button in uitableview is clicked, it removes the download button, add uiprogressview and start downloading
-(void)downloadImage:(UIButton *)link
{
UITableViewCell *cell = (UITableViewCell*)[link superview];
NSIndexPath *pathToCell = [tView indexPathForCell:cell];
NSMutableDictionary *checkItHasFile = [messages objectAtIndex:pathToCell.row];
NSString *str = [checkItHasFile objectForKey:#"hasFile"];
if([str isEqualToString:#"1"])
{
progress = [[UIProgressView alloc]initWithProgressViewStyle:UIProgressViewStyleBar];
progress.frame = CGRectMake(10, 50, 160, 30);
progress.progress = 0.0;
//progress.center = CGPointMake(23,21);
[cell addSubview:progress];
}
UIButton *view = [[UIButton alloc]init];
NSArray *subviews = [cell subviews];
for (view in subviews)
{
if([view isKindOfClass:[UIButton class]])
{
[view removeFromSuperview];
}
}
//
NSString *linkToPass = [NSString stringWithFormat:#"THE URL"];
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:linkToPass]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection)
{
nsmd = [[NSMutableData alloc]init];
}
else
{
NSLog(#"Connection to server failed!");
}
...
This method is a NSURLConnection delegate to indicate about response, in this I am calculating response size
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
[self.resourceData setLength:0];
self.filesize = [NSNumber numberWithLongLong:[response expectedContentLength]];
}
This method is a NSURLConnection delegate to indicate about received data, I am updating progress bar by doing some calculation
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
resourceData = [[NSMutableData alloc]init];
[self.resourceData appendData:data];
NSNumber *resourceLength = [NSNumber numberWithUnsignedInteger:[self.resourceData length]];
[self.progress setProgress:[resourceLength floatValue] / [self.filesize floatValue] animated:YES];
}
I want to know when downloding is finished so i can also remove progress view for that the delegate method is connectionDidFinishLoading:connection of NSURLConnection.
Problem is it immediately triggers, so when progress view is animating the download progress this method also executes, if I am removing the progress view here, the progress view will disappear immediately with out indicating a complete download progress.
How to solve this issue?
Define a method that removes the progressbar, then in your - (void)connectionDidFinishLoading:(NSURLConnection *)connection implementation, if the resource length is less than a certain amount you decide, call the remove method with a delay:
[self performSelector:#selector(removeProgressBar) withObject:nil afterDelay:2];
otherwise call the same method without any delay.