Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionViewCell programmatically memory leak

I have created a UICollectionView and UICollectionViewCell programmatically without using storyboards, but several things happening and I cannot understand why.

There is a memory leak in BKPhotoCell.

Cells are full screen, but when scrolling the CollectionView, they never reused. Init is called in every indexpath.row.

PrepareForReuse also never called.

Can anyone help me understand what I am doing wrong?

- (void)viewDidLoad
{
    [super viewDidLoad];

        CGSize screenSize = self.view.bounds.size;
        UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
        [flowLayout setItemSize:CGSizeMake(screenSize.width, screenSize.height)];
        [flowLayout setMinimumInteritemSpacing:0.0f];
        [flowLayout setMinimumLineSpacing:0.0f];
        [flowLayout setSectionInset:UIEdgeInsetsMake(0, 0, 0, 0)];

        [flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];

        self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0,0, screenSize.width, screenSize.height)  collectionViewLayout:flowLayout];
        [self.collectionView setDelegate:self];
        [self.collectionView setDataSource:self];
        [self.collectionView registerClass:[BKPhotoCell class] forCellWithReuseIdentifier:@"BKPhotoCell"];
        [self.collectionView setPagingEnabled:YES];
        [self.collectionView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
        [self.view addSubview:self.collectionView];

}

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

    BKPhotoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"BKPhotoCell" forIndexPath:indexPath];
    [cell.imageView setImage:[UIImage imageNamed:[_photos objectAtIndex:indexPath.row]]];
    return cell;
}

BKPhotoCell.h:

@interface BKPhotoCell : UICollectionViewCell
    @property (nonatomic, strong) UIImageView *imageView;
@end

BKPhotoCell.m:

@interface BKPhotoCell() <UIScrollViewDelegate>
@property (nonatomic ,strong) UIScrollView *scrollView;
@end

@implementation BKPhotoCell

#define isPortrait [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationPortrait || [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationPortraitUpsideDown

#pragma mark - ScrollView Delegate

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {

    return self.imageView;
}

- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{

    CGFloat offsetX = (scrollView.bounds.size.width > scrollView.contentSize.width)?
    (scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5 : 0.0;

    CGFloat offsetY = (scrollView.bounds.size.height > scrollView.contentSize.height)?
    (scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5 : 0.0;

    _imageView.center = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX,
                                 scrollView.contentSize.height * 0.5 + offsetY);
}

#pragma mark - View Lifecycle

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {

        self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.contentView.bounds.size.width, self.contentView.bounds.size.height)];
        [self.scrollView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
        [self.scrollView setContentMode:UIViewContentModeScaleAspectFit];
        [self.scrollView setDelegate:self];
        [self.contentView addSubview:_scrollView];

        self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.contentView.bounds.size.width, self.contentView.bounds.size.height)];
        [self.imageView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
        [self.imageView setContentMode:UIViewContentModeScaleAspectFit];

        [self.scrollView addSubview:self.imageView];

    }
    return self;
}


- (void)prepareForReuse {

    [super prepareForReuse];
    NSLog(@"Prepare for reuse");

}

- (void)layoutSubviews {

    [super layoutSubviews];

    [self.imageView  setFrame:CGRectMake(0, 0, _imageView.image.size.width, _imageView.image.size.height)];
    // NSLog(@"Setting Content Size: %f, %f", _imageView.image.size.width, _imageView.image.size.height);
    [_scrollView setContentSize:_imageView.image.size];

    CGFloat offsetX = (_scrollView.bounds.size.width > _scrollView.contentSize.width)?
    (_scrollView.bounds.size.width - _scrollView.contentSize.width) * 0.5 : 0.0;

    CGFloat offsetY = (_scrollView.bounds.size.height > _scrollView.contentSize.height)?
    (_scrollView.bounds.size.height - _scrollView.contentSize.height) * 0.5 : 0.0;

    _imageView.center = CGPointMake(_scrollView.contentSize.width * 0.5 + offsetX,
                                    _scrollView.contentSize.height * 0.5 + offsetY);

    float minimumZoomScale;

    if (isPortrait) {
        minimumZoomScale = _imageView.frame.size.width >= _imageView.frame.size.height ? _scrollView.frame.size.width / _imageView.frame.size.width : _scrollView.frame.size.height / _imageView.frame.size.height;
    } else {
        minimumZoomScale = _imageView.frame.size.width > _imageView.frame.size.height ? _scrollView.frame.size.width / _imageView.frame.size.width : _scrollView.frame.size.height / _imageView.frame.size.height;
    }

    [self.scrollView setMinimumZoomScale:minimumZoomScale];
    [self.scrollView setMaximumZoomScale:minimumZoomScale * 2];
    [self.scrollView setZoomScale:minimumZoomScale];


}
like image 832
displacedface Avatar asked Jul 13 '26 16:07

displacedface


1 Answers

If you are using iOS7 it could be you are running into a reuse bug!

I do have the same issue, it works fine on iOS6.1 and iOS6, but as soon as it's started on iOS7 (final) the cells never get reused!

I don't have a fix, I'm trying to create my own reuse logic.

Edit:

I got something working, so you need to save your attributes from the layout (maybe override FlowLayout if you are using it).

Then in

- (void)collectionView:(UICollectionView *)collectionView 
  didEndDisplayingCell:(UICollectionViewCell *)cell 
    forItemAtIndexPath:(NSIndexPath *)indexPath

save your cell to a NSMutableArray.

in

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

check your array for cells and apply the properties e.g.:

UICollectionViewCell *cell;

if (![_removedCells count]) {
    cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier 
                                                     forIndexPath:indexPath];
} else {
    cell = [_removedCells lastObject];
    [_removedCells removeLastObject];
    [cell prepareForReuse];        
    UICollectionViewLayoutAttributes* attr = [_layout getTempAttribute:indexPath];

    [cell setFrame: attr.frame];        
    [cell applyLayoutAttributes:attr];

    if (![cell superview]) {
        [_collectionView addSubview:cell];
    }
}

getTempAttribute just retrieves the previously calculated position for my UICollectionViewCell from the UICollectionViewLayout, so just a lookup by indexPath and returns the UICollectionViewLayoutAttributes, it looks as if it's working fine and reusing the UICollectionViewCells - but I can't guarantee anything ;-)

Edit2: After further testing I have to come to the conclusion that this breaks the rotation in the UICollectionView, leading to weird layout issues!

like image 171
Thomas Rosenstein Avatar answered Jul 15 '26 05:07

Thomas Rosenstein