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 时,它不会调整图像视图的大小。

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

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

    如果这样做,您可以为自定义 UIImageView 出口异步更新 image 属性,并且由于布局不取决于图像的存在,任何image 的异步更新应该正确显示。

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

    如果您这样做,假设您从缓存中正确检索图像并且在 cellForRowAtIndexPath 完成之前这样做,单元格将被正确布局。

    注意,如果你这样做,你将需要修复你的 getImageFromUrl 以实际尝试首先从缓存中检索图像(并从主队列中执行此操作,然后再分派到后台队列), 否则你将陷入死循环。


话说回来,这里还有更深层次的问题。

  1. 正如我上面提到的,您正在缓存图像,但在检索图像时从不使用缓存。

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

    • 您应该在启动新的异步提取之前初始化 UIImageViewimage 属性,否则当重复使用单元格时,您会看到旧图像在那里,直到检索到新图像。

    • 如果在调用 getImageFromUrl 和异步请求完成之间的间隔期间重用该单元会怎样?您将为错误的单元格更新图像视图。 (当通过慢速连接执行此操作时,此问题会更加明显。运行 您的代码使用网络 link 调节器来模拟慢速连接,您会看到我正在描述的问题。)

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

  3. getImageFromUrl中有一堆战术上的小问题。

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

    • 为什么要在块外定义imageData__block;只需在块中定义它,您不需要 __block 限定符。

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

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

我们可以解决所有这些单独的问题,但解决所有这些问题的工作量并不小。因此,我建议您考虑 SDWebImage or AFNetworkingUIImageView 类别。他们负责处理这些问题中的大部分,以及其他问题。它会让你的生活变得更加轻松。