我已经写了两种方法在UITableView单元中异步加载图片.在这两种情况下,图像都会很好地加载,但当我滚动表格时,图像会改变几次,直到滚动结束,图像会返回到正确的图像.我不知道为什么会这样.

#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

- (void)viewDidLoad
{
    [super viewDidLoad];
    dispatch_async(kBgQueue, ^{
        NSData* data = [NSData dataWithContentsOfURL: [NSURL URLWithString:
                                                       @"http://myurl.com/getMovies.php"]];
        [self performSelectorOnMainThread:@selector(fetchedData:)
                               withObject:data waitUntilDone:YES];
    });
}

-(void)fetchedData:(NSData *)data
{
    NSError* error;
    myJson = [NSJSONSerialization
              JSONObjectWithData:data
              options:kNilOptions
              error:&error];
    [_myTableView reloadData];
}    

- (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.
    // Usually the number of items in your array (the one that holds your list)
    NSLog(@"myJson count: %d",[myJson count]);
    return [myJson count];
}
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

        myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
        if (cell == nil) {
            cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
        }

        dispatch_async(kBgQueue, ^{
        NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]]];

            dispatch_async(dispatch_get_main_queue(), ^{
        cell.poster.image = [UIImage imageWithData:imgData];
            });
        });
         return cell;
}

... ...

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

            myCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
            if (cell == nil) {
                cell = [[myCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
            }
    NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg",[[myJson objectAtIndex:indexPath.row] objectForKey:@"movieId"]]];
    NSURLRequest* request = [NSURLRequest requestWithURL:url];


    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse * response,
                                               NSData * data,
                                               NSError * error) {
                               if (!error){
                                   cell.poster.image = [UIImage imageWithData:data];
                                   // do whatever you want with image
                               }

                           }];
     return cell;
}

推荐答案

假设您正在寻找一种快速的战术解决方案,您需要做的是确保细胞图像已初始化,并且该细胞所在的行仍然可见,例如:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];

    cell.poster.image = nil; // or cell.poster.image = [UIImage imageNamed:@"placeholder.png"];

    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg", self.myJson[indexPath.row][@"movieId"]]];

    NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (data) {
            UIImage *image = [UIImage imageWithData:data];
            if (image) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    MyCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
                    if (updateCell)
                        updateCell.poster.image = image;
                });
            }
        }
    }];
    [task resume];

    return cell;
}

上述代码解决了由于单元被重用而产生的一些问题:

  1. 在发起后台请求之前,您没有初始化单元图像(这意味着在下载新图像时,出列的单元的最后一个图像仍然可见).确保所有图像视图的image属性都为nil,否则会看到图像闪烁.

  2. 一个更微妙的问题是,在非常慢的网络上,在单元滚动出屏幕之前,您的异步请求可能还没有完成.您可以使用UITableView方法cellForRowAtIndexPath:(不要与名称相似的UITableViewDataSource方法tableView:cellForRowAtIndexPath:混淆)来查看该行的单元格是否仍然可见.如果单元格不可见,此方法将返回nil.

    问题是,当异步方法完成时,单元格已被滚动掉,更糟糕的是,单元格已被重新用于表的另一行.通过判断该行是否仍然可见,可以确保不会意外地使用已从屏幕上滚动的行的图像更新图像.

  3. 在某种程度上与手头的问题无关,我仍然觉得有必要对其进行更新,以利用现代约定和API,特别是:

    • 使用NSURLSession而不是将-[NSData contentsOfURL:]调度到后台队列;

    • 使用dequeueReusableCellWithIdentifier:forIndexPath:而不是dequeueReusableCellWithIdentifier:(但请确保使用单元原型或注册类或NIB作为该标识符);以及

    • 我使用的类名符合Cocoa naming conventions(即以大写字母开头).

即使有了这些更正,也存在一些问题:

  1. 上面的代码没有缓存下载的图像.这意味着,如果你将一张图片滚动出屏幕并重新显示在屏幕上,这款应用程序可能会再次try 检索该图片.也许您会足够幸运,您的服务器响应头将允许NSURLSessionNSURLCache提供的相当透明的缓存,但如果不允许,您将发出不必要的服务器请求并提供一个慢得多的UX.

  2. 我们不会取消滚动到屏幕之外的单元格的请求.因此,如果您快速滚动到第100行,该行的图像可能会积压在前99行的请求后面,这些请求甚至不再可见.您总是希望确保对可见单元格的请求进行优先级排序,以获得最佳的用户体验.

解决这些问题的最简单的修复方法是使用UIImageView类别,例如随SDWebImageAFNetworking提供的类别.如果您愿意,您可以编写自己的代码来处理上述问题,但这需要大量的工作,上面的UIImageView个类别已经为您完成了这项工作.

Ios相关问答推荐

Swift addtarget方法的默认参数不生效

在Swift5中,将某些属性值从一个类复制到另一个类实例

快速相机放大手势不能正常工作

SWIFT-仅解码感兴趣的元素

clang:错误:SDK 路径中不包含libarclite

Flutter iOS 键盘问题:. TextInputType.number 中缺少字符

保存和读取登录到 keys 串不工作 IOS swift

多协议和类继承混乱,Swift

从iOS键盘输入时Flutter TextField输入消失

如何让替换视图淡入旧视图而不是交叉淡入淡出?

Text() 正在添加额外的前导尾随填充 SwiftUI

用溢出的长文本对齐 Flutter 中的行和列

我可以在 Apple-Watch 上使用 iPhone 的摄像头扫描 SwiftUi 中的二维码(例如用于登录)吗

如何将单个按钮传递到自定义 ConfirmationDialog

Swift 图表:.chartYScale 似乎只适用于 100 的增量?

将视图(例如 Text())不透明度设置为 0 或将背景设置为 Color.clear 会导致它不被绘制

正确的领域使用模式/最佳实践?

Swift 中的日期到毫秒和回溯到日期

有没有办法在应用store 之外分发 ios 应用程序?

如何在 iOS 中获取正在运行的应用程序的名称