I’m building an article reading app. I’m fetching data from JSON link like article image and title in uitableview.
I’m unable to implement pagination in uitableview, let say my JSON link is www.example.com&page=1 contain 10 articles at a time which is 1-10.
When I concatenate in the JSON link like www.example.com&page=2 to get 11-20 article list.
I’m unable to implement how I can load the data in uitableview on scrolling and increase no.of rows with data.
Here is my code:
int *x=1;
int *inc=10;
#interface ysTableViewController ()
{
Reachability *internetReachable;
}
#end
#implementation ysTableViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self checkInternetConnection];
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(20,10,0,20)];
titleLabel.textColor = [UIColor blackColor];
titleLabel.backgroundColor = [UIColor clearColor];
titleLabel.text = #"Story";
[self.navigationItem setTitleView:titleLabel];
}
- (void)scrollViewDidScroll:(UIScrollView *)aScrollView {
CGPoint offset = aScrollView.contentOffset;
CGRect bounds = aScrollView.bounds;
CGSize size = aScrollView.contentSize;
UIEdgeInsets inset = aScrollView.contentInset;
float y = offset.y + bounds.size.height - inset.bottom;
float h = size.height;
float reload_distance = 10;
if(y > h + reload_distance) {
NSLog(#"load more rows");
inc=inc+10;
BOOL myBool = [self isNetworkAvailable];
if (myBool)
{
#try {
// for table cell seperator line color
self.tableView.separatorColor = [UIColor colorWithRed:190/255.0 green:190/255.0 blue:190/255.0 alpha:1.0];
// for displaying the previous screen lable with back button in details view controller
UIBarButtonItem *backbutton1 = [[UIBarButtonItem alloc] initWithTitle:#"" style:UIBarButtonItemStyleBordered target:nil action:nil];
[[self navigationItem] setBackBarButtonItem:backbutton1];
_Title1 = [[NSMutableArray alloc] init];
_Author1 = [[NSMutableArray alloc] init];
_Images1 = [[NSMutableArray alloc] init];
_Details1 = [[NSMutableArray alloc] init];
_link1 = [[NSMutableArray alloc] init];
_Date1 = [[NSMutableArray alloc] init];
NSString *urlString=[NSString stringWithFormat:#“www.example.com&page=%d",x];
NSLog(#"xxxxx===%d",x);
NSURL *newUrl=[NSURL URLWithString:urlString];
NSData* data = [NSData dataWithContentsOfURL:newUrl];
NSArray *ys_avatars = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
x++;
if(ys_avatars){
for (int j=0;j<ys_avatars.count;j++)
{
[_Title1 addObject:ys_avatars[j][#"title"]];
[_Author1 addObject: ys_avatars[j][#"author"]];
[_Images1 addObject: ys_avatars[j][#"featured_img"]];
[_Details1 addObject:ys_avatars[j][#"content"]];
[_link1 addObject:ys_avatars[j][#"permalink"]];
NSString *newStr=[ys_avatars[j][#"date"] substringToIndex:[ys_avatars[j][#"date"] length]-3];
[_Date1 addObject:newStr];
} }
else
{
NSLog(#"asd");
} }
#catch (NSException *exception) {
}
}
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return inc;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *Cellidentifier1 = #"ysTableViewCell";
ysTableViewCell *cell1 = [tableView dequeueReusableCellWithIdentifier:Cellidentifier1 forIndexPath:indexPath];
// Configure the cell...
long row = [indexPath row];
cell1.TitleLabel1.text = _Title1[row];
cell1.AuthorLabel1.text = _Author1[row];
NSString *yourStoryUrl = [_Images1[indexPath.row] stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
if(yourStoryUrl) {
NSArray *subStringsUrl = [yourStoryUrl componentsSeparatedByString:#"/"];
NSString *stripedName = [subStringsUrl lastObject];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//Local stored image file path
NSString* filePath =[documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"%#",stripedName]];
if(filePath) {
UIImage *image = [UIImage imageWithContentsOfFile:filePath];
if(image) {
// Now the image will have been loaded and decoded and is ready to rock for the main thread
ysTableViewCell *updateCell =(id)[tableView cellForRowAtIndexPath:indexPath];
if(updateCell)
updateCell.ThumbImage1.image=image;
cell1.ThumbImage1.image=image;
} else {
dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(taskQ, ^{
NSURL *Imageurl = [NSURL URLWithString:yourStoryUrl];
NSData *data = [NSData dataWithContentsOfURL:Imageurl];
UIImage *images1 = [[UIImage alloc] initWithData:data];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSData *imageData = UIImagePNGRepresentation(images1);
//_imagePath =[documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.png",stripedName]];
// NSLog((#"pre writing to file"));
if (![imageData writeToFile:filePath atomically:NO])
{
NSLog((#"Failed to cache image data to disk"));
}
else
{
NSLog((#"the cachedImagedPath is %#",filePath));
}
// Now the image will have been loaded and decoded and is ready to rock for the main thread
dispatch_sync(dispatch_get_main_queue(), ^{
ysTableViewCell *updateCell =(id)[tableView cellForRowAtIndexPath:indexPath];
if(updateCell)
updateCell.ThumbImage1.image=images1;
cell1.ThumbImage1.image=images1;
});
});
}
} else {
dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(taskQ, ^{
NSURL *Imageurl = [NSURL URLWithString:yourStoryUrl];
NSData *data = [NSData dataWithContentsOfURL:Imageurl];
UIImage *images1 = [[UIImage alloc] initWithData:data];
// NSString *myString = [Imageurl absoluteString];
// NSLog(#"%#",myString);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSData *imageData = UIImagePNGRepresentation(images1);
_imagePath =[documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.png",stripedName]];
// NSLog((#"pre writing to file"));
if (![imageData writeToFile:_imagePath atomically:NO])
{
NSLog((#"Failed to cache image data to disk"));
}
else
{
// NSLog((#"the cachedImagedPath is %#",_imagePath));
}
// Now the image will have been loaded and decoded and is ready to rock for the main thread
dispatch_sync(dispatch_get_main_queue(), ^{
ysTableViewCell *updateCell =(id)[tableView cellForRowAtIndexPath:indexPath];
if(updateCell)
updateCell.ThumbImage1.image=images1;
cell1.ThumbImage1.image=images1;
});
});
}
}
return cell1;
}
This is by no means easy. IN GENERAL TERMS you need code that looks like this..
Note the four very distinct parts of this fundamental routine.
I have never found a working "general" package solution for this problem.
Again, look to the "four sections" in this: they give the logic you're looking for!
-(void)forTerm:(NSString *)term doPageAfter:(int)doingThisPage
{
doingThisPage = doingThisPage + 1;
if ( doingThisPage > 20 ) return; // never, ever, ever forget that!! heh.
[CLOUD search:term page:doingThisPage then:^(NSArray *thoseTenResults)
{
self.searchSpinner.hidden = YES;
// (step 1) IF IT IS "PAGE 1", we need to re-kick-off the array...
if ( doingThisPage == 1 )
CLOUD.searchResultsRA = [[NSMutableArray alloc] init];
// (step 2) go ahead and add on these results
if ( doingThisPage == 1 )
{
[CLOUD.searchResultsRA addObjectsFromArray:thoseTenResults];
[self.searchDisplay safelyReloadBouncyTable];
}
else
{
[self.searchDisplay.collectionView performBatchUpdates:^
{
NSUInteger oldSize = CLOUD.searchResultsRA.count;
[CLOUD.searchResultsRA addObjectsFromArray:thoseTenResults];
NSUInteger newSize = CLOUD.searchResultsRA.count;
NSMutableArray *arrayWithIndexPaths = [NSMutableArray array];
for (NSUInteger i = oldSize; i < newSize; i++)
[arrayWithIndexPaths
addObject:[NSIndexPath indexPathForRow:i inSection:0]];
[self.searchDisplay justSignal];
[self.searchDisplay.collectionView
insertItemsAtIndexPaths:arrayWithIndexPaths];
}
completion:nil];
}
// (step 3) indeed if it's the first page, do a drop-in for fun
if ( doingThisPage == 1 ) [self.searchDisplay.view dropIn:nil];
// (for a "new search" which is now being displayed, in your UX
// there would be some sort of indication of that fact - do it here)
// (step 4) IF there WERE results .. try another page!
if ( thoseTenResults.count > 0 )
[self forTerm:term doPageAfter:doingThisPage];
// note we are calling this same routine, again!!!
}
];
}
Related
As part of syncing phone to server, the phone takes data from API to populate tableview. Local placeholder images appear immediately and are replaced with remote images asynchronously. The problem is that the label for each row does not appear for up to twenty seconds until after the images have all downloaded even the label is a constant. How can I get labels to load more quickly?
-(void)configureCell:(IDItemCell *)cell withItem:(Items *)item {
[cell layoutIfNeeded];
//Label
cell.nameLabel.text = #"TEST LABEL";//does not load for 20 seconds
//image
NSString *picname = item.pic== nil ? #"" : item.pic;
cell.iconView.image = [UIImage imageNamed:#"placeholder.png"];//loads instantly
//remote fetch
if (item.pic !=nil) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent:
[NSString stringWithString: picname] ];
if ([[NSFileManager defaultManager] fileExistsAtPath:path])
{
cell.iconView.image =[self loadImageNamed:picname];
}
else {
NSString *picURL = [NSString stringWithFormat:#"https://www.~/pics/%#",picname];
dispatch_async(kBgQueue, ^{
NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:picURL]];
if (imgData) {
UIImage *imageFromWeb = [UIImage imageWithData:imgData];
if (imageFromWeb) {
[self saveImage:imageFromWeb asPicName:picname];
dispatch_async(dispatch_get_main_queue(), ^{
cell.iconView.image = imageFromWeb;
[cell setNeedsDisplay];
});
}
}
});
}
}
// Rounding the image view
cell.iconView.layer.cornerRadius = cell.iconView.frame.size.width / 2;
cell.iconView.clipsToBounds = YES;
cell.iconView.contentMode = UIViewContentModeScaleAspectFill;
}
Is it possible to get only all the text content of the child elements recursively in hpple. Any method in TFHppleElement class?
such as the javascript
document.getElementById("testdiv").textContent
I'm using this code to get all content of the news title
NSURL *newURL = [NSURL URLWithString:#"http://somesite"];
NSData *newsData = [NSData dataWithContentsOfURL: newURL];
TFHpple *newsParser = [TFHpple hppleWithHTMLData: newsData];
NSString *newsXpathQueryString = #"//div[#class='item column-1']";
NSArray *newsNodes = [newsParser searchWithXPathQuery: newsXpathQueryString];
NSMutableArray *newNews = [[NSMutableArray alloc] initWithCapacity: 0];
for (TFHppleElement *element in newsNodes)
{
News *news = [[News alloc] init];
[newNews addObject: news];
news.title = [[element content] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
news.photo_url = [element objectForKey:#"src"];
_allNews = newNews;
[self.tableView reloadData];
}
}
you can use
news.title = [[element firstChild]content] to get children elements content
I wanted something like this - a quick boiler plate code, it is not an elegant solution with static contents. Please let me know, how can this be improved :)
#pragma mark - Hpple XML parser
/* The documents contents lots of nested div, table, span, style etc. */
- (NSString *) extractDefinition
{
NSString *html = [self.webView stringByEvaluatingJavaScriptFromString: #"document.getElementById('innerframe').innerHTML"];
if ([Resources stringIsEmpty:html]) {
return nil;
}
return [self extractSubDiv:html];
}
- (NSString *)extractSubDiv:(NSString *)html
{
TFHpple *hppleParser = [TFHpple hppleWithHTMLData:[html dataUsingEncoding:NSUTF8StringEncoding]];
NSString * xpathQuery;
xpathQuery = #"//div[#id='columnboth']";
NSArray * defNodes = [hppleParser searchWithXPathQuery:xpathQuery];
NSString * text = nil;
if ([defNodes count] > 0) {
TFHppleElement * element = [defNodes objectAtIndex:0];
text = [self parseContents:element];
} else {
xpathQuery = #"//div[#id='columnsingle']";
defNodes = [hppleParser searchWithXPathQuery:xpathQuery];
if ([defNodes count] > 0) {
TFHppleElement * element = [defNodes objectAtIndex:0];
text = [self parseContents:element];
}
}
return text;
}
- (NSString *) parseContents:(TFHppleElement *)element {
NSArray * innhold = [element searchWithXPathQuery:#"//div[contains(#class,'articlecontents')]"];
return [self getTextFromArray:innhold];
}
static NSMutableString * contents;
- (NSString *) getTextFromArray:(NSArray *)hppleElments {
NSMutableString * text = [[NSMutableString new] autorelease];
contents = nil;
contents = [[NSMutableString new] autorelease];
for (TFHppleElement * e in hppleElments) {
[text appendFormat:#"%# ", [self getText:e]];
}
return text;
}
/* Here are more nested div and then span for text. */
- (NSString *) getText:(TFHppleElement *)element
{
if ([element isTextNode]) {
[contents appendFormat:#" %#", element.content];
}
for (TFHppleElement * e in element.children) {
[self getText:e];
}
return contents;
}
I've already asked the same question about saving pressed button state and I thought that I should do in the same way to save progress state on cells but my tries are unsuccessful.
What I'm doing now: I select some UICollectionViewCell's and then press "download" button and then downolad action starts. Every cell I selected shows UIProgressView and everything is ok untill I scroll my UICollectionView up or down. When I do it another cells have progress view too but they mustn't! I know that I must save indexPath of selected cells in NSMutableArray and then in cellForItemAtIndexPath check if current cell indexPath is in my array and then show or hide my cell's subviews. I do that but it only works with cell selection! What should I do to save progress view state on each cell this progress really there?
Here is my code:
In cellForItemAtIndexPath:
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if ([selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
cell.selectedBG.hidden = NO;
cell.selectedImg.hidden = NO;
}
else
{
cell.selectedBG.hidden = YES;
cell.selectedImg.hidden = YES;
cell.progressView.hidden = YES;
}
In didSelectItemAtIndexPath:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
...
// Add the selected item into the array
[selectedIds addObject:selectedId];
[selectedVideos addObject:selectedVideo];
AVMVideoCell *collectionCell = (AVMVideoCell*)[collectionView cellForItemAtIndexPath:indexPath];
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if ( ![selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
[selecedCellsArray addObject:[NSString stringWithFormat:#"%ld",(long)indexPath.row]];
collectionCell.selectedBG.hidden = NO;
collectionCell.selectedImg.hidden = NO;
[collectionCell setSelected:YES];
}
}
}
In didDeselectItemAtIndexPath:
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
...
// Delete the selected item from the array
[selectedIds removeObject:selectedId];
[selectedVideos removeObject:selectedVideo];
AVMVideoCell *collectionCell = (AVMVideoCell*)[collectionView cellForItemAtIndexPath:indexPath];
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if ( [selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
[selecedCellsArray removeObject:[NSString stringWithFormat:#"%ld",(long)indexPath.row]];
collectionCell.selectedBG.hidden = YES;
collectionCell.selectedImg.hidden = YES;
[collectionCell setSelected:NO];
}
}
And this is how I show my progress:
-(NSString *)progress:(long long )val1 : (long long )val2 : (AVMVideoCell *)cell : (NSString *)name : (NSIndexPath *)path{
float progress = ((float)val1) / val2;
NSString *prog = [[NSNumber numberWithFloat:progress*100] stringValue];
if (prog != nil){
if(cell.isSelected){
cell.selectedImg.hidden = YES;
cell.progressView.hidden = NO;
}
}
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)path.row];
if ( [selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]])
{
[cell.progressView setProgress:progress animated:YES];
}
if([prog intValue]==100){
cell.progressView.hidden = YES;
}
return prog;
}
EDIT: AVMVideoCell.m
#import "AVMVideoCell.h"
#implementation AVMVideoCell
{
NSString *fullUrl;
}
#synthesize imageView;
#synthesize selectedBG;
#synthesize progressLabel;
#synthesize progressView;
#synthesize selectedImg;
#synthesize progLayer;
-(void) setVideo:(AVMDataStore *)video {
if(_video != video) {
_video = video;
}
NSString *durString = [NSString stringWithFormat:#"%#",[self timeFormatted:_video.duration]];
if((_video.filePreview != nil) && ![_video.filePreview isEqualToString:#""]){
fullUrl = [NSString stringWithFormat:#"http://example.com%#",_video.filePreview];
}
NSURL *imgURL = [NSURL URLWithString:fullUrl];
[self.imageView setImageWithURL:imgURL
placeholderImage:[UIImage imageNamed:#"yesterday.png"] options:0 usingActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
self.duration.text = durString;
}
- (NSString *)timeFormatted:(int)totalSeconds
{
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
return [NSString stringWithFormat:#"%02d:%02d:%02d",hours, minutes, seconds];
}
#end
EDIT 2: Explanation about progress
My progressView is not an IBOutlet it's a #property (nonatomic,strong) UIProgressView *progressView; (AVMVideoCell.h)
I allocate and initialize it in cellForItemAtIndexPath:
cell.progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0,0, 150.0f, 1.0f)];
cell.progressView.trackTintColor = [UIColor colorWithWhite:255 alpha:0.5f];
cell.progressView.progressTintColor = [UIColor whiteColor];
[cell.progLayer addSubview:cell.progressView];
cell.progressView.hidden = YES;
cell.progressView.tag = indexPath.row+500;
This is where I call progress change showing:
-(void)downloadStart:(NSString*)fileUrl : (NSString*)name : (AVMVideoCell *) cell : (NSIndexPath *)path{
NSURL *URL = [NSURL URLWithString:fileUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSString *fileName = [NSString stringWithFormat:#"%#.mp4",name]; //set full file name to save
AFHTTPRequestOperation *downloadRequest = [[AFHTTPRequestOperation alloc] initWithRequest:request]; //create download request
[downloadRequest setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSData *data = [[NSData alloc] initWithData:responseObject];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *pathToFile = [NSString stringWithFormat:#"%#/%#", [paths firstObject],fileName]; // path to 'Documents'
NSString *pathOfFile = [[paths objectAtIndex:0] stringByAppendingPathComponent:fileName];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:pathOfFile append:NO];
BOOL success = [data writeToFile:pathToFile atomically:YES];
if(success){
[self checkIfExists:name : cell :path];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"file downloading error : %#", [error localizedDescription]);
UIAlertView * alert=[[UIAlertView alloc]initWithTitle:#"Error" message:[NSString stringWithFormat:#"%#",error] delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil ];
[alert show];
cell.progressView.hidden = YES;
}];
// Step 5: begin asynchronous download
[downloadRequest start];
[downloadRequest setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
[self progress:totalBytesRead :totalBytesExpectedToRead :cell :name : path]; //here I pass val1 and val 2
}];
}
When I select an items in collection view, I gather their model's objects, get each id and make array of urls, then in for..in loop i pass urls one by one and then start async download. You can see how I download and call progress method above.
I need to know if you have a reuse issue or a model issue.
So first try this :
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
...
if (![selectedIds.containsObject:selectedId])
{
// Add the selected item into the array
[selectedIds addObject:selectedId];
[selectedVideos addObject:selectedVideo];
AVMVideoCell *collectionCell = (AVMVideoCell*)[collectionView cellForItemAtIndexPath:indexPath];
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if ( ![selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
[selecedCellsArray addObject:[NSString stringWithFormat:#"%ld",(long)indexPath.row]];
collectionCell.selectedBG.hidden = NO;
collectionCell.selectedImg.hidden = NO;
[collectionCell setSelected:YES];
}
}
else {
// Delete the selected item from the array
[selectedIds removeObject:selectedId];
[selectedVideos removeObject:selectedVideo];
AVMVideoCell *collectionCell = (AVMVideoCell*)[collectionView cellForItemAtIndexPath:indexPath];
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if ( [selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
[selecedCellsArray removeObject:[NSString stringWithFormat:#"%ld",(long)indexPath.row]];
collectionCell.selectedBG.hidden = YES;
collectionCell.selectedImg.hidden = YES;
[collectionCell setSelected:NO];
}
}
and remove the didDeselect delegate.
Let me know what happens then.
EDIT :
Ok try this now:
// Lazy instantiation of the progressView
- (UIProgressView *)progressView
{
if (!_progressView)
{
_progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0,0, 150.0f, 1.0f)];
_progressView.trackTintColor = [UIColor colorWithWhite:255 alpha:0.5f];
_progressView.progressTintColor = [UIColor whiteColor];
_progressView.hidden = YES;
[self.contentView addSubview:_progressView];
}
return _progressView;
}
// Here we remove the progressView on reuse
-(void)prepareForReuse
{
[super prepareForReuse];
[self.progressView removeFromSuperview];
self.progressView = nil;
}
Also remove what you did with the progressView with in the cellForItemAtIndexPath method.
You can create class for cell's models, store these models in array in your viewController and use them to get/set all states from/for cells.
Something like this:
#interface CellModel : NSObject
#property(nonatomic) BOOL selected;
#property(nonatomic) NSUInteger progress;
#end
In viewController:
#interface MyViewController () <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic) NSArray* models;
#end
This question already has answers here:
populating a tableview with data using JSON and AFNetworking NSDictionary
(1 answer)
JSON data is not loading in slow internet connection? [closed]
(1 answer)
Closed 8 years ago.
I'm building an article reading app.I'm parsing JSON data using NSData in UITableView.
I'm facing an issue that is data is not load in slow internet speed(2g or 3g)means UI is empty.I want to implement NSUrlConnection
but i'm new in iOS development unable to implement NSUrlConnection in my code.
this is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
BOOL myBool = [self isNetworkAvailable];
if (myBool)
{
#try {
// for table cell seperator line color
self.tableView.separatorColor = [UIColor colorWithRed:190/255.0 green:190/255.0 blue:190/255.0 alpha:1.0];
UIBarButtonItem *backbutton1 = [[UIBarButtonItem alloc] initWithTitle:#"" style:UIBarButtonItemStyleBordered target:nil action:nil];
[[self navigationItem] setBackBarButtonItem:backbutton1];
_Title1 = [[NSMutableArray alloc] init];
_Author1 = [[NSMutableArray alloc] init];
_Images1 = [[NSMutableArray alloc] init];
_Details1 = [[NSMutableArray alloc] init];
_link1 = [[NSMutableArray alloc] init];
_Date1 = [[NSMutableArray alloc] init];
NSData* data = [NSData dataWithContentsOfURL:ysURL];
NSArray *ys_avatars = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if(ys_avatars){
for (int j=0;j<ys_avatars.count;j++)
{
if( ys_avatars[j][#"title"]==[NSNull null] ){
[_Title1 addObject: #""];
}
else{
[_Title1 addObject:ys_avatars[j][#"title"]];
}
if( ys_avatars[j][#"author"]==[NSNull null] ){
[_Author1 addObject: #""];
}
[_Author1 addObject: ys_avatars[j][#"author"]];
if( ys_avatars[j][#"featured_img"]==[NSNull null] ){
[_Images1 addObject: #""];
}
else{
[_Images1 addObject: ys_avatars[j][#"featured_img"]];
}
if( ys_avatars[j][#"content"]==[NSNull null] ){
[_Details1 addObject: #""];
}else{
[_Details1 addObject:ys_avatars[j][#"content"]];
}
if( ys_avatars[j][#"permalink"]==[NSNull null] ){
[_link1 addObject: #""];
}
else{
[_link1 addObject:ys_avatars[j][#"permalink"]];
}
if( ys_avatars[j][#"date"]==[NSNull null] ){
[_Date1 addObject: #""];
}
else{
NSString *newStr=[ys_avatars[j][#"date"] substringToIndex:[ys_avatars[j][#"date"] length]-3];
[_Date1 addObject:newStr];
}
}
}
else
{
NSLog(#"asd");
}
}
#catch (NSException *exception) {
}
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *Cellidentifier1 = #"ysTableViewCell";
ysTableViewCell *cell1 = [tableView dequeueReusableCellWithIdentifier:Cellidentifier1 forIndexPath:indexPath];
long row = [indexPath row];
cell1.TitleLabel1.text = _Title1[row];
cell1.AuthorLabel1.text = _Author1[row];
NSString *StoryUrl = [_Images1[indexPath.row] stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
if(StoryUrl) {
NSArray *subStringsUrl = [yourStoryUrl componentsSeparatedByString:#"/"];
NSString *stripedName = [subStringsUrl lastObject];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* filePath =[documentsDirectory stringByAppendingPathComponent: [NSString stringWithFormat:#"%#",stripedName]];
if(filePath) {
UIImage *image = [UIImage imageWithContentsOfFile:filePath];
if(image) {
ysTableViewCell *updateCell =(id)[tableView cellForRowAtIndexPath:indexPath];
if(updateCell)
updateCell.ThumbImage1.image=image;
cell1.ThumbImage1.image=image;
} else {
dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(taskQ, ^{
NSURL *Imageurl = [NSURL URLWithString:yourStoryUrl];
NSData *data = [NSData dataWithContentsOfURL:Imageurl];
UIImage *images1 = [[UIImage alloc] initWithData:data];
NSData *imageData = UIImagePNGRepresentation(images1);
if (![imageData writeToFile:filePath atomically:NO])
{
NSLog((#"Failed to cache image data to disk"));
}
else
{
NSLog(#"the cachedImagedPath is %#",filePath);
}
dispatch_sync(dispatch_get_main_queue(), ^{
ysTableViewCell *updateCell =(id)[tableView cellForRowAtIndexPath:indexPath];
if(updateCell)
updateCell.ThumbImage1.image=images1;
cell1.ThumbImage1.image=images1;
});
});
}
return cell1;
}
Help is appreciated.
Thanks in advance.
This looks really messy, and i suggest you change your whole design.
A basic and cleaner way (but probably not the best/cleanest way) :
Create class to handle outside-of-view related work (JSON parsing here)
Call that class in viewDidLoad to start parsing
Call a method that refreshes your table view with the newly parsed data when the parsing is done (in the JSON class).
That way, the table view will load your placeholders first and then reload itself when it has the data.
In my opinion, a better way would be to populate it before loading it so there is no wait time.
Can you find what you need yourself, or code it alone? or do you need help? If so, with what?
EDIT : You could/should also use the AFNetworking framework that will make your life 10 times easier with JSON/Internet related code.
I usually create a class that handles the load of my data, whether from a URL or local store. You could use AFNetworking, but there is a ton of extra stuff you might not need. The basics of using NSUrlConnection is really easy.
Try this tutorial, it will help you to understand how Apple's implementation works before you add a third party library that masks it for you.
NSUrlConnection Tutorial
I'm using GCD to load my UITableView data on the background thread, however doing so mixes up the data in my custom UITableViewCell. The titleLabel and imageView on the cell are fine, but the textLabel (the subtitle) is wrong on every cell. This doesn't happen when the data is loaded on the main thread, and the data doesn't come from multiple arrays, so I can only guess it's because of my use of GCD, which I am new to.
Firstly, I set up the NSOperationQueue like so...
- (void)setUpTableForAlbums
{
dispatch_async(dispatch_get_global_queue(0, 0), ^
{
[self setUpTableForAlbumsFD];
dispatch_async(dispatch_get_main_queue(), ^
{
[albumTable reloadData];
});
});
}
The setUpTableForAlbumsFD selector is as so...
- (void)setUpTableForAlbumsFD
{
// __block CLProgressIndeterminateView *clP = [[CLProgressIndeterminateView alloc] initWithFrame:CGRectMake(325, tableScrollView.frame.size.height/2, 310, 20)];
// [tableScrollView addSubview:clP];
// [clP startAnimating];
type = #"Albums";
queryAlbums = [MPMediaQuery albumsQuery];
[queryAlbums setGroupingType:MPMediaGroupingAlbum];
mainArrayAlbum = [[NSMutableArray alloc] init];
otherArrayAlbum = [[NSMutableArray alloc] init];
theOtherArrayAlbum = [[NSMutableArray alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSArray *fullArray = [queryAlbums collections];
for (MPMediaItemCollection *collection in fullArray)
{
item = [collection representativeItem];
NSString *albumName = [item valueForProperty:MPMediaItemPropertyAlbumTitle];
NSString *albumArtist = [item valueForProperty:MPMediaItemPropertyArtist];
NSString *filePath = [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.png", albumName]];
Album *album = [[Album alloc] init];
album.albumTitle = albumName;
album.albumArtwork = [UIImage imageImmediateLoadWithContentsOfFile:filePath];
if (album.albumTitle.length > 4)
{
if ([album.albumTitle hasPrefix:#"The "])
{
album.albumOrderTitle = [album.albumTitle substringFromIndex:4];
}
else
{
album.albumOrderTitle = album.albumTitle;
}
}
else
{
album.albumOrderTitle = album.albumTitle;
}
album.albumArtist = albumArtist;
if (![mainArrayAlbum containsObject:album])
{
[mainArrayAlbum addObject:album];
}
}
}
The Album custom class is just a container for the data.
The cellForRowAtIndex path method is as so...
MasterCellAlbum *albumCell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (!albumCell)
{
albumCell = [[MasterCellAlbum alloc] initWithStyle:nil reuseIdentifier:#"Cell"];
}
alphabet = [self alphabet:#"album" withIndex:YES];
[albumCell setSelectionStyle:UITableViewCellEditingStyleNone];
NSString *alpha = [alphabet objectAtIndex:indexPath.section];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.albumOrderTitle beginswith[c] %#", alpha];
NSArray *predict = [mainArrayAlbum filteredArrayUsingPredicate:predicate];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
Album *album1 = [predict objectAtIndex:indexPath.row];
albumCell.titleLabel.text = album1.albumTitle;
albumCell.textLabel.text = album1.albumArtist;
albumCell.avatarImageView.image = album1.albumArtwork;
longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(albumLittleMenu:)];
[albumCell addGestureRecognizer:longPress];
return albumCell;
Am I using GCD correctly, or is there another way I should be doing it?
Yikes. There are lots of things that are, shall we say, interesting about this code. Let's start with the first method:
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSInvocationOperation *operation = [NSInvocationOperation alloc];
operation = [operation initWithTarget:self selector:#selector(setUpTableForAlbumsFD) object:nil];
[operation setCompletionBlock:^
{
[albumTable reloadData];
}];
[operationQueue addOperation:operation];
operation = nil;
What I think you're tying to do is execute the -setUpTableForAlbumsFD method in the background, and then when it's done, reload the tableView.
First, the completionBlock doesn't execute on the main thread (which is where you MUST call -reloadData from). The docs say:
The exact execution context for your completion block is not guaranteed but is typically a secondary thread. Therefore, you should not use this block to do any work that requires a very specific execution context.
The simpler way to do this method would be:
dispatch_async(dispatch_get_global_queue(0,0), ^{
[self setUpTableForAlbumsFD];
dispatch_async(dispatch_get_main_queue(), ^{
[albumTable reloadData];
}
});
Now for the setUpTableForAlbumsFD method...
- (void)setUpTableForAlbumsFD {
type = #"Albums";
queryAlbums = [MPMediaQuery albumsQuery];
[queryAlbums setGroupingType:MPMediaGroupingAlbum];
mainArrayAlbum = [[NSMutableArray alloc] init];
NSArray *fullArray = [queryAlbums collections];
for (MPMediaItemCollection *collection in fullArray) {
item = [collection representativeItem];
NSString *albumName = [item valueForProperty:MPMediaItemPropertyAlbumTitle];
NSString *albumArtist = [item valueForProperty:MPMediaItemPropertyArtist];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
You should do these two lines of finding the NSDocumentDirectory outside of the for loop, for efficiency.
NSString *filePath = [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.png", albumName]];
UIImage *artwork = [UIImage imageImmediateLoadWithContentsOfFile:filePath];
I'm assuming this is a UIImage category method?
Album *album = [[Album alloc] init];
album.albumTitle = albumName;
if (album.albumTitle.length > 4) {
if ([[NSString stringWithFormat:#"%c%c%c%c", [album.albumTitle characterAtIndex:0], [album.albumTitle characterAtIndex:1], [album.albumTitle characterAtIndex:2], [album.albumTitle characterAtIndex:3]] isEqual: #"The "]) {
Yikes! Just do: if ([album.albumTitle hasPrefix:#"The "]) {
album.albumOrderTitle = [album.albumTitle substringWithRange:NSMakeRange(4, album.albumTitle.length-4)];
And here do: album.albumOrderTitle = [album.albumTitle substringFromIndex:4];
} else {
album.albumOrderTitle = album.albumTitle;
}
} else {
album.albumOrderTitle = album.albumTitle;
When you see multiple lines that are doing the same thing like this, it's a sign you can pull it out and do it differently. For example, you could always set the album.albumOrderTitle to the albumTitle, and then only do something different if the albumTitle length is more than 4 and it has a prefix of #"The ".
}
album.albumArtist = albumArtist;
album.albumArtwork = artwork;
if (![mainArrayAlbum containsObject:album]) {
[mainArrayAlbum addObject:album];
}
}
}
Your cellForRowAtIndexPath: is similarly convoluted:
MasterCellAlbum *albumCell = [[MasterCellAlbum alloc] init];
You should be using UITableView's cell-reuse mechanism.
alphabet = [self alphabet:#"album" withIndex:YES];
[albumCell setSelectionStyle:UITableViewCellEditingStyleNone];
NSString *alpha = [alphabet objectAtIndex:indexPath.section];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.albumOrderTitle beginswith[c] %#", alpha];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
NSArray *predict = [mainArrayAlbum filteredArrayUsingPredicate:predicate];
Why are you re-filtering the mainArrayAlbum every time you need a cell? It looks like you're always going to be grabbing the same alphabet, which means you're always going to be defining the same predicate, which means you're always going to be ending up with the same predict array.
Album *album1 = [predict objectAtIndex:indexPath.row];
albumCell.titleLabel.text = album1.albumTitle;
albumCell.textLabel.text = album1.albumArtist;
if (album1.albumArtwork) {
albumCell.avatarImageView.image = album1.albumArtwork;
} else {
albumCell.avatarImageView.image = [UIImage imageNamed:#"albumArtInvertedLight1.png"];
}
longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(albumLittleMenu:)];
[albumCell addGestureRecognizer:longPress];
return albumCell;
So, there are some obvious places where your code can use some improvement. Honestly, I think the answer to the problem you're having is because you're trying to reload the tableview on a background thread, which is a Bad Idea™.