表视图不显示 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;
}

根本问题是标准表视图单元格的标准imageView属性将根据cellForRowAtIndexPath完成时出现的图像自动调整自身大小。但是,由于第一次展示表格时还没有图像,因此单元格的布局就好像没有图像一样。当异步更新图像视图的image时,它不会调整图像视图的大小。

有几种方法可以解决这个问题:

  1. 不要使用UITableViewCell提供的默认imageView,而是使用IBOutlet到自己的UIImageView属性来定义自己的自定义单元格子类。请确保此UIImageView具有固定布局(即,它不使用从底层图像派生的固有大小)。

    如果执行此操作,则可以异步更新自定义UIImageView插座的image属性,并且由于布局不取决于图像的存在,因此该image的任何异步更新都应正确显示。

  2. 收到图像时,不要只设置图像视图的image属性,而是使用reloadRowsAtIndexPaths重新加载与该NSIndexPath关联的整行。

    如果执行此操作,则假定从缓存中正确检索图像,并且在cellForRowAtIndexPath完成之前进行检索,则单元将正确布局。

    请注意,如果您这样做,您将需要修复getImageFromUrl,以便首先尝试从缓存中检索图像(然后从主队列中检索,然后再发送到后台队列),否则您将陷入无休止的循环。


话虽如此,这里还有更深层次的问题。

  1. 如上所述,您正在缓存图像,但在检索图像时从不使用缓存。

  2. 您正在异步更新图像视图。

    • 在启动新的异步获取之前,您应该初始化UIImageViewimage属性,否则,当单元被重用时,您将在那里看到旧映像,直到检索到新映像为止。

    • 如果在调用getImageFromUrl和异步请求完成之间的中间时间段内重用了信元,该怎么办?您将更新错误单元格的图像视图。(当在慢速连接上执行此操作时,此问题将更加明显。使用网络链接调节器运行代码以模拟慢速连接,您将看到我所描述的问题。)

    • 如果用户快速向下滚动到表中的第100行,该怎么办?可见单元格的网络请求将积压在其他99个图像请求之后。您甚至可能在慢速连接时出现超时错误。

  3. getImageFromUrl中有很多战术性的小问题。

    • 为什么要从全局队列同步调度到另一个全局队列?这没必要。为什么要将UI更新同步发送到主线程?这是低效的。

    • 为什么将imageData定义为块外的__block;只需在块中定义它,就不需要__block限定符。

    • 如果您没有收到来自网络请求的有效UIImage(例如,您收到404错误消息),该怎么办;现有的代码将会崩溃。服务器可能提供的各种响应都不是有效的映像,您必须确定这种情况(即,确保您收到的NSData不仅不是nil,而且您从中创建的UIImage也不是nil)。

  4. 我可能会使用NSCache而不是NSMutableDictionary作为缓存。此外,无论使用NSCache还是NSMutableDictionary,都需要确保对内存压力事件做出响应,并在需要时清空缓存。

我们可以解决所有这些单独的问题,但解决所有这些问题是一项不平凡的工作。因此,我建议您考虑SDWebImage或AFNetworking的UIImageView类别。他们处理了这些问题中的大部分,还有其他问题。这会让你的生活轻松很多。

最新更新