Handling UI update in a for loop - iOS - ios

I'm using NSNotificationcentre to update the UI from a for loop. The UI isn't updated until the execution is out of the loop. Is there way to handle this case?
Here is my code below:
- (void)uploadContent{
NSURLResponse *res = nil;
NSError *err = nil;
for (int i = 0; i < self.requestArray.count; i++) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[NSNotificationCenter defaultCenter] postNotificationName:kUpdatePreviewImageView object:nil userInfo:#{#"image": [self.imageArray objectAtIndex:i],#"count":[NSNumber numberWithInt:i],#"progress":[NSNumber numberWithFloat:0.5f]}];
}];
ImageUploadRequest *request = [self.requestArray objectAtIndex:i];
NSData *data = [NSURLConnection sendSynchronousRequest:request.urlRequest returningResponse:&res error:&err];
if (err) {
NSLog(#"error:%#", err.localizedDescription);
}
NSError *jsonError;
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&jsonError];
NSLog(#"current thread %#",[NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[NSNotificationCenter defaultCenter] postNotificationName:kUpdatePreviewImageView object:nil userInfo:#{#"image":[self.imageArray objectAtIndex:i],#"count":[NSNumber numberWithInt:i],#"progress":[NSNumber numberWithFloat:1.0f]}];
}];
}
[[NSNotificationCenter defaultCenter] postNotificationName:kImageUploaded object:nil];
}
In my viewcontroller.m file I have the observer declared under viewdidLoad()
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updatePreviewView:) name:kUpdatePreviewImageView object:nil];
The updatepreview: class is defined below:
-(void)updatePreviewView:(NSNotification *)notify{
NSDictionary *previewImageDetails = [notify userInfo];
self.previewImageView.image = previewImageDetails[#"image"];
hud.labelText = [NSString stringWithFormat:#"Uploading media %# of %lu",previewImageDetails[#"count"],(long unsigned)self.mediaDetail.count];
hud.progress = [previewImageDetails[#"progress"] floatValue];
}

Since the for loop is running the main thread this thread gets blocked until the for look is completed. Since the main threat is also the UI thread your UI updated aren't done until the loop is finished.
You should run the loop on a background thread an the UI changes should them be run asynchronies on the main thread.
And in your updatePreviewView: make sure the code will run on the main thread.

Do this:
-(void)updatePreviewView:(NSNotification *)notify{
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *previewImageDetails = [notify userInfo];
self.previewImageView.image = previewImageDetails[#"image"];
hud.labelText = [NSString stringWithFormat:#"Uploading media %# of %lu",previewImageDetails[#"count"],(long unsigned)self.mediaDetail.count];
hud.progress = [previewImageDetails[#"progress"] floatValue];
});
}

You should take it in the main thread. But NSOperationQueue could be not sending all in for loop. You can take operation in async queue and send it without NSOperationQueue
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *previewImageDetails = [notify userInfo];
self.previewImageView.image = previewImageDetails[#"image"];
hud.labelText = [NSString stringWithFormat:#"Uploading media %# of %lu",previewImageDetails[#"count"],(long unsigned)self.mediaDetail.count];
hud.progress = [previewImageDetails[#"progress"] floatValue];
});

Related

UIlabel text not getting update when download files in progress

I am trying to update the `UILabel` i.e downloaded data and remeaning data to be downloaded estimated time and total size of the downloading files via `NSnotificationCenter`, but not being updated `UILabel` text Please help me on this.
Also tried putting the `NSnotificationCenter` block in the main thread but no result found.
I have tried like this:
- (AFHTTPRequestOperation )downloadMediaOperation:(ILSCDowloadMedia )media success:(void (^)(ILSCDowloadMedia *media))success {
if (media.mediaUrl.length == 0) nil;
__block NSString *mediaKey = [[NSUserDefaults standardUserDefaults] objectForKey:media.mediaUrl];
NSURL *url = [NSURL URLWithString:media.mediaUrl];
if (mediaKey.length == 0) {
mediaKey = [NSString stringWithFormat:#"%#.%#", [ILSCUtility createUUID], [[[url path] lastPathComponent] pathExtension]];
}
NSFileManager *fileManager= [NSFileManager defaultManager];
NSString *mediaFilePath = NIPathForDocumentsResource(mediaKey);
media.mediaFilePath = mediaFilePath; if (![fileManager fileExistsAtPath:mediaFilePath]) {
__weak ILSCSyncManager *weakSelf = self;
NSURLRequest *request = [self.HTTPClient requestWithMethod:#"GET" path:[url path] parameters:nil];
AFHTTPRequestOperation *downLoadOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
downLoadOperation.downloadSpeedMeasure.active = YES; [downLoadOperation setShouldExecuteAsBackgroundTaskWithExpirationHandler:^{
// Clean up anything that needs to be handled if the request times out
// It may be useful to initially check whether the operation finished or was cancelled
}];
downLoadOperation.outputStream = [NSOutputStream outputStreamToFileAtPath:mediaFilePath append:NO];
[downLoadOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[[NSUserDefaults standardUserDefaults] setObject:mediaKey forKey:media.mediaUrl];
[[NSUserDefaults standardUserDefaults] synchronize];
if (success) {
success(media);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NIDERROR(#"y error %#", [error localizedDescription]);
__strong ILSCSyncManager *strongSelf = weakSelf;
strongSelf.numberOfDownloadErrors++;
}];
[downLoadOperation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead)
{
NSLog(#"vvv Byted total expected to read %f",totalImagesBytesExpectedToRead);
totalImagesBytesRead += bytesRead;
humanReadableSpeed = downLoadOperation.downloadSpeedMeasure.humanReadableSpeed;
humanReadableRemaingTime = [downLoadOperation.downloadSpeedMeasure humanReadableRemainingTimeOfTotalSize:totalImagesBytesExpectedToRead numberOfCompletedBytes:totalImagesBytesRead];
NSLog(#"Speed Human %#",humanReadableSpeed);
NSLog(#"Time is human read %#",humanReadableRemaingTime);
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"UpdateProgressBar" object:[NSString stringWithFormat:#"%#-%f-%f-%#", humanReadableSpeed,totalImagesBytesRead,totalImagesBytesExpectedToRead,humanReadableRemaingTime]];
});
}];
return downLoadOperation;
} else {
if (success) {
success(media);
}
}
return nil;
}
Please help me on this.
This is the listener of the NSnotification please check and please let me know.
I add this class as Loader while once down load starts.
I have gone through some of the sites as i got some information NSOperation queue is runs in the background thread . i am not sure on this please help me .
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:#"UpdateProgressBar" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
NSString *str =[note object]; NSArray *arrayTotalOperationsIn = [str componentsSeparatedByString:#"-"];
NSLog(#"%#",arrayTotalOperationsIn); self.lblSpeedMeasure.text =[NSString stringWithFormat:#"Internet Speed - %#" ,[arrayTotalOperationsIn objectAtIndex:0]];
float bytesRead = [[arrayTotalOperationsIn objectAtIndex:1] floatValue];
float bytesExpectedToRead = [[arrayTotalOperationsIn objectAtIndex:2] floatValue];
NSString *timeExpectedToRead = [arrayTotalOperationsIn objectAtIndex:3];
self.progressCountTextLabel.text=[NSString stringWithFormat:#"%.2f MB/%.2f MB - %# Left",bytesRead/1000000,bytesExpectedToRead/1000000,timeExpectedToRead];
}];
The above is the listener of the NSnotification please check and please let me know.
I add this class as Loader while once down load starts.
I have gone through some of the sites as i got some information NSOperation queue is runs in the background thread . i am not sure on this please help me .
Try calling the setNeedsDisplay method on your UILabel after setting the text
[self.progressCountTextLabel setNeedsDisplay];

How can i reload the table after download json

I have a problem when reloading the table after downloading the data in JSON format.
Use the NSOperation to download data async.
The code that i use it's this
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadInformactionToSql];
}
-(void)loadInformactionToSql {
NSOperationQueue * queue = [NSOperationQueue new];
NSInvocationOperation * operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(downloadJSONBDD) object:nil];
[queue addOperation:operation];
}
-(void)downloadJSONBDD {
NSURL * url = [NSURL URLWithString:#"http://judokatium.com/index.php/api/Belts/getBeltsWithTechnicals"];
//Leer el JSON
NSData * allCinturonesTecnicasJson =
[[NSData alloc] initWithContentsOfURL:url];
NSError *error;
NSArray * allCinturonesJson =
[NSJSONSerialization JSONObjectWithData:allCinturonesTecnicasJson options:kNilOptions error:&error];
if(error) {
NSLog(#"%#, %#", [error localizedDescription], [error localizedFailureReason]);
} else {
NSDictionary * cintns;
cinturones = [[NSMutableArray alloc] init];
for(int i = 0; i < [allCinturonesJson count]; i++){
JLMCinturon * cinturon = [[JLMCinturon alloc] init];
cintns = [allCinturonesJson objectAtIndex:i];
cinturon.idCinturon = [cintns objectForKey:#"id"];
[cinturones addObject:cinturon];
}
[self.tablaCinturones reloadData];
self.tablaCinturones.hidden = NO;
}
}
The downloaded data are correct, but not shown in the table.
How can i fix it?
Thanks and Sorry for my bad english.
Put these lines
[self.tablaCinturones reloadData];
self.tablaCinturones.hidden = NO;
into a dispatch block that moves them to the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self.tablaCinturones reloadData];
self.tablaCinturones.hidden = NO;
});
The problem is that NSOperation moves your method calls to a different thread, and the UI cannot be updated in iOS from any thread but the main one.
Or, you could use NSOperation as you already have and as #JulianKról pointed out.
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
NSInvocationOperation *reloadOperation = [[NSInvocationOperation alloc] initWithTarget:self.tablaCinturones selector:#selector(reloadData) object:nil];
NSInvocationOperation *hiddenOperation = [[NSInvocationOperation alloc] initWithTarget:self.tablaCinturones selector:#selector(setHidden:) object:#(NO)];
[mainQueue addOperation:reloadOperation];
[mainQueue addOperation:hiddenOperation];

AFNetworking 2 Retry Pattern

I'm using AFNetworking 2.2.3, AFNetworking+AutoRetry 0.0.3 and AFKissXMLRequestOperation#aceontech 0.0.4.
I have the following codes to fetch data from server:
+ (void)doNetwork {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFKissXMLResponseSerializer serializer];
NSDictionary *param = [NSDictionary dictionaryWithObjectsAndKeys:#"someValue", #"someKey", nil];
[manager POST:#"http://example.com/api/" parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSError *error;
DDXMLDocument *xml = [[DDXMLDocument alloc] initWithXMLString:operation.responseString options:0 error:&error];
if(error != nil) {
NSLog(#"Error Parsing XML");
[[NSNotificationCenter defaultCenter] postNotificationName:#"FetchAPINotification" object:nil];
} else {
NSString *xPath = #"response/status";
NSArray *arr_status = [xml nodesForXPath:xPath error:nil];
if(arr_status == nil || arr_status.count == 0) {
NSLog(#"Status Not Found");
[[NSNotificationCenter defaultCenter] postNotificationName:#"FetchAPINotification" object:nil];
} else {
int status = [[arr_status objectAtIndex:0] intValue];
if(status == 0) { // OK
[[NSNotificationCenter defaultCenter] postNotificationName:#"FetchAPINotification" object:nil userInfo:[NSDictionary dictionaryWithObjectsAndKeys:#"OK", #"status", nil];
} else if(status == 123) { // Requires Re-login
[self doLogin];
// How should I call the method again?
[self doNetwork];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:#"FetchAPINotification" object:nil];
}
}
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Network Error");
[[NSNotificationCenter defaultCenter] postNotificationName:#"FetchAPINotification" object:nil];
} autoRetry:3];
}
Here is the explanation:
First of all, I issue a HTTP POST request using AFHTTPRequestOperationManager with param. Then, in success block, if status = 0, post a notification to pre-defined notification observer to mark successful.
If there is any error, I post a notification without userInfo to mark the operation is unsuccessful.
However, there is a case that when the server responses status = 123, which means user token has expired and has to re-login to refresh its token.
My question is: How can I re-try the operation after re-login?
Note: I'm not talking about network timeout retry, which I have already implemented.

Progress View and background loading

my code is loading 7 pics from url and adding their data to an array. in the end of the process I do get an 8 objects array, but I'm trying to show a progress bar until the process of loading all the photos finished.
I do not have an idea how to do that...
here is the code
-(void)SetUpDrinks
{
loadingView.hidden=NO;
[loadingView_activity startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[UIApplication sharedApplication].networkActivityIndicatorVisible=YES;
imgsDATA = [[NSMutableArray alloc] init];
for (int i=0; i<8; i++) {
imageDownloadNum++;
absPath = [NSString stringWithFormat:#"http://domain/app/menu/drinks/%i.png",imageDownloadNum];
trimmedAbsPath = [absPath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSURL *imgURL = [NSURL URLWithString:trimmedAbsPath];
NSLog(#"%#",imgURL);
imgDATA = [[NSData alloc] initWithContentsOfURL:imgURL];
[imgsDATA addObject:imgDATA];
}
dispatch_async(dispatch_get_main_queue(), ^{
loadingView.hidden=YES;
[loadingView_activity stopAnimating];
[UIApplication sharedApplication].networkActivityIndicatorVisible=NO;
[self RefreshImg];
});
});
}
You could make an NSOperation subclass to download your image and then use a dispatch_group. Dispatch groups are a way to block a thread until one or more tasks finish executing - in this scenario we are waiting for all of your downloads to finish.
You can now use the progressBlock to update the UI and let the user know how many of the images have finished downloading.
If you want progress for an individual download then take a look at the NSURLConnection reference. In particular - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
- (void)enqueueGroupOfOperations:(NSArray *)operations
progress:(void (^)(NSUInteger completedCount, NSUInteger totalOperations))progressBlock
completion:(void (^)(NSArray *operations))completionBlock;
{
NSParameterAssert(operations);
NSParameterAssert(progress);
NSParameterAssert(completion);
__block dispatch_group_t group = dispatch_group_create();
NSBlockOperation *dependentOperation = [NSBlockOperation blockOperationWithBlock:^{
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
completion(operations);
});
dispatch_release(group);
}];
for (NSOperation *operation in operations) {
operation.completionBlock = ^{
dispatch_group_async(group, dispatch_get_main_queue(), ^{
NSUInteger count = [[operations indexesOfObjectsPassingTest:^BOOL(NSOperation *operation, NSUInteger idx, BOOL *stop) {
return [operation isFinished];
}] count];
progress(count, [operations count]);
dispatch_group_leave(group);
});
};
dispatch_group_enter(group);
[dependentOperation addDependency:operation];
}
[self.operationQueue addOperations:operations waitUntilFinished:NO];
[self.operationQueue addOperation:dependentOperation];
}
If this is too much for you then you can go over to AFNetworking where this is all done for you, https://github.com/AFNetworking/AFNetworking. But its always nice to know how some of this stuff works.

Nothing happens after calling [table reloadData] when called with dispatch_async

- (void) setRooms:(NSArray *)newRooms
{
NSLog(#"Main thread(cp3)... %d", [rooms count]);
rooms = newRooms;
[table reloadData];
NSLog(#"Main thread(cp4)... %d", [rooms count]);
}
- (void) parseJSONWithURL:(NSURL *)jsonURL
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSLog(#"Main thread(cp1)...%d", [rooms count]);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSLog(#"Background thread(cp1)...%d", [rooms count]);
NSError *error = nil;
// Request the data and store in a string.
NSString *resp = [NSString stringWithContentsOfURL:jsonURL
encoding:NSASCIIStringEncoding
error:&error];
// Convert the String into an NSData object.
NSData *data = [resp dataUsingEncoding:NSASCIIStringEncoding];
// Parse that data object using NSJSONSerialization without options.
NSDictionary *json = [[NSDictionary alloc] init];
json = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&error];
// Return to the main thread to update the UI elements
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(#"Main thread(cp2)...%d", [rooms count]);
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[self setRooms:[json valueForKey:#"Rooms"]];
});
NSLog(#"Background thread(cp2)...%d", [rooms count]);
});
NSLog(#"Main thread(cp5)...%d", [rooms count]);
}
Try this
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSError *error = nil;
// Request the data and store in a string.
NSString *resp = [NSString stringWithContentsOfURL:jsonURL
encoding:NSASCIIStringEncoding
error:&error];
if (error == nil) {
// Convert the String into an NSData object.
NSData *data = [resp dataUsingEncoding:NSASCIIStringEncoding];
// Parse that data object using NSJSONSerialization without options.
NSDictionary *json = [[NSDictionary alloc] init];
json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
dispatch_sync(dispatch_get_main_queue(), ^{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
_rooms = [json valueForKey:#"Rooms"];
[_table reloadData];
});
}
});
or try
[self performSelectorOnMainThread:#selector(udpateAfterFetch:) withObject: [json valueForKey:#"Rooms"] waitUntilDone:YES];
-(void)udpateAfterFetch:(NSArray or whatever *) yourObject
{
_rooms = yourObject;
[_table reloadData];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
The table is not being reloaded because any UI update should happen in the main thread and it is not happening so in ur lines of code.

Resources