【问题标题】:How to Asynchronously load UITableViewcell images so that scrolling doesn't lag如何异步加载 UITableViewcell 图像以便滚动不会滞后
【发布时间】:2014-11-13 23:50:17
【问题描述】:

我已尝试为此目的使用ASyncImageView,但对于如何针对我的具体情况实施它感到有些困惑。我目前有一个MatchCenterViewController,其中包含一个表格。它同步加载单元格的图像,这在滚动表格时会导致很多延迟。如何修改加载远程图像的方式,使其异步完成?我的代码如下:

#import "MatchCenterViewController.h"
#import <UIKit/UIKit.h>
#import "MatchCenterCell.h"

@interface MatchCenterViewController () <UITableViewDataSource, UITableViewDelegate>

@property (nonatomic, strong) UITableView *matchCenter;
@property (nonatomic, assign) BOOL matchCenterDone;
@property (nonatomic, assign) BOOL hasPressedShowMoreButton;

@end



@implementation MatchCenterViewController


- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
    }
    return self;
}


- (void)viewDidLoad
{
    [super viewDidLoad];

    _matchCenterDone = NO;
    _hasPressedShowMoreButton = NO;

    // Set up MatchCenter table
    self.matchCenter = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewCellStyleSubtitle];
    self.matchCenter.frame = CGRectMake(0,70,320,self.view.frame.size.height-100);
    self.edgesForExtendedLayout = UIRectEdgeAll;
    self.matchCenter.contentInset = UIEdgeInsetsMake(0.0f, 0.0f, CGRectGetHeight(self.tabBarController.tabBar.frame), 0.0f);
    _matchCenter.dataSource = self;
    _matchCenter.delegate = self;
    [self.view addSubview:self.matchCenter];

    self.expandedSection = -1;

    _matchCenterArray = [[NSArray alloc] init];

    // Refresh button
    UIImageView *refreshImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"refresh.png"]];
    refreshImageView.frame = CGRectMake(280, 30, 30, 30);
    refreshImageView.userInteractionEnabled = YES;
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(refreshPressed:)];
    [refreshImageView addGestureRecognizer:tapGesture];
    [self.view addSubview:refreshImageView];


    // Preparing for MC and indicating loading
    self.matchCenterArray = [[NSArray alloc] init];

    UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    activityIndicator.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);
    [self.view addSubview: activityIndicator];

    [activityIndicator startAnimating];

    _matchCenterDone = NO;

    // Disable ability to scroll until table is MatchCenter table is done loading
    self.matchCenter.scrollEnabled = NO;

    [PFCloud callFunctionInBackground:@"MatchCenter3"
                       withParameters:@{}
                                block:^(NSArray *result, NSError *error) {

                                    if (!error) {
                                        _matchCenterArray = result;

                                        [activityIndicator stopAnimating];

                                        [_matchCenter reloadData];

                                        _matchCenterDone = YES;
                                        self.matchCenter.scrollEnabled = YES;
                                        NSLog(@"Result: '%@'", result);
                                    }
                                }];

}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return _matchCenterArray.count;
}

//the part where i setup sections and the deleting of said sections

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    return 21.0f;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
    return 40;
}

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
//code snipped out for conciseness
}

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
//Header code snipped out for conciseness
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSDictionary *currentSectionDictionary = _matchCenterArray[section];
    NSArray *top3ArrayForSection = currentSectionDictionary[@"Top 3"];

    return (top3ArrayForSection.count-1 < 1) ? 1 : top3ArrayForSection.count-1;
}

// Cell layout
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Initialize cell
    static NSString *CellIdentifier = @"MatchCenterCell";
    MatchCenterCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (!cell) {
        // if no cell could be dequeued create a new one
        cell = [[MatchCenterCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }

    //[cell.contentView addSubview:cell.priceLabel];
    [cell.contentView addSubview:cell.conditionLabel];

    // No cell seperators = clean design
    tableView.separatorColor = [UIColor clearColor];

    NSDictionary *currentSectionDictionary = _matchCenterArray[indexPath.section];
    NSArray *top3ArrayForSection = currentSectionDictionary[@"Top 3"];

    if (top3ArrayForSection.count-1 < 1) {

        // title of the item
        cell.textLabel.text = @"No items found, but we'll keep a lookout for you!";
        cell.textLabel.font = [UIFont systemFontOfSize:12];

    }

    else {

        // title of the item
        cell.textLabel.text = _matchCenterArray[indexPath.section][@"Top 3"][indexPath.row+1][@"Title"];
        cell.textLabel.font = [UIFont systemFontOfSize:14];

        // price + condition of the item
        NSString *price = [NSString stringWithFormat:@"$%@", _matchCenterArray[indexPath.section][@"Top 3"][indexPath.row+1][@"Price"]];
        NSString *condition = [NSString stringWithFormat:@"%@", _matchCenterArray[indexPath.section][@"Top 3"][indexPath.row+1][@"Item Condition"]];

        cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ - %@", price, condition];
        cell.detailTextLabel.textColor = [UIColor colorWithRed:0/255.0f green:127/255.0f blue:31/255.0f alpha:1.0f];

        // image of the item
        NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:_matchCenterArray[indexPath.section][@"Top 3"][indexPath.row+1][@"Image URL"]]];
        [[cell imageView] setImage:[UIImage imageWithData:imageData]];

        cell.imageView.layer.masksToBounds = YES;
        cell.imageView.layer.cornerRadius = 2.5;

    }

    return cell;
}


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section == self.expandedSection || indexPath.row <= 3) {
        return 65;
    }
    return 0;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (_matchCenterDone == YES) {
        self.itemURL = _matchCenterArray[indexPath.section][@"Top 3"][indexPath.row+1][@"Item URL"];
        [self performSegueWithIdentifier:@"WebViewSegue" sender:self];
    }
} 

@end

@implementation MoreButton
@end

【问题讨论】:

    标签: ios objective-c uitableview asynchronous


    【解决方案1】:
    // Use background thread to avoid the laggy tableView
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        // Download or get images here
        NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"url"]];
        UIImage *cellImage = [[UIImage alloc] initWithData:imageData];
    
        // Use main thread to update the view. View changes are always handled through main thread
        dispatch_async(dispatch_get_main_queue(), ^{
            // Refresh image view here
            [cell.imageView setImage:cellImage];
            [cell.imageView.layer setMasksToBounds:YES];
            [cell.imageView.layer setCornerRadius:2.5f];
            [cell setNeedsLayout];
        });
    });
    

    【讨论】:

    • 将其放入 cellForRowAtIndexPath 会导致应用程序崩溃并出现以下错误:libsystem_kernel.dylib__pthread_kill: 0x10e687278: movl $0x2000148, %eax 0x10e68727d: movq %rcx, %r10 0x10e687280: syscall 0x10e687282: jae 0x10e68728c ; __pthread_kill + 20 0x10e687284: movq %rax, %rdi 0x10e687287: jmp 0x10e682ca3 ; cerror_nocancel 0x10e68728c: retq 0x10e68728d: nop 0x10e68728e: nop 0x10e68728f: nop 我错过了什么吗?
    • 好吧,如果你把它放在正确的位置,它应该可以工作。请参阅答案编辑并再次用上面的代码替换您的部分代码。
    • 我的错误确实放错了位置。
    • 如果您要使用此解决方案(我不推荐,您应该使用下面的答案),然后至少在返回主队列之前创建 UIImage 对象。这应该会给你的用户界面带来轻微的改进。
    • :) 我不推荐它,因为它很容易由于多种原因而损坏。我已经多次处理过这种情况,这个答案从根本上是错误的。如果用户快速滚动,图像加载返回的顺序是不可预测的,并且单元格将使用错误的图像,因为没有一个加载调用被取消。
    【解决方案2】:

    对此最常见的解决方案是AFNetworkingAFImageView。它完美地处理了这种情况。它应该不会花费您任何时间来实施,所以请尝试一下。

    【讨论】:

      【解决方案3】:

      Guy Kogus 的回答效果很好。他是对的,我遇到了他在上面评论中提到的各种问题,像第一个答案一样做类似的事情。

      不过,这里有一个关于如何使用 AFNetworking 的 UIImageView 类别的示例。假设下面的代码在 Cell 中(或从 UIView 继承的东西)。

      首先导入类:

      #import "UIImageView+AFNetworking.h"
      

      然后在你的 UITableViewCell 中添加这段代码:

      NSString *url = @"http://www.domain.www/some_image.jpg";
      
      [self.productImage setImageWithURL:[NSURL URLWithString:url]
                        placeholderImage:[UIImage imageNamed:@"placeholderImg.png"]];
      
      [self setNeedsLayout];
      

      不是 100% 确定在这种情况下是否需要 setNeedsLayout。请随时更正此问题。

      【讨论】:

      • 哦,在处理了另一个地狱般的选项后,我找到了你的答案。我希望我以前能看到它。
      猜你喜欢
      • 2016-06-27
      • 1970-01-01
      • 1970-01-01
      • 2019-03-31
      • 2017-07-10
      • 2019-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多