生成新单元格时清除集合视图单元格(iOS7 - AFNetworking)

Posted

技术标签:

【中文标题】生成新单元格时清除集合视图单元格(iOS7 - AFNetworking)【英文标题】:Clear Collection View Cells when new ones are generated (iOS7 - AFNetworking) 【发布时间】:2014-02-03 16:40:23 【问题描述】:

我有一个集合视图控制器,其中包含从 JSON 调用获取其内容的单元格。每个单元格都有一个滚动视图(图像幻灯片)。我注意到幻灯片正在加载在旧幻灯片之上(我可以看到旧单元格中的幻灯片出现在新幻灯片加载之前)。或者,如果我滑动到第一个单元格的第三个图像,然后滚动第四个单元格,它会显示我的第三个图像(而不是第一个),但页面控件会显示这是第一张幻灯片。

如何在生成新单元格时“清除”旧单元格(或至少清除滚动视图,或阻止其重复使用)?

Article.h (NSObject)

@interface Article : NSObject

@property (readonly) NSURL *imageURL;
@property (nonatomic, strong) NSArray *images;

- (instancetype)initWithAttributes:(NSDictionary *)attributes;

+ (void)latestNewsWithBlock:(void (^)(NSArray *news, NSError *error))block;

@end

#pragma mark -

@interface ArticleImage : NSObject

@property (readonly, nonatomic, strong) NSURL *URL;

- (instancetype)initWithAttributes:(NSDictionary *)attributes;

@end

Article.m (NSObject)

@interface Article ()
@property (readwrite, nonatomic, strong) NSURL *URL;
@end

@implementation Article

- (instancetype)initWithAttributes:(NSDictionary *)attributes 
    self = [super init];
    if (!self) 
        return nil;
    

    self.URL = [NSURL URLWithString:attributes[@"url"]];

    NSMutableArray *mutableImages = [NSMutableArray array];

    for (NSDictionary *imageAttributes in attributes[@"photos"]) 
        NSDictionary *imageFileAttributes = [imageAttributes valueForKeyPath:@"photo_file.photo_file"];
        ArticleImage *image = [[ArticleImage alloc] initWithAttributes:imageFileAttributes];
        [mutableImages addObject:image];
    
    self.images = mutableImages;

    return self;


- (NSURL *)imageURL 
    return [[self.images firstObject] URL];


+ (void)latestNewsWithBlock:(void (^)(NSArray *news, NSError *error))block 
    [[DeadstockAPIManager sharedManager] GET:@"articles" parameters:nil success:^(AFHTTPRequestOperation *operation, id JSON) 
        NSMutableArray *mutableNews = [NSMutableArray array];
        for (NSDictionary *attributes in JSON[@"articles"]) 
            Article *news = [[Article alloc] initWithAttributes:attributes];
            [mutableNews addObject:news];

            [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
        

        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

        if (block) 
            block([NSArray arrayWithArray:mutableNews], nil);
        
     failure:^(AFHTTPRequestOperation *operation, NSError *error) 
        if (block) 
            block(nil, error);
        
    ];


@end

#pragma mark -

@interface ArticleImage ()
@property (readwrite, nonatomic, strong) NSURL *URL;
@property (readwrite, nonatomic, strong) NSURL *thumbnailURL;
@end

@implementation ArticleImage

- (instancetype)initWithAttributes:(NSDictionary *)attributes 
    self = [super init];
    if (!self) 
        return nil;
    

    self.URL = [NSURL URLWithString:[attributes valueForKeyPath:@"thumb.url"]];

    return self;

集合视图控制器

- (void)setLatestNews:(NSArray *)latestNews 
    _latestNews = latestNews;

    [self.collectionView reloadData];


- (void)loadData:(id)sender 
    [Article latestNewsWithBlock:^(NSArray *news, NSError *error) 
        self.latestNews = news;
    ];


#pragma mark - UIViewController

- (void)viewDidLoad 
    [super viewDidLoad];

    [self.collectionView registerClass:[ArticleCell class] forCellWithReuseIdentifier:@"ArticleCell"];

    [self loadData:nil];


#pragma mark - UICollectionViewDataSource

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath

    static NSString *identifier = @"articleCell";

    ArticleCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
    cell.article = [self.latestNews objectAtIndex:indexPath.row];

    return cell;

集合视图单元格

@interface ArticleCell ()
@property (readwrite, nonatomic, strong) NSArray *pageImages;
@end

@implementation ArticleCell

- (void)setArticle:(Article *)article 
    _article = article;

    self.pageImages = [self.article.images valueForKeyPath:@"URL"];

    NSUInteger pageCount = [self.pageImages count];
    self.pageControl.currentPage = 0;
    self.pageControl.numberOfPages = pageCount;
    self.pageControl.hidden = (pageCount == 1);

    for (NSInteger page = 0; page < pageCount; page++) 
        CGRect frame = self.scrollView.bounds;
        frame.origin.x = frame.size.width * page;
        frame.origin.y = 0.0f;

        UIImageView *imageView = [[UIImageView alloc] init];
        imageView.contentMode = UIViewContentModeScaleAspectFit;
        imageView.frame = frame;
        [imageView setImageWithURL:self.pageImages[page]];
        [self.scrollView addSubview:imageView];
    

    CGSize pagesScrollViewSize  = self.scrollView.frame.size;

    self.scrollView.contentSize = CGSizeMake(pagesScrollViewSize.width * self.pageImages.count, pagesScrollViewSize.height);

谢谢。

【问题讨论】:

顺便说一句,您通常不希望创建像 article 属性那样具有副作用的属性。相反,删除setArticle 并创建一个您调用的-(void)configureForArticle:(Article *)article 方法,然后该方法可以执行所有必要的代码并设置article 属性。 【参考方案1】:

示例项目可以在这里找到:http://www.filedropper.com/collectionviewtest

在您的 ArticleCell 中将此作为公共方法实现:

- (void)cleanForReuse

   [[self.scrollView subviews]
               makeObjectsPerformSelector:@selector(removeFromSuperview)];
   self.scrollView.contentSize = CGSizeZero;


- (void)prepareForReuse

   [super prepareForReuse];
   self.pageImages = nil;

然后在重用单元格之前更新您的代码以调用 cleanForReuse:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView 
                  cellForItemAtIndexPath:(NSIndexPath *)indexPath

    static NSString *identifier = @"articleCell";

    ArticleCell *cell = 
           [collectionView dequeueReusableCellWithReuseIdentifier:identifier 
                                                     forIndexPath:indexPath];

    [cell cleanForReuse];
    cell.article = [self.latestNews objectAtIndex:indexPath.row];

    return cell;

【讨论】:

如果我在生成新的滚动视图后向上滚动,此代码将删除我的滚动视图。 @Chris 你还有其他事情要做。此代码在我的测试项目中运行。 我用我拥有的 NSObject(“Article”)更新了我的代码,它解析我的 JSON 字符串以防万一。 删除和重新创建子视图破坏了单元重用的大部分要点。 @AaronBrager 你错了。重用的重点是最大限度地减少内存流失,如果您必须不断地一遍又一遍地重新初始化一个单元格对象,并提高滚动性能,否则会因为为每一行创建一个新的单元格对象的延迟而发生。重用与单元格的内容无关。事实上,如果你从不移除 scrollView 的子视图,那么每一行的子视图数量就会增加一倍。【参考方案2】:

您看到这一点是因为单元正在被重复使用(出于性能原因,这是一件好事)。

如果您有旧内容需要在重用时从单元格中删除,您需要在 collectionView:cellForItemAtIndexPath: 中将其删除。

在您的情况下,您需要在添加新的 imageView 之前对添加到 scrollView 的所有 imageView 调用 removeFromSuperview。

你也可以在 UICollectionViewCell 方法 prepareForReuse 中做一些复用准备。但是,出于 UITableViewCells 中的性能原因,Apple 建议“您应该只重置与内容无关的单元格属性,例如 alpha、编辑和选择状态。”。据推测,同样的建议也适用于 UICollectionViewCells。

【讨论】:

对添加到 scrollView 的每个 imageView 调用 removeFromSuperview。您可能希望有一个数组属性来存储它们以使它们更易于访问。 不要以这种方式使用prepareForReuse。出于性能原因,您应该只重置与内容无关的单元格属性。例如,alpha、编辑和选择状态。 有趣,我以前没听说过这个。我真的不明白在 prepareForReuse 中做某事比在 collectionView:cellForItemAtIndexPath: 中做某事要耗费更多的资源。无论哪种方式,您都需要在重新使用之前重置单元格的状态。你碰巧知道关于这个主题的任何好的文章吗?我在搜索中找不到任何相关内容。 Apple 在 UITableViewCell: developer.apple.com/library/ios/documentation/UIKit/Reference/… 的文档中指出了这一点。应该注意的是,他们没有对UICollectionReusableView 发表相同的评论,但他们确实指出“子类可以覆盖此方法以将任何新的 data 分配给视图”,这似乎表明类似的方法(没有 UI 更改)。 删除和重新创建子视图破坏了单元重用的大部分要点。例如,我将 UIImageView 的 image 设置为 nil,但在下次单元格出现时重用 UIImageView。

以上是关于生成新单元格时清除集合视图单元格(iOS7 - AFNetworking)的主要内容,如果未能解决你的问题,请参考以下文章

集合视图中单元格的宽度不正确

滚动和拖动单元格时集合视图抛出异常

使用两种不同类型的单元格时如何确定集合视图的大小

当作为子视图添加到集合视图单元格时,滚动视图中的内容不可见

在我的视图集合中,我添加了一个搜索栏,它过滤了单元格,但是当我单击单元格时,它自己的路径没有改变

如何在集合视图中单击单元格时创建按钮操作