如何在异步获取 JSON 数据以填充到 UITableView 中时显示 UIActivityIndicatorView?
Posted
技术标签:
【中文标题】如何在异步获取 JSON 数据以填充到 UITableView 中时显示 UIActivityIndicatorView?【英文标题】:How to display UIActivityIndicatorView while fetching JSON data asynchronously to be populated in UITableView? 【发布时间】:2014-09-14 19:08:27 【问题描述】:我正在调用一个 GET API,它接受一个字符串关键字并返回我的 JSON 数据,我解析并显示在我的 UITableView 中
当 API 返回数据时,我正在显示 UIActivityIndicatorView,这工作正常。 但是,一旦收到数据,UIActivityIndicatorView 就会按预期消失,但数据不会显示在 UITableView 中,但是如果我触摸屏幕上的任何位置,数据就会在 UI TableView 中可见。
这是我的代码:
-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
[indicator startAnimating];
indicator.hidesWhenStopped = YES;
dispatch_queue_t queue = dispatch_queue_create("ID", NULL);
dispatch_async(queue, ^
NSString *searchText=searchBar.text;
NSString *trimmedString = [searchText stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
if (trimmedString.length==0)
isFilter=NO;
UIAlertView *noConn = [[UIAlertView alloc] initWithTitle:@"ERROR" message:@"Please enter something in search bar" delegate:self cancelButtonTitle:nil otherButtonTitles:@"ok", nil];
[noConn show];
else
NSString *searchNew = [trimmedString stringByReplacingOccurrencesOfString:@" " withString:@"%20"];
isFilter=YES;
@try
[label removeFromSuperview];
_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* myURLString = [NSString stringWithFormat:@"www.example.com=%@", searchNew];
NSURL *url = [NSURL URLWithString:myURLString];
NSData* data = [NSData dataWithContentsOfURL:url];
if ((unsigned long)data.length > 3)
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(@"error");
[self.myTableView reloadData];
else
if(IDIOM == IPAD)
[self.myTableView reloadData];
self.tableView.separatorColor = [UIColor colorWithRed:255/255.0 green:255/255.0 blue:255/255.0 alpha:1.0];
label = [[UILabel alloc] initWithFrame:CGRectMake(150, 200, 200, 100)];
label.text=@"No Article Found";
label.backgroundColor = [UIColor clearColor];
[self.view addSubview:label];
else
[self.myTableView reloadData];
self.tableView.separatorColor = [UIColor colorWithRed:255/255.0 green:255/255.0 blue:255/255.0 alpha:1.0];
label = [[UILabel alloc] initWithFrame:CGRectMake(90, 100, 200, 100)];
label.text=@"No Article Found";
label.backgroundColor = [UIColor clearColor];
[self.view addSubview:label];
@catch (NSException *exception)
dispatch_async(dispatch_get_main_queue(), ^
[indicator performSelectorOnMainThread:@selector(stopAnimating) withObject:nil waitUntilDone:YES];
);
);
[self.mySearchBar resignFirstResponder];
【问题讨论】:
【参考方案1】:您的基本问题是您试图从后台线程更新 UI。 所有 UI 更新必须在主线程/队列上完成。
通常最简单的方法是使用:
dispatch_async(dispatch_get_main_queue(), ^
// code to run on the main queue
);
当您在此处停止 UIActiviteIndicatorView
时,我实际上看到您正在使用它:
dispatch_async(dispatch_get_main_queue(), ^
[indicator performSelectorOnMainThread:@selector(stopAnimating) withObject:nil waitUntilDone:YES];
);
但是,在这种情况下,您实际上将 stopAnimating
方法分派到主队列两次。你只需要这个:
dispatch_async(dispatch_get_main_queue(), ^
[indicator stopAnimating];
);
至于您的表没有更新,那是因为您需要将所有 reloadData
调用分派到主队列。
您的代码中有很多地方需要分派回主队列,但与其将dispatch_async
中的所有位置都包装到主队列中,还有一种更简单的方法。我看到的唯一地方是你实际上在做一些应该在后台线程上做的事情是这一行:
NSData* data = [NSData dataWithContentsOfURL:url];
这意味着您可以在方法开始时去掉dispatch_async(queue, ^...);
,而只在调用[NSData dataWithContentsOfUrl:url]
之前这样做。然后,dispatch_async
之后立即回到主队列。
像这样:
-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
[indicator startAnimating];
indicator.hidesWhenStopped = YES;
NSString *searchText=searchBar.text;
NSString *trimmedString = [searchText stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
if (trimmedString.length==0)
isFilter=NO;
UIAlertView *noConn = [[UIAlertView alloc] initWithTitle:@"ERROR" message:@"Please enter something in search bar" delegate:self cancelButtonTitle:nil otherButtonTitles:@"ok", nil];
[noConn show];
else
NSString *searchNew = [trimmedString stringByReplacingOccurrencesOfString:@" " withString:@"%20"];
isFilter=YES;
@try
[label removeFromSuperview];
_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* myURLString = [NSString stringWithFormat:@"www.example.com=%@", searchNew];
NSURL *url = [NSURL URLWithString:myURLString];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
NSData* data = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^
if ((unsigned long)data.length > 3)
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(@"error");
[self.myTableView reloadData];
else
if(IDIOM == IPAD)
[self.myTableView reloadData];
self.tableView.separatorColor = [UIColor colorWithRed:255/255.0 green:255/255.0 blue:255/255.0 alpha:1.0];
label = [[UILabel alloc] initWithFrame:CGRectMake(150, 200, 200, 100)];
label.text=@"No Article Found";
label.backgroundColor = [UIColor clearColor];
[self.view addSubview:label];
else
[self.myTableView reloadData];
self.tableView.separatorColor = [UIColor colorWithRed:255/255.0 green:255/255.0 blue:255/255.0 alpha:1.0];
label = [[UILabel alloc] initWithFrame:CGRectMake(90, 100, 200, 100)];
label.text=@"No Article Found";
label.backgroundColor = [UIColor clearColor];
[self.view addSubview:label];
[indicator stopAnimating];
);
);
@catch (NSException *exception)
[self.mySearchBar resignFirstResponder];
注意:您在这一种方法中做了很多工作。我建议将其拆分为多种方法,以使您的代码更具可读性和可维护性。
【讨论】:
非常感谢,它工作得很好。这正是我想要的。 再次感谢您的回答,我有另一个问题请您帮帮我。这是链接:***.com/questions/25838122/…【参考方案2】:尝试使用NSURLConnection
,这样可以省去很多麻烦,并使您的 URL 请求更易于管理
@interface myTableView : UITableViewController<NSURLConnectionDelegate>
NSMutableData *_responseData;
然后使用委托方法解析接收到的数据,停止你的indicatorview,并重新加载你的tableview
#pragma mark NSURLConnection Delegate Methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
// A response has been received, this is where we initialize the instance var you created
// so that we can append data to it in the didReceiveData method
// Furthermore, this method is called each time there is a redirect so reinitializing it
// also serves to clear it
_responseData = [[NSMutableData alloc] init];
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
// Append the new data to the instance variable you declared
[_responseData appendData:data];
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse*)cachedResponse
// Return nil to indicate not necessary to store a cached response for this connection
return nil;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
// The request is complete and data has been received
// You can parse the stuff in your instance variable now
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
// The request has failed for some reason!
// Check the error var
并在任何你想要的地方发出你的 URL 请求
// Create the request.
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://google.com"]];
// Create url connection and fire request
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
source
【讨论】:
以上是关于如何在异步获取 JSON 数据以填充到 UITableView 中时显示 UIActivityIndicatorView?的主要内容,如果未能解决你的问题,请参考以下文章
如何在Objective-C中加载数据期间异步加载JSON数据并填充UITableView
从动态 HTTPService 异步获取数据以填充 Advanced Data Grid Flex