TableView 不显示 uiimage
TableView doesn't show uiimage
我有一个显示 Twitter 帐户提要的应用程序。所以我有用于提要内容的 ImageView、textLabel 和 detailLabel。问题是当所有数据加载完毕后,uiimage 并没有出现。当我单击单元格或上下滚动时,将设置图像。这是我的一些代码。
-(void)getImageFromUrl:(NSString*)imageUrl asynchronouslyForImageView:(UIImageView*)imageView andKey:(NSString*)key{
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:imageUrl];
__block NSData *imageData;
dispatch_sync(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
imageData =[NSData dataWithContentsOfURL:url];
if(imageData){
[self.imagesDictionary setObject:[UIImage imageWithData:imageData] forKey:key];
dispatch_sync(dispatch_get_main_queue(), ^{
imageView.image = self.imagesDictionary[key];
});
}
});
});
}
- (void)refreshTwitterHomeFeedWithCompletion {
// Request access to the Twitter accounts
ACAccountStore *accountStore = [[ACAccountStore alloc] init];
ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[accountStore requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error){
if (granted) {
NSArray *accounts = [accountStore accountsWithAccountType:accountType];
// Check if the users has setup at least one Twitter account
if (accounts.count > 0)
{
ACAccount *twitterAccount = [accounts objectAtIndex:0];
NSLog(@"request.account ...%@",twitterAccount.username);
NSURL* url = [NSURL URLWithString:@"https://api.twitter.com/1.1/statuses/home_timeline.json"];
NSDictionary* params = @{@"count" : @"50", @"screen_name" : twitterAccount.username};
SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeTwitter
requestMethod:SLRequestMethodGET
URL:url parameters:params];
request.account = twitterAccount;
[request performRequestWithHandler:^(NSData *responseData,
NSHTTPURLResponse *urlResponse, NSError *error) {
if (error)
{
NSString* errorMessage = [NSString stringWithFormat:@"There was an error reading your Twitter feed. %@",
[error localizedDescription]];
NSLog(@"%@",errorMessage);
}
else
{
NSError *jsonError;
NSArray *responseJSON = [NSJSONSerialization
JSONObjectWithData:responseData
options:NSJSONReadingAllowFragments
error:&jsonError];
if (jsonError)
{
NSString* errorMessage = [NSString stringWithFormat:@"There was an error reading your Twitter feed. %@",
[jsonError localizedDescription]];
NSLog(@"%@",errorMessage);
}
else
{
NSLog(@"Home responseJSON..%@",(NSDictionary*)responseJSON.description);
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadData:responseJSON];
});
}
}
}];
}
}
}];
}
-(void)reloadData:(NSArray*)jsonResponse
{
self.tweets = jsonResponse;
[self.tableView reloadData];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return self.tweets.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell";
SNTwitterCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell)
{
cell = [[SNTwitterCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
NSDictionary *tweetDictionary = self.tweets[indexPath.row];
NSDictionary *user = tweetDictionary[@"user"];
NSString *userName = user[@"name"];
NSString *tweetContaint = tweetDictionary[@"text"];
NSString* imageUrl = [user objectForKey:@"profile_image_url"];
[self getImageFromUrl:imageUrl asynchronouslyForImageView:cell.imageView andKey:userName];
cell.profileImage.image = [UIImage imageNamed:@"images.png"];
NSArray *days = [NSArray arrayWithObjects:@"Mon ", @"Tue ", @"Wed ", @"Thu ", @"Fri ", @"Sat ", @"Sun ", nil];
NSArray *calendarMonths = [NSArray arrayWithObjects:@"Jan", @"Feb", @"Mar",@"Apr", @"May", @"Jun", @"Jul", @"Aug", @"Sep", @"Oct", @"Nov", @"Dec", nil];
NSString *dateStr = [tweetDictionary objectForKey:@"created_at"];
for (NSString *day in days) {
if ([dateStr rangeOfString:day].location == 0) {
dateStr = [dateStr stringByReplacingOccurrencesOfString:day withString:@""];
break;
}
}
NSArray *dateArray = [dateStr componentsSeparatedByString:@" "];
NSArray *hourArray = [[dateArray objectAtIndex:2] componentsSeparatedByString:@":"];
NSDateComponents *components = [[NSDateComponents alloc] init];
NSString *aux = [dateArray objectAtIndex:0];
int month = 0;
for (NSString *m in calendarMonths) {
month++;
if ([m isEqualToString:aux]) {
break;
}
}
components.month = month;
components.day = [[dateArray objectAtIndex:1] intValue];
components.hour = [[hourArray objectAtIndex:0] intValue];
components.minute = [[hourArray objectAtIndex:1] intValue];
components.second = [[hourArray objectAtIndex:2] intValue];
components.year = [[dateArray objectAtIndex:4] intValue];
NSTimeZone *gmt = [NSTimeZone timeZoneForSecondsFromGMT:2];
[components setTimeZone:gmt];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
[calendar setTimeZone:[NSTimeZone systemTimeZone]];
NSDate *date = [calendar dateFromComponents:components];
NSString *tweetDate = [self getTimeAsString:date];
NSString *tweetValues = [NSString stringWithFormat:@"%@ :%@",userName,tweetDate];
cell.textLabel.text = [NSString stringWithFormat:@"%@",tweetValues];
cell.detailTextLabel.text = [NSString stringWithFormat:@"%@",tweetContaint];
[cell.detailTextLabel setFont:[UIFont fontWithName:@"Helvetica" size:20]];
return cell;
}
- (NSString*)getTimeAsString:(NSDate *)lastDate {
NSTimeInterval dateDiff = [[NSDate date] timeIntervalSinceDate:lastDate];
int nrSeconds = dateDiff;//components.second;
int nrMinutes = nrSeconds / 60;
int nrHours = nrSeconds / 3600;
int nrDays = dateDiff / 86400; //components.day;
NSString *time;
if (nrDays > 5){
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateStyle:NSDateFormatterShortStyle];
[dateFormat setTimeStyle:NSDateFormatterNoStyle];
time = [NSString stringWithFormat:@"%@", [dateFormat stringFromDate:lastDate]];
} else {
// days=1-5
if (nrDays > 0) {
if (nrDays == 1) {
time = @"1 day ago";
} else {
time = [NSString stringWithFormat:@"%d days ago", nrDays];
}
} else {
if (nrHours == 0) {
if (nrMinutes < 2) {
time = @"just now";
} else {
time = [NSString stringWithFormat:@"%d minutes ago", nrMinutes];
}
} else { // days=0 hours!=0
if (nrHours == 1) {
time = @"1 hour ago";
} else {
time = [NSString stringWithFormat:@"%d hours ago", nrHours];
}
}
}
}
return [NSString stringWithFormat:NSLocalizedString(@"%@", @"label"), time];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 100;
}
根本问题是标准 table 视图单元格的标准 imageView
属性 会根据 cellForRowAtIndexPath
完成时出现的图像自动调整自身大小.但是由于当您第一次显示 table 时还没有图像,单元格的布局就好像没有图像一样。当您异步更新图像视图的 image
时,它不会调整图像视图的大小。
有几种方法可以解决这个问题:
不要使用 UITableViewCell
提供的默认值 imageView
,而是使用 IBOutlet
定义自己的自定义单元格子类 [=16] =] 属性。确保此 UIImageView
具有固定布局(即,它不使用从底层图像派生的固有尺寸)。
如果这样做,您可以为自定义 UIImageView
出口异步更新 image
属性,并且由于布局不取决于图像的存在,任何image
的异步更新应该正确显示。
当您收到图像时,不要只设置图像视图的 image
属性,而是重新加载与 NSIndexPath
关联的整行,使用reloadRowsAtIndexPaths
。
如果您这样做,假设您从缓存中正确检索图像并且在 cellForRowAtIndexPath
完成之前这样做,单元格将被正确布局。
注意,如果你这样做,你将需要修复你的 getImageFromUrl
以实际尝试首先从缓存中检索图像(并从主队列中执行此操作,然后再分派到后台队列), 否则你将陷入死循环。
话说回来,这里还有更深层次的问题。
正如我上面提到的,您正在缓存图像,但在检索图像时从不使用缓存。
您正在异步更新图像视图。
您应该在启动新的异步提取之前初始化 UIImageView
的 image
属性,否则当重复使用单元格时,您会看到旧图像在那里,直到检索到新图像。
如果在调用 getImageFromUrl
和异步请求完成之间的间隔期间重用该单元会怎样?您将为错误的单元格更新图像视图。 (当通过慢速连接执行此操作时,此问题会更加明显。运行 您的代码使用网络 link 调节器来模拟慢速连接,您会看到我正在描述的问题。)
如果用户快速向下滚动到 table 中的第 100 行怎么办?可见单元格的网络请求将积压在其他 99 个图像请求之后。您甚至可能在慢速连接时遇到超时错误。
getImageFromUrl
中有一堆战术上的小问题。
为什么要从全局队列同步调度到另一个全局队列?那是不必要的。为什么调度 UI 同步更新到主线程?那是低效的。
为什么要在块外定义imageData
为__block
;只需在块中定义它,您不需要 __block
限定符。
如果您没有从网络请求中收到有效的 UIImage
怎么办(例如,您收到 404 错误消息);现有代码会崩溃。服务器可能提供的各种响应都不是有效图像,您确实必须确定这种情况(即确保不仅 NSData
您收到的不是 nil
,而且您从中创建的 UIImage
也不是 nil
。
我可能会使用 NSCache
而不是 NSMutableDictionary
作为缓存。此外,无论您使用 NSCache
还是 NSMutableDictionary
,您都希望确保响应内存压力事件并在需要时清空缓存。
我们可以解决所有这些单独的问题,但解决所有这些问题的工作量并不小。因此,我建议您考虑 SDWebImage or AFNetworking 的 UIImageView
类别。他们负责处理这些问题中的大部分,以及其他问题。它会让你的生活变得更加轻松。
我有一个显示 Twitter 帐户提要的应用程序。所以我有用于提要内容的 ImageView、textLabel 和 detailLabel。问题是当所有数据加载完毕后,uiimage 并没有出现。当我单击单元格或上下滚动时,将设置图像。这是我的一些代码。
-(void)getImageFromUrl:(NSString*)imageUrl asynchronouslyForImageView:(UIImageView*)imageView andKey:(NSString*)key{
dispatch_async(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:imageUrl];
__block NSData *imageData;
dispatch_sync(dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
imageData =[NSData dataWithContentsOfURL:url];
if(imageData){
[self.imagesDictionary setObject:[UIImage imageWithData:imageData] forKey:key];
dispatch_sync(dispatch_get_main_queue(), ^{
imageView.image = self.imagesDictionary[key];
});
}
});
});
}
- (void)refreshTwitterHomeFeedWithCompletion {
// Request access to the Twitter accounts
ACAccountStore *accountStore = [[ACAccountStore alloc] init];
ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[accountStore requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error){
if (granted) {
NSArray *accounts = [accountStore accountsWithAccountType:accountType];
// Check if the users has setup at least one Twitter account
if (accounts.count > 0)
{
ACAccount *twitterAccount = [accounts objectAtIndex:0];
NSLog(@"request.account ...%@",twitterAccount.username);
NSURL* url = [NSURL URLWithString:@"https://api.twitter.com/1.1/statuses/home_timeline.json"];
NSDictionary* params = @{@"count" : @"50", @"screen_name" : twitterAccount.username};
SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeTwitter
requestMethod:SLRequestMethodGET
URL:url parameters:params];
request.account = twitterAccount;
[request performRequestWithHandler:^(NSData *responseData,
NSHTTPURLResponse *urlResponse, NSError *error) {
if (error)
{
NSString* errorMessage = [NSString stringWithFormat:@"There was an error reading your Twitter feed. %@",
[error localizedDescription]];
NSLog(@"%@",errorMessage);
}
else
{
NSError *jsonError;
NSArray *responseJSON = [NSJSONSerialization
JSONObjectWithData:responseData
options:NSJSONReadingAllowFragments
error:&jsonError];
if (jsonError)
{
NSString* errorMessage = [NSString stringWithFormat:@"There was an error reading your Twitter feed. %@",
[jsonError localizedDescription]];
NSLog(@"%@",errorMessage);
}
else
{
NSLog(@"Home responseJSON..%@",(NSDictionary*)responseJSON.description);
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadData:responseJSON];
});
}
}
}];
}
}
}];
}
-(void)reloadData:(NSArray*)jsonResponse
{
self.tweets = jsonResponse;
[self.tableView reloadData];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return self.tweets.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell";
SNTwitterCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell)
{
cell = [[SNTwitterCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
NSDictionary *tweetDictionary = self.tweets[indexPath.row];
NSDictionary *user = tweetDictionary[@"user"];
NSString *userName = user[@"name"];
NSString *tweetContaint = tweetDictionary[@"text"];
NSString* imageUrl = [user objectForKey:@"profile_image_url"];
[self getImageFromUrl:imageUrl asynchronouslyForImageView:cell.imageView andKey:userName];
cell.profileImage.image = [UIImage imageNamed:@"images.png"];
NSArray *days = [NSArray arrayWithObjects:@"Mon ", @"Tue ", @"Wed ", @"Thu ", @"Fri ", @"Sat ", @"Sun ", nil];
NSArray *calendarMonths = [NSArray arrayWithObjects:@"Jan", @"Feb", @"Mar",@"Apr", @"May", @"Jun", @"Jul", @"Aug", @"Sep", @"Oct", @"Nov", @"Dec", nil];
NSString *dateStr = [tweetDictionary objectForKey:@"created_at"];
for (NSString *day in days) {
if ([dateStr rangeOfString:day].location == 0) {
dateStr = [dateStr stringByReplacingOccurrencesOfString:day withString:@""];
break;
}
}
NSArray *dateArray = [dateStr componentsSeparatedByString:@" "];
NSArray *hourArray = [[dateArray objectAtIndex:2] componentsSeparatedByString:@":"];
NSDateComponents *components = [[NSDateComponents alloc] init];
NSString *aux = [dateArray objectAtIndex:0];
int month = 0;
for (NSString *m in calendarMonths) {
month++;
if ([m isEqualToString:aux]) {
break;
}
}
components.month = month;
components.day = [[dateArray objectAtIndex:1] intValue];
components.hour = [[hourArray objectAtIndex:0] intValue];
components.minute = [[hourArray objectAtIndex:1] intValue];
components.second = [[hourArray objectAtIndex:2] intValue];
components.year = [[dateArray objectAtIndex:4] intValue];
NSTimeZone *gmt = [NSTimeZone timeZoneForSecondsFromGMT:2];
[components setTimeZone:gmt];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
[calendar setTimeZone:[NSTimeZone systemTimeZone]];
NSDate *date = [calendar dateFromComponents:components];
NSString *tweetDate = [self getTimeAsString:date];
NSString *tweetValues = [NSString stringWithFormat:@"%@ :%@",userName,tweetDate];
cell.textLabel.text = [NSString stringWithFormat:@"%@",tweetValues];
cell.detailTextLabel.text = [NSString stringWithFormat:@"%@",tweetContaint];
[cell.detailTextLabel setFont:[UIFont fontWithName:@"Helvetica" size:20]];
return cell;
}
- (NSString*)getTimeAsString:(NSDate *)lastDate {
NSTimeInterval dateDiff = [[NSDate date] timeIntervalSinceDate:lastDate];
int nrSeconds = dateDiff;//components.second;
int nrMinutes = nrSeconds / 60;
int nrHours = nrSeconds / 3600;
int nrDays = dateDiff / 86400; //components.day;
NSString *time;
if (nrDays > 5){
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateStyle:NSDateFormatterShortStyle];
[dateFormat setTimeStyle:NSDateFormatterNoStyle];
time = [NSString stringWithFormat:@"%@", [dateFormat stringFromDate:lastDate]];
} else {
// days=1-5
if (nrDays > 0) {
if (nrDays == 1) {
time = @"1 day ago";
} else {
time = [NSString stringWithFormat:@"%d days ago", nrDays];
}
} else {
if (nrHours == 0) {
if (nrMinutes < 2) {
time = @"just now";
} else {
time = [NSString stringWithFormat:@"%d minutes ago", nrMinutes];
}
} else { // days=0 hours!=0
if (nrHours == 1) {
time = @"1 hour ago";
} else {
time = [NSString stringWithFormat:@"%d hours ago", nrHours];
}
}
}
}
return [NSString stringWithFormat:NSLocalizedString(@"%@", @"label"), time];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 100;
}
根本问题是标准 table 视图单元格的标准 imageView
属性 会根据 cellForRowAtIndexPath
完成时出现的图像自动调整自身大小.但是由于当您第一次显示 table 时还没有图像,单元格的布局就好像没有图像一样。当您异步更新图像视图的 image
时,它不会调整图像视图的大小。
有几种方法可以解决这个问题:
不要使用
UITableViewCell
提供的默认值imageView
,而是使用IBOutlet
定义自己的自定义单元格子类 [=16] =] 属性。确保此UIImageView
具有固定布局(即,它不使用从底层图像派生的固有尺寸)。如果这样做,您可以为自定义
UIImageView
出口异步更新image
属性,并且由于布局不取决于图像的存在,任何image
的异步更新应该正确显示。当您收到图像时,不要只设置图像视图的
image
属性,而是重新加载与NSIndexPath
关联的整行,使用reloadRowsAtIndexPaths
。如果您这样做,假设您从缓存中正确检索图像并且在
cellForRowAtIndexPath
完成之前这样做,单元格将被正确布局。注意,如果你这样做,你将需要修复你的
getImageFromUrl
以实际尝试首先从缓存中检索图像(并从主队列中执行此操作,然后再分派到后台队列), 否则你将陷入死循环。
话说回来,这里还有更深层次的问题。
正如我上面提到的,您正在缓存图像,但在检索图像时从不使用缓存。
您正在异步更新图像视图。
您应该在启动新的异步提取之前初始化
UIImageView
的image
属性,否则当重复使用单元格时,您会看到旧图像在那里,直到检索到新图像。如果在调用
getImageFromUrl
和异步请求完成之间的间隔期间重用该单元会怎样?您将为错误的单元格更新图像视图。 (当通过慢速连接执行此操作时,此问题会更加明显。运行 您的代码使用网络 link 调节器来模拟慢速连接,您会看到我正在描述的问题。)如果用户快速向下滚动到 table 中的第 100 行怎么办?可见单元格的网络请求将积压在其他 99 个图像请求之后。您甚至可能在慢速连接时遇到超时错误。
getImageFromUrl
中有一堆战术上的小问题。为什么要从全局队列同步调度到另一个全局队列?那是不必要的。为什么调度 UI 同步更新到主线程?那是低效的。
为什么要在块外定义
imageData
为__block
;只需在块中定义它,您不需要__block
限定符。如果您没有从网络请求中收到有效的
UIImage
怎么办(例如,您收到 404 错误消息);现有代码会崩溃。服务器可能提供的各种响应都不是有效图像,您确实必须确定这种情况(即确保不仅NSData
您收到的不是nil
,而且您从中创建的UIImage
也不是nil
。
我可能会使用
NSCache
而不是NSMutableDictionary
作为缓存。此外,无论您使用NSCache
还是NSMutableDictionary
,您都希望确保响应内存压力事件并在需要时清空缓存。
我们可以解决所有这些单独的问题,但解决所有这些问题的工作量并不小。因此,我建议您考虑 SDWebImage or AFNetworking 的 UIImageView
类别。他们负责处理这些问题中的大部分,以及其他问题。它会让你的生活变得更加轻松。