I applied this code in my tableview. However, i realise that data will load slow for first time request. After that, if i access to the same page, the records will be loaded faster. Any idea to improve for first time loading? Thanks for your help.
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:#"http://api.domainname.com/api/abc" parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
_serverDataArr = responseObject;
self.dataArr=[NSMutableArray array];
for (NSDictionary *subDic in self.serverDataArr) {
Friend_Model *model=[[Friend_Model alloc]initWithDic:subDic];
[self.dataArr addObject:model];
}
_rowArr=[Friend_DataHelper getFriendListDataBy:self.dataArr];
_sectionArr=[Friend_DataHelper getFriendListSectionBy:[_rowArr mutableCopy]];
[self.tableView reloadData];
} failure:^(NSURLSessionTask *operation, NSError *error) {
UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:#"Error Retrieving Ninjas"
message:[error localizedDescription]
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"Ok"
style:UIAlertActionStyleCancel
handler:nil];
[alertVC addAction:okAction];
[self presentViewController:alertVC animated:YES completion:nil];
}];
And here is my cellForRowAtIndexPath code :-
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIde=#"cellIde";
Friend_TableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIde];
if (cell==nil) {
cell=[[Friend_TableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIde];
}
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
if (tableView==_searchDisplayController.searchResultsTableView){
NSString *url_Img_FULL = [_searchResultArr[indexPath.row] valueForKey:#"image"];
[cell.headImageView setImageWithURL:[NSURL URLWithString:url_Img_FULL] placeholderImage:[UIImage imageNamed:#"Placeholder.png"]];
[cell.nameLabel setText:[_searchResultArr[indexPath.row] valueForKey:#"name"]];
}else{
Friend_Model *model=_rowArr[indexPath.section][indexPath.row];
[cell.headImageView setImageWithURL:[NSURL URLWithString:model.image] placeholderImage:[UIImage imageNamed:#"Placeholder.png"]];
[cell.nameLabel setText:model.name];
}
return cell;
}
It looks like you're doing perfectly appropriate stuff to form the request and present the results. You might be seeing a big difference between the 1st and nth request because your back-end is awake, and data has been cached at various points on the stack. Hard to suggest much here besides moving the request earlier, maybe to app startup, where the user might not notice as much delay.
Another idea would be to request data for table rows more lazily, based on where the table is scrolled. When user gestures a pull at the bottom of the table, show a busy indicator and go get more data.
Related
I'm implementing a lazy loading strategy in my app where I use AFNetworking to asynchronously load news article images in a UITableView. This is the following code that does that in my cellForRowAtIndexPath method:
#implementation PocketTableViewController
- (AFHTTPRequestOperationManager *)operationManager
{
if (!_operationManager)
{
_operationManager = [[AFHTTPRequestOperationManager alloc] init];
_operationManager.responseSerializer = [AFImageResponseSerializer serializer];
};
return _operationManager;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
if (articleImageURL != nil)
{
//set article image
cell.ThumbImage.image = [UIImage imageNamed:#"greyImage.png"];
[cell.ThumbImage.associatedObject cancel];
cell.ThumbImage.associatedObject =
[self.operationManager GET:[articleImageURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
cell.ThumbImage.image = responseObject;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failed with error %#.", error);
}];
}
It works for most images when I scroll through the table view, but then it crashes here:
Any thoughts on why it's crashing?
UPDATE:
I found that it's crashing on this image url for some reason:
http://www.profitconfidential.com/wp-content/uploads/2015/12/Bill-Gates-Bought-Stock-in-Third-Quarter.jpg
Use UIImage+AFNetworking.
It lazy loading and supported image caching.
In your case, you can use this:
UIImage *image = [UIImage imageWithData:responseObject];
But the best way is use UIImage+AFNetworking.
Apparently I fixed the problem. There was nothing wrong with any of the images. I just had to disable all breakpoints in Xcode and it worked. Weird, but it fixed the problem.
I am writing an app, which is getting data from the net using XML. It is a master-detail-app which is fetching data for the master-table and after selecting one item from the master-Table it fills the data for the detailview(s) using another network-access.
I would like to present an alert in order to show the user that the app is busy accessing the net or busy calculating. So I would present the view from the UIAlertController before the calculation / network access starts and dismiss the view when the activity has completed
Problem is: I don't know where to put this call to show / dismiss the UIAlertcontroller view.
Putting the activity code into ViewWillAppear shows and dismisses the alertview BEFORE the network-access... Putting everything into ViewDidLoad seems not the way to go.
- (void) viewWillAppear:(BOOL)animated
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Working"
message:#"Working on it"
preferredStyle:UIAlertControllerStyleAlert];
self.objects = [[NSMutableArray alloc] init];
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:#"Accounts"
style:UIBarButtonItemStylePlain
target:nil
action:nil];
NSURL *url;
if ([self getSession])
{
NSMutableString *URLstring = [NSMutableString stringWithString:#"https://XXXXXXXXXXXXXXX.xml?session="];
[self presentViewController:alert animated:YES completion:nil];
[URLstring appendString:[[DataStore getData]SessionString]];
url = [NSURL URLWithString:URLstring];
self.myXXXXXXParserAlleKontenXMLDelegate = [[KontenParserDelegate alloc] init];
self.xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];
self.xmlParser.delegate = self.myfxbookParserAlleKontenXMLDelegate;
if ([self.xmlParser parse])
{
[self.objects removeAllObjects];
for(int i=0; i< [self.XXXXXXXXXrAlleKontenXMLDelegate.allAccounts count];i++)
{
[self.objects addObject : [self.myfxbookParserAlleKontenXMLDelegate.allAccounts objectAtIndex:i]];
}
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey: #"name" ascending: YES];
[self.objects sortUsingDescriptors:#[sort]];
[self.tableView reloadData];
[self dismissViewControllerAnimated:YES completion:nil];
// Do any additional setup after loading the view, typically from a nib.
}
}
}
Added After some discussions I added some new framework "SVProgressHUD" and entered the following code into "ViewWillAppear" where [self parserstuff] contains all the XML parsing..
[SVProgressHUD show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self parserstuff];
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
});
});
This leaves me with a HUD-Display showing up while parsing XML but the resulting Tableview does not show data but is empty... After switching to DISPATCH_SYNC it works.
Solution would be
1) Put code in ViewDidLoad
2) encapsulate Code between "SVProgressHUD show" and "dismiss" with dispatch_async
as in
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD show];
[self parserstuff];
[self fillComparatorParameters:nil];
[self.tableView reloadData];
[SVProgressHUD dismiss];
// }];
});
I have a class that runs similar to the AFHTTPSessionManager component of this tutorial http://www.raywenderlich.com/59255/afnetworking-2-0-tutorial
However, [self.tableView reloadData] is not working for me.
I have the manager implemented as so:
-(void) refresh{
manager = [[AFHTTPSessionManager...] iniwithBaseURL:...];
[manager Get:... parameters:... success:^(NSURLSessionDataTask *task, id responseObject){
//test success values in responseObject
if(test){
//Get table data
[self.tableView reloadData];
}
}
....
}
However if I run [self.tableView reloadData] in a separate function afterwards, it works just fine.
Why is this happening, instead of how it should in the tutorial?
Always reload on the main queue:
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
write the [self.tableView reloadData];in the main queue.
dispatch_sync(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
I'm trying to build an app that will display a collection of magazines that can be downloaded and then read. What I want to happen is if the magazine isn't downloaded, a download will commence when a cell is tapped and a progressview will appear in the corresponding cell showing the download progress. I have subclassed UICollectionViewCelland added a UIProgressView property. So far, I've been able to check whether the file is downloaded and if it isn't, download it, and if it is, just open it using a PDF library called VFR Library. I can also update a progress view placed outside of the UICollectionViewCell but I can't update one placed within the UICollectionViewCell. Unfortunately, most of my logic for downloading and updating the progressview is within didSelectItemAtIndexPath:. I know this isn't elegant but my experience level isn't high enough that I can effectively develop my own classes to work together seamlessly. But once I figure this out I'll work on refining the code and abstracting things away a bit. Anyway, here's what I'm doing in didSelectItemAtIndexPath:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
IssueCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
self.navBarLabel.hidden = YES;
self.mainProgressView.hidden = NO;
self.view.userInteractionEnabled = NO;
//self.activityIndicator.hidden = NO;
[self.activityIndicator startAnimating];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[[paths objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat:#"issue%ld", (long)indexPath.row]]stringByAppendingPathExtension:#"pdf"];
if ([[NSFileManager defaultManager]fileExistsAtPath:path])
{
ReaderDocument *document = [ReaderDocument withDocumentFilePath:path password:nil];
if (document != nil)
{
//opens PDF for viewing
ReaderViewController *readerViewController = [[ReaderViewController alloc] initWithReaderDocument:document];
readerViewController.delegate = self;
readerViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
readerViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:readerViewController animated:YES completion:nil];
self.mainProgressView.hidden = YES;
self.navBarLabel.hidden = NO;
self.view.userInteractionEnabled = YES;
//self.activityIndicator.hidden = YES;
[self.activityIndicator stopAnimating];
}
}
else
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlArray[indexPath.row]]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:path append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Successfully downloaded file to %#", path);
ReaderDocument *document = [ReaderDocument withDocumentFilePath:path password:nil];
if (document != nil)
{
//opens PDF for viewing
ReaderViewController *readerViewController = [[ReaderViewController alloc] initWithReaderDocument:document];
readerViewController.delegate = self;
readerViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
readerViewController.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:readerViewController animated:YES completion:nil];
self.mainProgressView.hidden = YES;
self.navBarLabel.hidden = NO;
//self.activityIndicator.hidden = YES;
self.view.userInteractionEnabled = YES;
[self.activityIndicator stopAnimating];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Download Failed" message:#"There was a problem downloading this issue" delegate:self cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alertView show];
self.mainProgressView.hidden = YES;
self.navBarLabel.hidden = NO;
[self.activityIndicator stopAnimating];
//self.activityIndicator.hidden = YES;
self.view.userInteractionEnabled = YES;
NSLog(#"Error: %#", error);
}];
[operation setDownloadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
float percentDone =((float)((int)totalBytesWritten) / (float)((int)totalBytesExpectedToWrite));
self.mainProgressView.progress = percentDone;
[cell.progressView setProgress:percentDone];
[self.collectionView reloadData];
// [cell.progressView performSelectorOnMainThread:#selector(loadingProgress:) withObject:[NSNumber numberWithFloat:percentDone] waitUntilDone:NO];
NSLog(#"Sent %lld of %lld bytes, %#", totalBytesWritten, totalBytesExpectedToWrite, path);
}];
[operation start];
}
}
So is there any way that I can update the progress view within a corresponding cell within this method or is there something else I need to do to get it to work? Thanks for any help you can give.
Well, I've finally solved my own problem. Apparently the problem was due to not fully understanding how to reference a particular item in a UICollectionView. All I had to do was create a method that actually did this and then call it in the "setDownloadProgressBlock:" method above. The method I wrote for this is:
- (void)setProgressAtIndex:(NSInteger)index withProgress:(float)progress
{
IssueCell *cell = (IssueCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]];
[cell.progressView setProgress:0.0];
[cell.progressView setProgress:progress];
}
The important line in this method is the first one where I grab a reference to the desired cell. This one actually grabs the cell that was tapped, whereas before I guess I was just referring to all the cells in general? I'm not too sure, but I would definitely appreciate it if someone had a better explanation.
This method was then called like so:
[self setProgressAtIndex:indexPath.row withProgress:percentDone];
Again, I did this inside the block in "setDownloadProgressBlock:".
Hopefully this helps someone else in the future!
Update your progress view on your main thread. Try using dispatch_sync(dispatch_get_main_queue()).
I'm loading images from a remote server using SDWebImage into a UICollectionView using the following code:
[myCell.imageView setImageWithURL:imgURL placeholderImage:nil options:SDWebImageRetryFailed success:^(UIImage *image)
{
[_imageCache storeImage:image forKey:[imgURL absoluteString] toDisk:YES];
} failure:^(NSError *error){
NSLog(#"ERROR: %#", error);
}];
For most cells, this code works fine - it loads the images and saves them to my local disk. However, after several (it seems random?) images, they stop loading. I then get the following error:
ERROR: Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo=0x1d33fdc0 {NSErrorFailingURLStringKey=http://path/to/image.jpg, NSErrorFailingURLKey=http://path/to/image.jpg, NSLocalizedDescription=The request timed out., NSUnderlyingError=0x1d34c0f0 "The request timed out."}
When this happens, my app seems to stop sending NSURLRequests altogether. After a period of time, probably about 20-30 seconds, I can refresh the table and the failed images will load in correctly and the app will resume responding to all NSURLRequests perfectly fine.
I find that this tends to happen more often if I scroll down my collection view fast. Could it be trying to download too many at once? Is there a way to limit the number of concurrent downloads? This method appears to be deprecated in the latest SDWebImage code.
Figured it out. I was using MWPhotoBrowser in another part of my app. MWPhotoBrowser comes with an older/modified version of SDWebImage. I downloaded the latest version of SDWebImage from Github, renamed/refactored all of the files, and imported my newly updated and modified SDWebImage alongside the one MWPhotoBrowser relies on.
The new version of SDWebImage has solved my problem completely!
It's better if use asynchronous download by share manager and display when finish download with SDWebImage. Now, you can scroll fast to test.
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCell *myCell = (MyCell *)[cv dequeueReusableCellWithReuseIdentifier:#"MyCell" forIndexPath:indexPath];
//Set placeholder
myCell.imageView.image = [UIImage imageNamed:#"placeholder.png"];
NSURL *cellImageURL = [NSURL URLWithString:[NSString stringWithFormat:#"%#",[self.collectionData objectAtIndex:indexPath.row]]];
[myCell.cellIndicator startAnimating];
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:cellImageURL
delegate:self
options:0
success:^(UIImage *image, BOOL cached)
{
//Display when finish download
myCell.imageView.image = image;
[myCell.cellIndicator stopAnimating];
} failure:nil];
return cell;
}
*EDIT:
If problem still not solve on device: try to separate download and display
#interface ViewController ()
#property (retain , nonatomic) NSMutableArray *linksArray;
#property (retain , nonatomic) NSMutableArray *imagesArray;
#end
- (void)viewDidLoad
{
[super viewDidLoad];
[self initLinksArray];
//Add placehoder.png to your imagesArray
[self initImagesArray];
//Download to NSMultableArray
[self performSelectorInBackground:#selector(downloadImages) withObject:nil];
}
- (void)initImagesArray{
self.imagesArray = [[[NSMutableArray alloc] init] autorelease];
for (NSInteger i = 0; i < self.linksArray.count; i++) {
[self.imagesArray addObject:[UIImage imageNamed:#"placeholder.png"]];
}
}
- (void)downloadImages{
for (NSInteger i = 0; i < self.linksArray.count; i++) {
NSURL *cellImageURL = [NSURL URLWithString:[self.linksArray objectAtIndex:i]];
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:cellImageURL
delegate:self
options:0
success:^(UIImage *image, BOOL cached)
{
[self.imagesArray replaceObjectAtIndex:i withObject:image];
} failure:nil];
}
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [self.imagesArray count];;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
SDCell *cell = (SDCell *)[cv dequeueReusableCellWithReuseIdentifier:#"SDCell" forIndexPath:indexPath];
cell.cellImageView.image = [self.imagesArray objectAtIndex:indexPath.row];
return cell;
}