我有一个显示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;
}
根本问题是标准表视图单元格的标准imageView
属性将根据cellForRowAtIndexPath
完成时出现的图像自动调整自身大小。但是,由于第一次展示表格时还没有图像,因此单元格的布局就好像没有图像一样。当异步更新图像视图的image
时,它不会调整图像视图的大小。
有几种方法可以解决这个问题:
-
不要使用
UITableViewCell
提供的默认imageView
,而是使用IBOutlet
到自己的UIImageView
属性来定义自己的自定义单元格子类。请确保此UIImageView
具有固定布局(即,它不使用从底层图像派生的固有大小)。如果执行此操作,则可以异步更新自定义
UIImageView
插座的image
属性,并且由于布局不取决于图像的存在,因此该image
的任何异步更新都应正确显示。 -
收到图像时,不要只设置图像视图的
image
属性,而是使用reloadRowsAtIndexPaths
重新加载与该NSIndexPath
关联的整行。如果执行此操作,则假定从缓存中正确检索图像,并且在
cellForRowAtIndexPath
完成之前进行检索,则单元将正确布局。请注意,如果您这样做,您将需要修复
getImageFromUrl
,以便首先尝试从缓存中检索图像(然后从主队列中检索,然后再发送到后台队列),否则您将陷入无休止的循环。
话虽如此,这里还有更深层次的问题。
-
如上所述,您正在缓存图像,但在检索图像时从不使用缓存。
-
您正在异步更新图像视图。
-
在启动新的异步获取之前,您应该初始化
UIImageView
的image
属性,否则,当单元被重用时,您将在那里看到旧映像,直到检索到新映像为止。 -
如果在调用
getImageFromUrl
和异步请求完成之间的中间时间段内重用了信元,该怎么办?您将更新错误单元格的图像视图。(当在慢速连接上执行此操作时,此问题将更加明显。使用网络链接调节器运行代码以模拟慢速连接,您将看到我所描述的问题。) -
如果用户快速向下滚动到表中的第100行,该怎么办?可见单元格的网络请求将积压在其他99个图像请求之后。您甚至可能在慢速连接时出现超时错误。
-
-
getImageFromUrl
中有很多战术性的小问题。-
为什么要从全局队列同步调度到另一个全局队列?这没必要。为什么要将UI更新同步发送到主线程?这是低效的。
-
为什么将
imageData
定义为块外的__block
;只需在块中定义它,就不需要__block
限定符。 -
如果您没有收到来自网络请求的有效
UIImage
(例如,您收到404错误消息),该怎么办;现有的代码将会崩溃。服务器可能提供的各种响应都不是有效的映像,您必须确定这种情况(即,确保您收到的NSData
不仅不是nil
,而且您从中创建的UIImage
也不是nil
)。
-
-
我可能会使用
NSCache
而不是NSMutableDictionary
作为缓存。此外,无论使用NSCache
还是NSMutableDictionary
,都需要确保对内存压力事件做出响应,并在需要时清空缓存。
我们可以解决所有这些单独的问题,但解决所有这些问题是一项不平凡的工作。因此,我建议您考虑SDWebImage或AFNetworking的UIImageView
类别。他们处理了这些问题中的大部分,还有其他问题。这会让你的生活轻松很多。