I am using a UIViewController (1st) that displays a list of comics where a user can select a comic to download.
The download takes place in another UIViewController (2nd) that has a tableView (with a custom download cell).
I have an NSMutableArray declared in the 2nd viewController with all the comic URLs and it's tableView gets loaded from this array.
The problem is:
Whenever I add a downloadable item to the list and open the 2nd viewController, the tableView gets loaded from the beginning and all the files start to download again.
I need a way so that I can add the files from the 1st viewController to 2nd viewController's tableView and the downloads should continue from the same stage as they were previously (and not from the start again)
Below is the code of how my DownloadCell works. Each cell is downloading multiple files. The first method is called while a cell is being created.
-(void)startDownload:(NSArray*)comicFiles
{
allComicFiles=comicFiles;
downloadedFiles=[[NSMutableArray alloc] init];
[self removeViews];
[self appendViewsToCell];
[loading startAnimating];
[self downloadFile:0];
}
Now, the second method is called to download the files asynchronously.
-(void)downloadFile: (int)index
{
if(index>=[allComicFiles count])
{
[self downloadComplete];
return;
}
SingleComicFile *comicFile=allComicFiles[index];
NSURL *url=comicFile.URL;
NSString *fileName=comicFile.FileName;
lblFileName.text=fileName;
NSString *applicationDocumentsDir =
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *storePath = [applicationDocumentsDir stringByAppendingPathComponent:[NSString stringWithFormat:#"%#",fileName]];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:storePath]){
[self updateDownloadProgress:url];
[self downloadFile:index+1];
}
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:url];
dispatch_sync(dispatch_get_main_queue(), ^{
[data writeToFile:storePath atomically:TRUE];
[self updateDownloadProgress:url];
[self downloadFile:index+1];
});
});
}
The tablewviecontroller class has a mutable array which is basically a array of file array. It has a constructor which I call from the comic listing page to populate the array.
-(MyDownloadManager *)initFileQueue: (NSMutableArray *)withComicFileQueue
{
if(_fileArray==nil)
{
_fileArray= [[NSMutableArray alloc]init];
}
_fileArray=withComicFileQueue;
return self;
}
And I am binding the tableview like
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
DownloadCell *cell = [tableView dequeueReusableCellWithIdentifier:nil];
if (cell == nil) {
cell = [[DownloadCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSArray *comicFiles= _fileArray[indexPath.row];
[cell startDownload:comicFiles];
return cell;
}
Maybe in your case the solution is to make shareViewController that will be initiated once (loadView, viewDidLoad will be called once). Also when you close shared loading view controller it is not reallocated and continues to download selected file. You can implement some delegate method to notify download is completed and so on. In this way you will get shared long-living downloader.
static DownloadViewController *sharedInstance = nil;
#implementation DownloadViewController
+ (id) sharedInstance
{
static dispatch_once_t once;
dispatch_once(&once, ^{
sharedInstance = [DownloadViewController new];
});
return sharedInstance;
}
Related
I have local JSON and have display on the first page.
After that I want to select one of the row from tableview. When I select the row, it will be display the other JSON from url, different rows have different json. However, I don't know how to find out the one of row. I try many ways but it can't work.
Here is the code of the first page:
#implementation HKTableViewController{
NSArray *_location_array,*_hki;
}
- (void)viewDidLoad {
[super viewDidLoad];
JSONLoader *location_jsonLoader = [[JSONLoader alloc] init];
NSURL *url = [[NSBundle mainBundle] URLForResource:#"locations" withExtension:#"json"];
//NSURL *url = [NSURL URLWithString:#"http://127.0.0.1/funhiking/index.php/index/getHKIsland"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
_location_array = [location_jsonLoader locationsFromJSONFile:url];
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
});
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
DistrictTableViewController *district = segue.destinationViewController;
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
district.location = [_location_array objectAtIndex:indexPath.row];
if (indexPath.row == 0) {
// Create a new JSONLoader with a local file URL
JSONLoader *jsonLoader = [[JSONLoader alloc] init];
//go to web to take json file
NSURL *url = [NSURL URLWithString:#"http://127.0.0.1/funhiking/index.php/index/getHKIsland"];
// Load the data on a background queue...
// As we are using a local file it's not really necessary, but if we were connecting to an online URL then we'd need it
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
_hki = [jsonLoader hkiFromJSONFile:url];
// Now that we have the data, reload the table data on the main UI thread
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
});
}
}
#pragma mark - Table View Controller Methods
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"HKCell"];
Locations *location = [_location_array objectAtIndex:indexPath.row];
cell.textLabel.text = location.title;
NSLog(#"%#", location.title);
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [_location_array count];
}
You can use didselectrowatindex delegate method of table view after setting delegate of table view, then you can get object from your array in this method and and can simply show the JSON url.
Happy coding...
i'm having a viewcontroller which has show segue to next view controller
but for each button which connected to show segue having webservice to call and response from webservice is storing in plist file and accessing in next view
my issue:when i click on button it is directly going to nextview without loading plist contents first time but when i'm go back and click on button it is showing plist contents
any help most appreciated
here is my plist creation code it is creating well but after the button click
if(connection == conn1)
{
NSError *e = nil;
classResponse = [NSJSONSerialization JSONObjectWithData:classData options:NSJSONReadingMutableLeaves error:&e];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"classesArray.plist"];
NSLog(#"file path is %# ",path);
NSFileManager *fileManager=[NSFileManager defaultManager];
if(![fileManager fileExistsAtPath:path])
{
NSString *bundle = [[[NSBundle mainBundle]resourcePath]stringByAppendingString:#"classesArray.plist"];
//NSString *bundle = [[NSBundle mainBundle]pathForResource:#"create" ofType:#"plist"];
[fileManager copyItemAtPath:bundle toPath:path error:&error];
}
[classResponse writeToFile:path atomically:YES];
}
i'm using tableview with custom cell in next view to load from plist
here is my code
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [className count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"cellId";
classesTeacher *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier
forIndexPath:indexPath];
if (cell == nil) {
cell = [[classesTeacher alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}
long row = [indexPath row];
cell.classname.text = className[row];
cell.classdescription.text = classDescription[row];
cell.classCode.text = classRef[row];
cell.studentcount.text=classIds[row];
return cell;
}
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"classesArray.plist"];
NSDictionary *StudentDict=[[NSDictionary alloc]initWithContentsOfFile:path];
// NSLog(#" QuizDict has %# ",StudentDict);
//get all values for a key in array
NSArray *See = StudentDict[#"alldata"];
className= [See valueForKey:#"class_name"];
classDescription=[See valueForKey:#"class_description"];
classRef=[See valueForKey:#"class_ref"];
classIds=[See valueForKey:#"number_of_students"];
//classId = [See objectAtIndex:#"classid"];
NSLog(#"class id %#",classId);
NSLog(#"class id is %#",classIds);
UIImage *backImage = [UIImage imageNamed:#"s-left-arrow.png"];
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
backButton.frame = CGRectMake(0, 0, backImage.size.width, backImage.size.height);
[backButton setImage:backImage forState:UIControlStateNormal];
[backButton addTarget:self action:#selector(pushBackButton:) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton] ;
self.navigationItem.hidesBackButton = YES;
self.navigationItem.leftBarButtonItem = backBarButtonItem;
[self.table reloadData];
// Do any additional setup after loading the view.
}
This sends up a red flag for me, I don't understand why you would be storing data in a plist and accessing it later unless you are using this as a way to pass data between the two views.
Regardless - if the data is not available when you need it at transition time then you have a few choices: 1 - get the data before you transition to the new view. This will require that you hold up the transition until the data is returned; 2 - get the data after you transition to the new view. This will require that you pass enough data to the new view that it can make the request.
Need to be very careful with option 1. If you start a request and then transition to the new view the view controller for the first view will go out of scope leaving the return data with no where to go and possibly calling a method that no longer exists (app crash).
Here is a good article (one of many) on callbacks and delegates.
delegate function vs callback function
This can go in either the first or second view controller depending on when you decide to get the data.
If you put it in the first view controller you will want to change your segue and have it trigger manually rather than in the storyboard on the button. You will still create a segue in the storyboard but it will not be assigned to the button. Assign it generically to the view and be sure to give it a storyboard name so it can be referenced later. Re assign the button action to trigger the download. In the completion block for your callback (or in the delegate method) call the segue manually once the data has returned.
[self performSegueWithIdentifier: #"YourSegueName" sender: self];
If you decide to request the data in the second view controller then you can use the prepare for segue method to pass enough data to this method to complete the data request
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"YourSegueName"]) {
YourSecondVC *secondVC = (YourSecondVC *)segue.destinationViewController;
secondVC.dataForRequest= #"request this data;
}
This assumes your second view controller declares an NSString variable named "dataForRequest"
Because the second view controller has been created by the system at this point you can use this to assign data to it's member objects even if they are a complex data type.
I just want the user to just delete a picture by just tapping the picture using a collection view. I am doing this for a month and can't get things straight and even getting confused. My code for to do this is below. I am also getting a memory warning sometimes aswell, If i use the instruments app to look for detail the memory allocation does not get freed and just builds up until crash.
- (void)viewDidLoad
{ Trash = [NSMutableArray array];
filenames = [NSMutableArray new];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSArray *locationStrings = [[NSArray alloc]initWithObjects:#"Bottoms", #"Dress", #"Coats", #"Others", #"hats", #"Tops",nil ];
for(NSString* location in locationStrings){
NSString *fPath = [documentsDirectory stringByAppendingPathComponent:location];
NSError *error;
NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:fPath error:&error];
collectionTrash.delegate =self;
collectionTrash.dataSource=self;
for(NSString *str in directoryContent){
NSString *finalFilePath = [fPath stringByAppendingPathComponent:str];
[filenames addObject:finalFilePath];
}
}
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
NSLog(#"j");
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [filenames count];
NSLog(#"b");
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"ReuseID";
TrashCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
UIImageView *imageInCell = (UIImageView*)[cell viewWithTag:1];
NSString *cacheKey = filenames[indexPath.item];
imageInCell.image = [self.imageCache objectForKey:cacheKey];
if (imageInCell.image == nil) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageWithContentsOfFile:filenames[indexPath.item]];
if (image) {
[self.imageCache setObject:image forKey:cacheKey];
dispatch_async(dispatch_get_main_queue(), ^{
TrashCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];
UIImageView *imageInCell = (UIImageView*)[updateCell viewWithTag:1];
imageInCell.image = image;
});
}
});
}
return cell;
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"s:%d", [Trash count]);
NSString *trashBin = [Trash objectAtIndex:indexPath.row];
NSLog(#"k%#l",trashBin);
[filenames removeObjectAtIndex:indexPath.row];
[Trash removeObjectAtIndex:indexPath.row];
[self deleteMyFiles:trashBin];
[collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil]];
}
NSString *myFileName;
-(void) deleteMyFiles:(NSString*)filePath {
NSError *error;
if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
[[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
} else {
NSLog(#"%#",filePath);
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
[self.imageCache removeAllObjects];
}
When I try to tap a log comes out saying below.
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'
*** First throw call stack:
(0x30afbe83 0x3ae5c6c7 0x30a31d95 0x638e9 0x3349d76f 0x3349d495 0x33406ea3 0x33406ea3 0x332781a1 0x332af9fd 0x332af3ab 0x33284d79 0x33283569 0x30ac6f1f 0x30ac63e7 0x30ac4bd7 0x30a2f471 0x30a2f253 0x357632eb 0x332e4845 0x628a5 0x3b355ab7)
libc++abi.dylib: terminating with uncaught exception of type NSException
Your Trash array not content any objects.
And you are trying to access object from Trash array
NSString *trashBin = [Trash objectAtIndex:indexPath.row];
Which is the cause of crash.
Note: object name should start with small letters.
In your code, Trash = [NSMutableArray array]; will be autoreleased at some time, so when you access it in '-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath', it has been released. If you are sure there is reason to use this variable instead of using only filenames, you can do this Trash = [[NSMutableArray array] retain]; or Trash = [[NSMutableArray alloc] init]; in your '- (void)viewDidLoad'.
It is not clear what you are trying to do. The notion of "Trash" suggests you are trying to implement a two-stage delete: put items in the trash and at some later time, empty the trash (delete them for good when the user selects an "Empty the Trash" button). But your post and your code suggest a single-stage delete (the user selects to delete with no chance to undo).
Your collectionView:didSelectItemAtIndexPath: delegate method looks particularly confused. As Bhumeshwer correctly points out, your Trash is empty and trying to access any index is causing your crash. No where in this code are you putting anything in Trash But, by simply removing that line of code, I don't think the method will do what you want.
If you are implementing a two-stage delete, I think you simply want to remove the string from filenames and add it to Trash. Don't remove the object from Trash and don't delete files in Trash. Somewhere else (like when the "Empty the Trash" button is selected), call deleteMyFiles to empty the trash.
If you are implementing a single-stage delete, get rid to Trash all together: don't allocate it in viewDidLoad and don't empty it in didSelectItemAtIndexPath. Simply, remove the object from filenames AND update the imageInCell!
Suggestions:
move
collectionTrash.delegate =self;
collectionTrash.dataSource=self;
to wherever collectionTrash is being created and initialized. Inside viewDidLoad and inside the for loop, it is out of context. Your mixing unrelated lines of code making your code unorganized and confusing. At least, move it out of the for loop.
think model-view-controller. What's the model here? Is it filenames or Trash? Is the CollectionView a view of filenames or Trash? What is collectionTrash?
Trash should be trash (as Bhumeshwer points out);
what is NSString *myFileName; and what's it doing there?
don't forget to manage your imageCache. You put images in but don't take them out. When you remove a file from filenames, remove the image from imageCache.
I could go on, but will stop here. Good luck.
I am building an app that will allow users to download and read issues of a journal. I am using the Download Manager framework created by Robert Ryan and I modified the test project that came with the framework to have it work in my project (there are no issues with the framework). On each row in the table there is a Issue Cover Image (UIImageView), Download/Read label (UILabel), Issue Date label (UILabel), and a progress bar (UIProgressView) all are properties of a UITableViewCell. When a user taps the row, it initiates the download process of the issue which is reflected in the progress bar; after the download completes, the progress bar becomes hidden and the Download title of the label changes to Read, and when the user taps the row again to read the downloaded journal it opens a PDF viewer in a viewcontroller. I haven't added the Read functionality as yet. All this works fine except as a test I have 2 issues of the journal in the table each in a row with its ``. When I tap the first row, the progress bar reflects the download progress and it works fine. However, when I tap the second row, the download progress is reflected in the progress bar of the first row not the second row as expected (the progress bar remains static). It does download the second journal and everything works fine. It's just this unexpected behavior where the download progress of the second row is reflected in the progress bar in the first row. I still have to streamline the code and clean it up but the relevant code sections are below:
// optional method to indicate progress of individual download
//
// In this view controller, I'll update progress indicator for the download.
- (void)downloadManager:(DownloadManager *)downloadManager downloadDidReceiveData: (Download *)download;
{
for (NSInteger row = 0; row < [downloadManager.downloads count]; row++)
{
if (download == downloadManager.downloads[row])
{
[self updateProgressViewForIndexPath:[NSIndexPath indexPathForRow:row inSection:0] download:download];
break;
}
}
}
#pragma mark - Table View delegate and data source methods
// our table view will simply display a list of files being downloaded
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return[jitsArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"DownloadCell";
DownloadCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[DownloadCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
jits * jitsInstance = nil;
jitsInstance = [jitsArray objectAtIndex:indexPath.row];
cell.issue.text = jitsInstance.issue;
NSString * myCoverURL = [NSString stringWithFormat:#"%#", jitsInstance.coverimage];
UIImage* myImage = [UIImage imageWithData:
[NSData dataWithContentsOfURL:
[NSURL URLWithString: myCoverURL]]];
cell.coverimage.image = myImage;
[cell.progressView setProgress:0];
NSString * myURL = [NSString stringWithFormat:#"%#", jitsInstance.url];
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *downloadFolder = [documentsPath stringByAppendingPathComponent:#"downloads"];
NSString * fileName = [[NSString alloc]initWithFormat:#"%#", [myURL lastPathComponent]];
NSString* foofile = [downloadFolder stringByAppendingPathComponent:fileName];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:foofile];
NSLog(#"Search file path: %#", foofile);
if (!fileExists) {
[cell.downloadButton setTitle:#"Download" forState:normal];
[cell.progressView setHidden:NO];
NSLog(#"File does not exist!");
}
else if (fileExists){
NSLog(#"File exist!");
[cell.downloadButton setTitle:#"Read" forState:normal];
[cell.progressView setHidden:YES];
}
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *downloadFolder = [documentsPath stringByAppendingPathComponent:#"downloads"];
jits * jitsInstance = nil;
jitsInstance = [jitsArray objectAtIndex:indexPath.row];
NSString * myURL = [NSString stringWithFormat:#"%#", jitsInstance.url];
self.downloadManager = [[DownloadManager alloc] initWithDelegate:self];
self.downloadManager.maxConcurrentDownloads = 4;
NSString *downloadFilename = [downloadFolder stringByAppendingPathComponent:[myURL lastPathComponent]];
NSURL *url = [NSURL URLWithString:myURL];
[self.downloadManager addDownloadWithFilename:downloadFilename URL:url];
self.cancelButton.enabled = YES;
self.startDate = [NSDate date];
[self.downloadManager start];
}
#pragma mark - Table view utility methods
- (void)updateProgressViewForIndexPath:(NSIndexPath *)indexPath download:(Download *)download
{
DownloadCell *cell = (DownloadCell *)[self.tableView cellForRowAtIndexPath: [NSIndexPath indexPathForRow:indexPath.row inSection:0]];
// if the cell is not visible, we can return
if (!cell)
return;
if (download.expectedContentLength >= 0)
{
// if the server was able to tell us the length of the file, then update progress view appropriately
// to reflect what % of the file has been downloaded
cell.progressView.progress = (double) download.progressContentLength / (double) download.expectedContentLength;
}
else
{
// if the server was unable to tell us the length of the file, we'll change the progress view, but
// it will just spin around and around, not really telling us the progress of the complete download,
// but at least we get some progress update as bytes are downloaded.
//
// This progress view will just be what % of the current megabyte has been downloaded
cell.progressView.progress = (double) (download.progressContentLength % 1000000L) / 1000000.0;
}
}
I think your issue may lie in the following code:
for (NSInteger row = 0; row < [downloadManager.downloads count]; row++)
{
if (download == downloadManager.downloads[row])
{
[self updateProgressViewForIndexPath:[NSIndexPath indexPathForRow:row inSection:0] download:download];
break;
}
}
What this seems like it's essentially doing is finding the first cell in the downloads array, calling updateProgressViewForIndexPath on that first cell it finds, then stopping. There are a number of ways to fix this issue, but the first that comes to mind is once you tell yourself to update the cell at that index path when the if-statement evaluates to true, remove that item from the downloadManager's downloads array, so next time through it won't be there. Give that a try and let me know if that works..
Also, on a side note... I would think that you don't want to do the following two lines every time a row is selected:
self.downloadManager = [[DownloadManager alloc] initWithDelegate:self];
self.downloadManager.maxConcurrentDownloads = 4;
It would seem to me that is something you'd want to do perhaps in your init method of your tableView so it only occurs once, rather than every time the user taps a row. Perhaps you're attempting to create and set as a property a new download manager every time? That sort of seems unorthodox to me. If I had access to the project I think I might be better help debugging. Any chance you'd want to share the project if my response didn't help?
I want to make a application which will display images into UICollectionView.
Images will be downloaded from server and then shows into collectionView.
I am using custom collectionView layout into xib file.
At a time, 20 images is receiving from server.
Problem: I can't show newly downloaded images into collectionView.
Here is my code:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
BOOL reloaded = NO;
static NSString *cellIdentifier = #"cvCell";
CVCell *cell = (CVCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
NSMutableArray *data = [self.dataArray objectAtIndex:indexPath.section];
NSString *cellData = [data objectAtIndex:indexPath.row];
dispatch_queue_t queue = dispatch_queue_create("com.justTest.anotherSingleApplication", NULL);
dispatch_async(queue, ^{
//code to be executed in the background
NSString *imageName1 = [[NSString alloc]initWithFormat:#"http://www.abc.com/images/thumb/%#", cellData];
NSString *url_Img1 = imageName1;
UIImage *aImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url_Img1]]];
dispatch_async(dispatch_get_main_queue(), ^{
//code to be executed on the main thread when background task is finished
[cell.cellImage setImage:aImage];
});
});
if (indexPath.row == self.imageArray.count - 1 && !reloaded) {
getOnScrollImages *getImage = [[getOnScrollImages alloc] init]; // class to get image name from server
NSMutableArray *astring = (NSMutableArray *)[getImage getImageNameFromServer:#"list" board:#"111" pin:#"122345"]; // method to get image name from server
[self setNewTestArray:astring]; //adding newly downloaded image name into array
reloaded = YES;
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
}
return cell;
}
Any suggestion please?
NOTE: I am just starting developing iOS application, this may be a very silly question.
Use asynchronously fetch to get data from server and display it in collectionView
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
YourDataModel *model = self.dataArray[indexPath.row];
YourCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
if ([self checkWhetherImageAlreadyExist]) {
[cell.imageView setImage:model.image];
} else {
//show placeholder to avoid nothing in your UI, or your user gets confused
[cell.imageView setImage:placeholderImage];
[self startDownloadImageForIndexPath:indexPath];
}
}
- (void)startDownloadImageForIndexPath:(NSIndexPath *)indexPath
{
//YourImageDownloader is a class to fetch data from server
//imageDownloadsInProgress is a NSMutableDictionary to record the download process, which can avoid repeat download
YourImageDownloader *downloader = [self.imageDownloadsInProgress objectForKey:indexPath];
if (downloader == nil) {
YourDataModel *model = self.dataArray[indexPath.row];
//configure downloader
downloader = [[YourImageDownloader alloc] init];
[downloader setURL:model.url];
[downloader setCompletionHandler:^{
//download the image to local, or you can pass the image to the block
model.image = [UIImage imageWithContentsOfFile:model.localPath];
YourCell *cell = [self.mCollectionView cellForItemAtIndexPath:indexPath];
[cell.imageView setImage:model.image];
//remove downloader from dictionary
[self.imageDownloadsInProgress removeObjectForKey:indexPath];
}];
//add downloader to dictionary
[self.imageDownloadsInProgress setObject:downloader forKey:indexPath];
//start download
[downloader startDownload];
}
}
Use a class to download the image. If you have many images in one collection view, you may consider to save these images to local in case of memory warning. if now many, just leave the image in memory and display it in your collection view.
the code followed is save the image to local and read image data from local when displaying.
in .h:
#import <Foundation/Foundation.h>
#interface PortraitDownloader : NSObject
#property (nonatomic, copy) NSString *portraitName;
#property (nonatomic, copy) void (^completionHandler)(void);
- (void)startDownload;
- (void)cancelDownload;
#end
in .m
#import "PortraitDownloader.h"
#import <CFNetwork/CFNetwork.h>
#import "NSString+ImagePath.h" // it's a category to get the image local path
#interface PortraitDownloader ()
#property (nonatomic, strong) NSMutableData *activeDownload;
#property (nonatomic, strong) NSURLConnection *portraitConnection;
#end
#implementation PortraitDownloader
- (void)startDownload
{
self.activeDownload = [NSMutableData data];
NSString *urlstr = [NSString serverPortraitPathWithPortrait:self.portraitName];
NSURL *url = [NSURL URLWithString:urlstr];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
self.portraitConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)cancelDownload
{
[self.portraitConnection cancel];
self.portraitConnection = nil;
self.activeDownload = nil;
}
#pragma mark - NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.activeDownload appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// Clear the activeDownload property to allow later attempts
self.activeDownload = nil;
// Release the connection now that it's finished
self.portraitConnection = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// save to local path
NSString *localSavePath = [NSString localPortraitPathWithPortrait:self.portraitName];
[self.activeDownload writeToFile:localSavePath atomically:YES];
self.activeDownload = nil;
// Release the connection now that it's finished
self.portraitConnection = nil;
// call our delegate and tell it that our icon is ready for display
if (self.completionHandler) {
self.completionHandler();
}
}
#end
if you want to leave your image in-memory, just modify the completion block as:
in .h
typedef void (^Completion_handle) (UIImage *image);
#interface PortraitDownloader : NSObject
#property (nonatomic, copy) Completion_handle myCompletionBlock;
and in .m
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// get image from data
UIImage *image = [UIImage imageWithData:self.activeDownload];
self.activeDownload = nil;
// Release the connection now that it's finished
self.portraitConnection = nil;
// call our delegate and tell it that our icon is ready for display
if (self.myCompletionBlock) {
self.myCompletionBlock(image);
}
}
and also modify methods startDownloadImageForIndexPath, save the image to your model to retain it
This method expects to have answers immediately:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
when your code doesn't respond fast enough to it, the app will usually display nothing, or sometimes just crash (depending on what you've setup)
A common design pattern is to store the info that will be supplied to the collectionView in a class variable (it doesn't have to be a property, but it often times is). You always store SOMETHING in that variable, even if it is old or stale data.
Then you have the methods defined in the UICollectionViewDataSource protocol pull what they need directly from the class variables, with no delay.
Other methods can fetch and retrieve and sling updated data around, and once they finish you call reloadData: on the collectionView to update the interface.
assuming the asynchronous calls you are using are successfully retrieving data eventually, they are probably too slow for what the UICollectionViewDataSource protocol methods are expecting.
A suggestion for how to get started would be to move the code fetching your data to separate methods, and then stage the data in a class variable or two which the collectionView can reliably draw from.
You can try it with static data loaded into the bundle at first if you need, and then move into asynchronous pulls from the web too.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
UIImageView *imgView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"profile_pic.png"]];
NSMutableDictionary *contactData=[NSMutableDictionary new];
contactData = [self.collectionData objectAtIndex:indexPath.row];
imgView.image=[contactData objectForKey:#"image"];
[cell addSubview:imgView];
return cell;
}