In my application I'm implementing UITableView with data fetching from URL.
Everything goes fine.But the problem here is the data coming from backend url is too large. So I am showing UIActivityIndicatorView until data fetching completed.
Because of large data it's taking mostly 3 or 4 minutes to fetching. So how to update UITableView with data getting from url as soon as possible.
I'm using asynchronousRequest to do it.
I used this code in cellForRowAtIndexPath
NSString *post = [NSString stringWithFormat:#"skey=%#&user_id=%#",#"XXXXXX",#"3225"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://chkdin.com/dev/api/peoplearoundmexy/?%#",post]]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:nil];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data) {
id json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSMutableArray *designation=[json valueForKey:#"designation"];
UIImage *imageobj1=[UIImage imageNamed:#"userpic.jpg"];
NSData *imagedata = UIImagePNGRepresentation(imageobj1);
dispatch_async(dispatch_get_main_queue(), ^{
PeopleNearbyCell *updateCell = (id)[collectionview cellForItemAtIndexPath:indexPath];
if (updateCell)
cell.UserProfilePic.image = [UIImage imageWithData:imagedata];
});
}
}];
[task resume];
But it's not working. How can I achieve this?
The network request is asynchronous processing, cellforrowatindexpath is in the main thread, if you need to refresh the table in the network request, you need to write a network request method, and then get the data, in which the method of assignment, then refresh the table.
You can use lazyload method. This code in the cellForRow delegate.
[cell.loadActivity startAnimating];
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(backgroundQueue, ^{
// YOUR LOAD FROM URL METHOD HERE.
// only update UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
if (img==nil) {
// HANDLE WHEN IMAGE RETRIEVE FAILED
} else {
// ASSIGN IT
cell.UserProfilePic.image = img;
}
[cell.loadActivity stopAnimating];
});
});
Related
I have a large NSArray I am wanting to split into chunks and send to my web server, upon completion of each chunk I then need to update the fields in my SQLite DB that relate to each item in each array chunk.
This is the code I am currently running, where I try to use a call back to receive success or failure then update my local SQLite DB where appropriate.
- (void)postlowData:(NSArray *)lowMArray Callback:(void (^)(NSError *error, BOOL success))callback;
{
// Currently this method is sending the whole lowMArray
// What I want to do is Split lowMArray into a chunkArray (where chunk is 20 of the leading items from lowMArray)
// I would then send chunkArray with the following code, when I receive a response I then want to update local SQLite DB with result and recall this method to start on the next 20 chunks.
// Create Json data from lowMArray
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:lowMArray
options:NSJSONWritingPrettyPrinted
error:nil];
// Construct post request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#/lows", _silServerBaseUrl]]];
request = [self applyAuth:request];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json; charset=UTF-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:jsonData];
// Send post request
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
// NSLog(#"Response Failed!");
callback(error, NO);
} else {
// NSLog(#"Response Success!");
callback(error, YES);
// On success add itmes from lowChunkArray so that you can adjust sent_Flag later
}
}];
[dataTask resume]; // runs task
}
The issue I am running into is that when I run this code if I am splitting the array into chunks sending the chunk adjusting the main array for the next chunk I don't get a confirmed callback till the very end of all the requests, at which point I have lost track of what success or failure?
Maybe I am going about this the wrong way?
Update
I am now trying to do this using AFHTTPRequestOperation which seems to be working as a batch upload however the
setHTTPBody:jsonData
Never seems to make it to the server.
I used this Batch of Operations example to help me construct this method however as I said above the JSON data never makes it to the server.
- (void)postlowData:(NSArray *)lowMArray;
{
NSLog(#"Syncing Local");
NSArray *chunklow = [[NSArray alloc] init];
NSMutableArray *mutableOperations = [NSMutableArray array];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/lows", _silServerBaseUrl]];
//Test: creating 10 things to send
for (int i = 0; i < 10; i++) {
if ([lowMArray count] > 0) {
if ([lowMArray count] >= 20) {
low = [lowMArray subarrayWithRange:NSMakeRange(0, 20)];
} else if ([lowMArray count] < 20) {
low = [lowMArray subarrayWithRange:NSMakeRange(0, [lowMArray count])];
}
}
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:low
options:NSJSONWritingPrettyPrinted
error:nil];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request = [self applyAuth:request];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json; charset=UTF-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:jsonData];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[mutableOperations addObject:operation];
}
NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:mutableOperations progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
NSLog(#"%lu of %lu complete", numberOfFinishedOperations, totalNumberOfOperations);
} completionBlock:^(NSArray *operations) {
NSLog(#"All operations in batch complete");
NSLog(#"Syncing complete");
}];
[[NSOperationQueue mainQueue] addOperations:operations waitUntilFinished:NO];
}
For your problem of splitting insertion of a large array into chunks to be inserted to a DB via network operations, an NSOperationQueue can be created that will allow you to add a separate operation for each chunk of data to be inserted.
The queue can be set to run in a serial manner so that each operation will need to be complete before the next one is started.
Using a queue makes the multiple operations more manageable than having the flow be controlled by callbacks.
In summary, you create a queue and set its maximum concurrent operation count to 1. Then create an NSOperation subclass that performs the necessary steps to insert data into the database. Each chunk of data will correspond to a separate operation that will be added to the queue. Each operation will be performed in series until all are complete.
Here is an outline for the solution:
// Create a new queue to hold network operations.
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
// Split the large array into chunks of 20 items each.
NSInteger chunkSize = 20;
NSInteger i = 0;
NSInteger total = [lowMArray count];
while (i < total) {
NSInteger j = i;
NSMutableArray *chunk = [NSMutableArray alloc] init];
while (j < i + chunkSize - 1 && j < total) {
[chunk addObject:lowMArray[j]];
j++;
}
MyOperation *myOperation = [[MyOperation alloc] initWithArray:chunk];
self.operationQueue.addOperation(myOperation)
i += chunkSize;
}
MyOperation.h:
#interface MyOperation : NSOperation
- (instancetype)initWithArray:(NSArray *)chunk;
#property NSArray *chunk;
#end
MyOperation.m:
#implementation MyOperation
- (instancetype)initWithArray:(NSArray *)chunk
{
if (self = [super init]) {
self.chunk = chunk;
}
return self;
}
- (void)main
{
// Create Json data from lowMArray
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self.chunk
options:NSJSONWritingPrettyPrinted
error:nil];
// Construct post request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#/lows", _silServerBaseUrl]]];
request = [self applyAuth:request];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json; charset=UTF-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:jsonData];
// Send post request
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
// NSLog(#"Response Failed!");
} else {
// NSLog(#"Response Success!");
}
}];
[dataTask resume]; // runs task
}
#end
AFNetworking has support for its own NSOperation subclass in AFHTTPRequestOperation. An example can be found here. Also, the AFNetworking GitHub repository has an example for batch operations.
Based on your revised question, setting the completion block of each AFHTTPRequestOperation to handle the response and error can help to debug the problem.
Here is how it is done:
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) {
NSString* decodedResponse = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
NSLog(#"response %#", decodedResponse);
} failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
NSLog(#"error %#", error);
}];
It would be inserted after AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];.
In my app I am downloading image using blocks but it is freezing my UI. I have one network class which contains method to download image,
-(void)downloadImageWithCompletionHandler:^(NSData *aData, NSError *error)aBlock;
I am calling above method in my view controller to download image. So once the image is downloaded I am using NSData to show in image view. The network class method uses NSURLConnection methods to download the image.
[[NSURLConnection alloc] initWithRequest:theURLRequest delegate:self];
Once the data download is complete I am calling completion handler block of the view controller.
But I am not sure why my UI is freezing? Can anyone help me find where I am doing wrong?
Thanks in advance!
- (void) setThumbnailUrlString:(NSString *)urlString
{
NSString *url= [NSString stringWithFormat:#"%#",urlString];
//Set up Request:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]init];
[request setURL:[NSURL URLWithString:url]];
NSOperationQueue *queue=[[NSOperationQueue alloc] init];
if ( queue == nil ){
queue = [[NSOperationQueue alloc] init];
}
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse * resp, NSData *data, NSError *error)
{
dispatch_async(dispatch_get_main_queue(),^
{
if ( error == nil && data )
{
UIImage *urlImage = [[UIImage alloc] initWithData:data];
_headImageView.image=urlImage;
_backgroundImageView.image=urlImage;
}
});
}];
}
You need to download the image in background thread to avoid freezing the UI thread.There is a simple demo to achieve this.
- (void)downloadImageWithCompletionHandler:(void(^)(NSData *aData, NSError *error))aBlock {
NSURLRequest *theURLRequest = nil; // assign your request here.
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[NSURLConnection sendAsynchronousRequest:theURLRequest queue:mainQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// UIThread.
aBlock(data,connectionError);
}];
}
how to call this method.
[self downloadImageWithCompletionHandler:^(NSData *aData, NSError *error) {
// get UIImage.
UIImage *image = [UIImage imageWithData:aData];
}];
I figured out the problem. Problem was not in the block or using NSUrlConnection method, it is working properly. Problem was, I was saving data in file once I download it. This operation was happening on main thread which was blocking the UI.
I have a problem with my application.It freeze for several second when I tap the sidebar menu.
What happen when I tapped menu is I pass string that gonna be url for json data fetch in my mainviewcontroller.Then it freeze because I fetch the data and populating data in tableview.
However I really new to ios programming,I wonder how can I remove the freeze?.
thanks in advance
here is my code snippet for the mainviewcontroller:
Don't use dataWiyhContentsOfURL:, or at least not directly on the main thread. If you block the main thread then the whole app stops working (as you see).
You need to learn about background threads and callback blocks, and look at using NSURLSession to download your data and then process it.
Instead of using dataWithContentsOfURL (which will block the main thread and so the UI) you need to start an asynchronous connection. In the IF ELSE change the two requests to something like below. The completionHandler (Block) is executed when done, the data parsed, HUD removed and table Updated.
You can even (and in fact must) do this within your cellForRowAtIndexPath for each of the images, however, I would use SDWebImage as it has a cache and is very easy to use.
There are also other methods if this is not right for you such as NSURLSession.
Some other points;
I have also noted that the HUD is stopped on every iteration of the FOR and probably should be outside.
I also can not see how your data is being loaded so I added a [myTable reloadData];
I can not see that the "dictionary" object is needed as it can be added directly to the array (see code)
// If you have the status bar showing
// [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[HUD showUIBlockingIndicatorWithText:#"Please wait. . ."];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:kategori]];
[request setTimeoutInterval: 10.0];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
// [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if (data != nil && error == nil)
{
//All Worked
id jsonObjects = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
for (NSDictionary *dataDict in jsonObjects)
{
NSString *title_data = [dataDict objectForKey:#"title"];
NSString *thumbnail_data = [dataDict objectForKey:#"thumb"];
NSString *author_data = [dataDict objectForKey:#"creator"];
NSString *link_data = [dataDict objectForKey:#"link"];
[myObject addObject:[[NSDictionary alloc]initWithObjectsAndKeys:
title_data, title,
thumbnail_data, thumbnail,
author_data,author,
link_data,link,
nil]];
}
[HUD hideUIBlockingIndicator];
[myTableView reloadData];
}
else
{
// There was an error
}
}];
For the images something like (this is not tested). I am not sure what format your images are in but you should be able to just add it, this may need tweeking;
cell.imageView.frame = CGRectMake(0, 0, 80, 70);
__block UIImageView *cellImage = cell.imageView;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[tmpDict objectForKey:thumbnail]]];
[request setTimeoutInterval: 10.0];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (data != nil && error == nil)
{
//All Worked
cellImage.image = [[UIImage alloc]initWithData:data];
[cellImage layoutIfNeeded];
}
else
{
// There was an error
}
}];
You can start activity indicator and call fetch data method after few time...
- (void)viewDidLoad{
[activityIndicator startAnimating];
[self performSelector:#selector(fetchData) withObject:nil afterDelay:0.5];
}
- (void)fetchData{
Fetch your data over here
}
Or ideally you have to load data Asynchronous
For loading data Asynchronously check out the following link-
iphone-synchronous-and-asynchronous-json-parse
I Prefer MBProgressHUD.
Here is the link for 3rd Party API.
https://github.com/jdg/MBProgressHUD
Just copy these two files in your app.
MBProgressHUD.h
MBProgressHUD.m
I have some code that gets an image from a web page and displays it in an ImageView. But the image loads very slowly for some reason I don't really understand! Through my logging I can see that all the data for the image (base64 string) arrives pretty instantly, yet it takes about 12 - 15 seconds for the image to appear in the ImageView.
I find this very strange because I used an NSStream to get the data for the image in a different method and the image loaded as soon as all the data arrived. But with this URLSession method its taking longer for the image to load. This doesn't really make sense! This method shouldn't affect how the ImageView loads that data.
Has anybody any ideas why this might be happening?
heres the code:
- (void)postMethod:(NSDictionary *)numDict
{
NSURL *url = [NSURL URLWithString:#"http://theWebAddress.com/aPage.php"]; // add url to page
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
request.HTTPMethod = #"POST";
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:numDict options:kNilOptions error:&error];
NSLog(#"%#", numDict);
if (!error)
{
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:data completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *diction = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
for (id key in diction)
{
if ([key isEqualToString:#"text"])
{
NSLog(#"data is text");
self.messageLabel.text = diction[#"text"];
break;
}
else if ([key isEqualToString:#"image"])
{
NSLog(#"data is an image");
// gets the base64 string pretty instantly but takes 12 - 15 seconds to pop up in the imageView
NSData *ImgData = [[NSData alloc] init];
ImgData = [[NSData alloc] initWithBase64EncodedString:diction[#"image"] options:1];
self.ImageView.image = [UIImage imageWithData:ImgData];
break;
}
}
}];
[uploadTask resume];
}
}
many thanks!
Your completion handler might be operating on a background thread. UI updates should always work on the main thread. Put a break point at
self.ImageView.image = [UIImage imageWithData:ImgData];
and see if it is on the main thread. If not, dispatch it to the main thread before you set the ImageView.image:
dispatch_async(dispatch_get_main_queue(), ^{
self.ImageView.image = [UIImage imageWithData:ImgData];
});
You can try to use SDWebImage https://github.com/rs/SDWebImage and all you need is to set the image in imageView like this:
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
You are firstly downloading image and then showing image.You can download image by using lazy loading.
For this you can use EgoImageView not uiimageview.
self.ImageView.imageURL=[NSURL URLWithString:
here self.ImageView is of egoimageview type.
you can get this class from github.
https://github.com/enormego/EGOImageLoading
I am fetching the data from a web service by synchronous method. I make the request to the web service then view freezes. I try to add the UIActivityIndicatorView before loading the data from the web service and stopped it after getting the data but activity indicator is not displayed.
I tried to put the web service data fetch operations on the different thread
[NSThread detachNewThreadSelector:#selector(fetchRequest) toTarget:self withObject:nil];
but at this time TableView crashes as it does not get the data for drawing the cells.
in fetchRequest function I am doing
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL
URLWithString:URLString]];
NSData *response = [NSURLConnection sendSynchronousRequest:request
returningResponse:nil error:nil];
NSError *jsonParsingError = nil;
NSDictionary *tableData = [NSJSONSerialization JSONObjectWithData:response
options:0
error:&jsonParsingError];
responseArray = [[NSMutableArray alloc]initWithArray:[tableData objectForKey:#"data"]];
for(int i = 0; i < responseArray.count; i++)
{
NSArray * tempArray = responseArray[i];
responseArray[i] = [tempArray mutableCopy];
}
This responseArray is used to fill the information in the cell
Please tell me how to do this. Any help will be appreciated ...
The problem lies in your very approach. Synchronous methods run on the main thread. And because the UI updates on the main thread, your app hangs.
So, the solution would be using an asynchronous method to download the data on a separate thread, so that your UI won't hang.
So, use the NSURLConnection's sendAsynchronousRequest. Here's some sample code :
NSURL *url = [NSURL URLWithString:#"YOUR_URL_HERE"];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
//this is called once the download or whatever completes. So you can choose to populate the TableView or stopping the IndicatorView from a method call to an asynchronous method to do so.
}];
You should better use Grand Central Dispatch to fetch the data like this so you dispatch it in a background queue and do not block the main thread which is also used for UI updates:
dispatch_queue_t myqueue = dispatch_queue_create("myqueue", NULL);
dispatch_async(myqueue, ^(void) {
[self fetchRequest];
dispatch_async(dispatch_get_main_queue(), ^{
// Update UI on main queue
[self.tableView reloadData];
});
});
Regarding the Activity indicator you can use in the start of the parsing:
[self.activityIndicator startAnimating];
self.activityIndicator.hidesWhenStopped = YES
And then when your table is filled with data:
[self.activityIndicator stopAnimating];