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];
Related
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];
});
I have a view controller, that loads some an array. While everything is loading, I need to present another view controller (with the UIProgressView) and update it's UI (the progress property of a UIProgressView) and then dismiss and present first vc with downloaded data. I'm really struggling on it and I've tried delegation, but nothing worked for me.
- (void)viewDidLoad
{
[super viewDidLoad];
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"downloaded"]) {
} else {
NSLog(#"First time Launched");
ProgressIndicatorViewController *progressVC = [ProgressIndicatorViewController new];
progressVC.modalPresentationStyle = UIModalPresentationFullScreen;
[self syncContacts];
[self presentViewController:progressVC animated:YES completion:nil];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"downloaded"];
[progressVC release];
}
}
sync contacts method:
- (void)syncContacts
{
NSLog(#"Sync data");
NSMutableArray *allContacts = [ContactsOperations getAllContactsFromAddressBook];
NSInteger allContactsCount = [allContacts count];
if (allContactsCount > 0) {
for (ContactData *contact in allContacts) {
NSMutableArray *phoneNumbersArray = [[NSMutableArray alloc] init];
NSString *nospacestring = nil;
for (UserTelephone *tel in [contact.abonNumbers retain]) {
NSArray *words = [tel.phoneNumber componentsSeparatedByCharactersInSet :[NSCharacterSet whitespaceCharacterSet]];
NSString *nospacestring = [words componentsJoinedByString:#""];
[phoneNumbersArray addObject:nospacestring];
}
contact.abonNumbers = phoneNumbersArray;
if (phoneNumbersArray != nil) {
NSLog(#"NOT NULL PHONENUMBERS: %#", phoneNumbersArray);
}
NSDictionary *dataDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:contact.abonNumbers, #"phoneNumbers", contact.contactName, #"fullName", [NSNumber numberWithBool:contact.isBlackList], #"blacklist", [NSNumber numberWithBool:contact.isIgnore], #"ignore", contact.status, #"status", nil];
NSLog(#"dictionary: %#", dataDictionary);
NSError *error;
NSData *postData = [NSJSONSerialization dataWithJSONObject:dataDictionary options:0 error:&error];
NSLog(#"POST DATA IS : %#", postData);
NSMutableURLRequest *newRequest = [self generateRequest:[[NSString stringWithFormat:#"%#c/contacts%#%#", AVATATOR_ADDR, SESSION_PART, [[ServiceWorker sharedInstance] SessionID]] stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding] withHTTPMethod:#"POST"];
[newRequest setHTTPBody:postData];
[newRequest setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
//__block NSMutableData *newData;
[NSURLConnection sendAsynchronousRequest:newRequest queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
if (!connectionError) {
NSDictionary *allData = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"alldata from contacts: %#", allData);
//NSInteger errorCode = [[allData objectForKey:#"CommandRes"] integerValue];
//if (errorCode == 0) {
NSInteger remoteId = [[allData objectForKey:#"contactId"] integerValue];
contact.remoteId = remoteId;
NSLog(#"remote id is from parse content : %d", remoteId);
[[AvatatorDBManager getSharedDBManager]createContactWithContactData:contact];
} else {
NSLog(#"error");
}
}];
//Somewhere here I need to update the UI in another VC
[phoneNumbersArray release];
[dataDictionary release];
}
} else {
}
}
generate request method:
- (NSMutableURLRequest *)generateRequest:(NSString *)urlString withHTTPMethod:(NSString *)httpMethod
{
NSLog(#"url is :%#", urlString);
NSURL *url = [NSURL URLWithString:urlString];
request = [NSMutableURLRequest requestWithURL:url];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[request setHTTPMethod:httpMethod];
return request;
}
ProgressViewController is just an empty VC with the progress bar. No code yet.
In the view controller that will display the progress view expose a method like this...
- (void)updateProgress:(float)progress;
Its implementation will look like this...
- (void)updateProgress:(float)progress {
[self.progressView setProgress:progress animated:YES];
}
On the main view controller you need to execute the long-running process on a background thread. Here's viewDidLoad for the main view controller. This example code uses a property for the progress view controller (you may not require this) and assumes your are in a navigation controller...
- (void)viewDidLoad {
[super viewDidLoad];
// Create and push the progress view controller...
self.pvc = [[ProgressViewController alloc] init];
[self.navigationController pushViewController:self.pvc animated:YES];
// Your long-running process executes on a background thread...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Your long-running process goes here. Wherever required you would
// call updateProgress but that needs to happen on the main queue...
dispatch_async(dispatch_get_main_queue(), ^{
[self.pvc updateProgress:progress];
});
// At the end pop the progress view controller...
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
});
});
}
I'm trying to create a simple rss reader. The code works okay, except the UI hangs when the feeds are being updated. I thought I cobbled together the code to get the feed and parse it on a background queue while updating the UI on the mainQueue, but the table hangs pretty badly. Code below:
-(void)refreshFeed2
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (NSString *feed in _feeds) {
// iterate over all feeds
NSLog(#"feed=%#", feed);
NSURL *url = [NSURL URLWithString:feed];
// Create url connection and fire request
NSURLConnection *conn = [[NSURLConnection alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
(void)[conn initWithRequest:request delegate:self];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if ([data length] == 0 && error == nil) {
// handle empty response
} else if (error != nil) {
// handle error
NSLog(#"Error %#", [error localizedDescription]);
} else if ([httpResponse statusCode] == 200) {
// data present and no errors
[queue addOperationWithBlock:^{
// parse feed on queue
RXMLElement *rss = [RXMLElement elementFromXMLData:data];
RXMLElement *rssChild = [rss child:#"channel"];
RXMLElement* title = [rssChild child:#"title"];
NSArray* items = [[rss child:#"channel"] children:#"item"];
NSMutableArray* result=[NSMutableArray array];
for (RXMLElement *e in items) {
// iterate over the articles
RSSArticle* article = [[RSSArticle alloc] init];
article.sourceTitle = [title text];
article.articleTitle = [[e child:#"title"] text];
article.articleDescription = [[e child:#"description"] text];
article.articleUrl = [NSURL URLWithString: [[e child:#"link"] text]];
NSString *articleDateString = [[e child:#"pubDate"] text];
article.articleDate = [NSDate dateFromInternetDateTimeString:articleDateString formatHint:DateFormatHintRFC822];
if (article.articleUrl != NULL) {
[result addObject:article];
}
}
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// update table on mainQueue
for (RSSArticle *article in result) {
// iterate over articles
int insertIdx = [_allEntries indexForInsertingObject:article sortedUsingBlock:^(id a, id b) {
RSSArticle *entry1 = (RSSArticle *) a;
RSSArticle *entry2 = (RSSArticle *) b;
return [entry1.articleDate compare:entry2.articleDate];
}];
[_allEntries insertObject:article atIndex:insertIdx];
[self.LeftTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:insertIdx inSection:0]]
withRowAnimation:UITableViewRowAnimationFade];
}
}];
}];
}
}];
// Stop refresh control
[refreshControl endRefreshing];
}
}
Code that calls refreshFeed2:
- (void)viewDidLoad {
[super viewDidLoad];
self.allEntries = [NSMutableArray array];
self.feeds = [NSArray arrayWithObjects:
#"http://feeds.washingtonpost.com/rss/politics",
#"http://rss.cnn.com/rss/cnn_allpolitics.rss",
#"http://www.npr.org/rss/rss.php?id=1012",
#"http://www.slatedigital.com/support/feeds/rss_kb.php?s=fd5aa35e773dc3177b85a2126583f002",
nil];
}
//add refresh control to the table view
refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self
action:#selector(refreshInvoked:forState:)
forControlEvents:UIControlEventValueChanged];
NSString* fetchMessage = [NSString stringWithFormat:#"Fetching Articles"];
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:fetchMessage
attributes:#{NSFontAttributeName:[UIFont fontWithName:#"Helvetica" size:11.0]}];
[self.LeftTableView addSubview: refreshControl];
[self refreshInvoked:self forState:UIControlStateNormal];
}
-(void) refreshInvoked:(id)sender forState:(UIControlState)state {
NSOperationQueue *refreshQueue = [[NSOperationQueue alloc] init];
[refreshQueue addOperationWithBlock:^{
[self refreshFeed2];
}];
}
Any help?
Thanks!
Can you try this? replace
[self refreshInvoked:self forState:UIControlStateNormal];
by
[self performSelectorOnBackground:#selector(refreshFeed2) withObject:nil];
and replace the same instead of
-(void) refreshInvoked:(id)sender forState:(UIControlState)state {
[self performSelectorOnBackground:#selector(refreshFeed2) withObject:nil ];
}
The code below loads data into a UITableView by getting a google news RSS feed parsing the XML and putting it into the view . It works but when i push another view and come back the table view scroll is broken . I have isolated the problem to the GCD code . If i remove it the problem disappears . So here is the GCD code :
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"View did load ..");
self.title = #"News stories";
NewsItem *item = [[NewsItem alloc] init];
item.title = #"Loading ...";
self.newsItems = [#[item] mutableCopy];
NSString *URL = #"http://news.google.com/news?q=apple+OR+google+OR+microsoft&output=rss";
NSURL *xmlURL = [NSURL URLWithString:URL];
NSURLRequest *request = [NSURLRequest requestWithURL:xmlURL];
Parser *parser = [[Parser alloc] initXMLParser];;
dispatch_queue_t downloadQueue = dispatch_queue_create("news downloader", NULL);
dispatch_async(downloadQueue, ^{
BOOL success;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
if (data != nil) {
NSXMLParser *nsXmlParser = [[NSXMLParser alloc] initWithData:data];
// create and init our delegate
// set delegate
[nsXmlParser setDelegate:parser];
// parsing...
success = [nsXmlParser parse];
}
else {
success = FALSE;
}
// test the result
dispatch_async(dispatch_get_main_queue(), ^{
if (success) {
NSLog(#"reloading data ...");
self.newsItems = [parser.newsItems copy];
[self.tableView reloadData];
} else {
NewsItem *item = [[NewsItem alloc] init];
item.title = #"Error loading";
self.newsItems = [#[item] mutableCopy];
[self.tableView reloadData];
NSLog(#"Error parsing document!");
}
});
});
}
Try [self.tableView reloadData]; in viewDidAppear.
I've used dispatch_async to put in background a xml document's parsing, I've putted information in an array and, with a for cycle, I would assign the content of every element to an UILabel (for now), the problem is that in the output console I can see the right content of every element but the uilabel are added only in the end after a long delay.
Code:
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(kBgQueue, ^{
NSData *xmlData = [[NSMutableData alloc] initWithContentsOfURL:url];
NSError *error;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error];
NSArray *areaCortina = [doc nodesForXPath:#"query" error:nil];
int i=0;
for (GDataXMLElement *element in areaCortina) {
NSLog(#"%#",[[element attributeForName:#"LiftName"] stringValue]); //data are shown correctly
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, (30+10)*i, 200, 30)];// don't appear after log
[label setText:[[element attributeForName:#"name"] stringValue]];
[self.view performSelectorOnMainThread:#selector(addSubview:) withObject:label waitUntilDone:YES];
i++;
}
As you can see i've used performSelectorOnMainThread but nothig, the label doesn't appear once at once but are shown correctly only after a 10 or 15 seconds after the end of the block.
Ideas?
Thanks in advance
Ok edit 1
Thanks to the advice of Shimanski Artem I've now the following:
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
...
dispatch_async(kBgQueue, ^{
NSData *xmlData = [[NSMutableData alloc] initWithContentsOfURL:url];
NSError *error;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error];
NSArray *areaCortina = [doc nodesForXPath:#"query" error:nil];
self.data = [[NSMutableArray alloc] init];
for (GDataXMLElement *element in areaCortina) {
[self.data addObject:[[element attributeForName:#"name"] stringValue]];
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"name" object:self];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(caricaItem) name:#"name" object:nil];
...
} //end method
-(void) caricaItem
{
int i=0;
for (NSString * string in self.dati) {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, (30+10)*i, 200, 30)];
[label setText:string];
[self.view addSubview:label];
i++;
}
}
I've put uilabels creation out of dispatch, in the caricaItem method where i've ready an array full of precious data, but same delay... and the same if in caricaItem i use a UITableView...
What is the right way to proceed?
Thanks