Powered By Blogger

Monday, March 10, 2014

UICollectionView with dynamic image height

Hi,
Dear readers,

I had successfully used UICollectionView with dynamic heights of images which fetches data from NSURL string. iOS 6.0 and later versions can use "UICollectionView" to make a model like "Water Flow" or "Pinterest Layout".

Please see below steps to have it in your application.


=======================================================================

*** STEP 1 : 
A. Include these two files into your project source directory.
     1.) " CHTCollectionViewWaterfallLayout.h"
     2.) " CHTCollectionViewWaterfallLayout.m"
B. You can download these files from CHTCollectionViewWaterfallLayout .
C. Import "ImageIO.framework" into project.

=======================================================================

*** STEP 2:  

A) Generate two class files “CategoryCell.h” and “CategoryCell.m” for UICollectionViewCell.


#import "FXImageView.h"
@interface CategoryCell : UICollectionViewCell

@property (nonatomic, strong) UIButton *btnPhoto;
@property (nonatomic, strong) FXImageView *photoView;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIView *bottomView;


#import "CategoryCell.h"

const CGFloat kTMPhotoQuiltViewMargin = 5;

@implementation CategoryCell

@synthesize photoView = _photoView;
@synthesize titleLabel = _titleLabel;
@synthesize btnPhoto = _btnPhoto;
@synthesize bottomView = _bottomView;

#pragma mark - Accessors

- (UILabel *)titleLabel {
if (!_titleLabel) {
_titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, self.contentView.bounds.size.width, 30.0)];
_titleLabel.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
_titleLabel.backgroundColor = [UIColor clearColor];
_titleLabel.textColor = WHITE_COLOR;
        if (IS_IPAD) {
            _titleLabel.font = FONT_BOLD(14.0);
        }
        else
        {
            _titleLabel.font = FONT_BOLD(12.0);
        }
_titleLabel.textAlignment = NSTextAlignmentCenter;
}
return _titleLabel;
}

- (UIView *)bottomView {
if (!_bottomView) {
_bottomView = [[UIView alloc] initWithFrame:CGRectMake(0.0, self.contentView.bounds.size.height - 30.0, self.contentView.bounds.size.width, 30.0)];
_bottomView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
_bottomView.backgroundColor = BLACK_COLOR;
        _bottomView.alpha = 0.6;
}
return _bottomView;
}

- (FXImageView *)photoView {
if (!_photoView) {
_photoView = [[FXImageView alloc] initWithFrame:self.contentView.bounds];
        _photoView.asynchronous = YES;
        _photoView.contentMode = UIViewContentModeScaleAspectFit;
_photoView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
_photoView.backgroundColor = CLEAR_COLOR;
        _photoView.layer.shadowOpacity = 2.0;
        _photoView.layer.shadowColor = LIGHT_GRAY_COLOR.CGColor;
        _photoView.layer.shadowRadius = 2.0;
        _photoView.layer.shadowOffset = CGSizeMake(-0.5, 0.5);
}
return _photoView;
}

- (UIButton *)btnPhoto{
if (!_btnPhoto) {
_btnPhoto = [UIButton buttonWithType:UIButtonTypeCustom];
        _btnPhoto.frame = self.contentView.bounds;
        _btnPhoto.contentMode = UIViewContentModeScaleAspectFill;
_btnPhoto.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
_btnPhoto.backgroundColor = CLEAR_COLOR;
        _btnPhoto.layer.shadowOpacity = 5.0;
        _btnPhoto.layer.shadowColor = LIGHT_GRAY_COLOR.CGColor;
        _btnPhoto.layer.shadowRadius = 5.0;
        _btnPhoto.layer.shadowOffset = CGSizeMake(-1.0, 1.0);
}
return _btnPhoto;
}

#pragma mark - Life Cycle

#if !__has_feature(objc_arc)
- (void)dealloc {
[_photoView removeFromSuperview];
_photoView = nil;
    
    [_titleLabel removeFromSuperview];
_titleLabel = nil;
    
    [super dealloc];
}
#endif

- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self.contentView addSubview:self.photoView];
        [self.contentView addSubview:self.btnPhoto];
        [self.bottomView addSubview:self.titleLabel];
        [self.contentView addSubview:self.bottomView];
}
return self;
}

@end

========================================================================

*** STEP 3:  

#import <ImageIO/ImageIO.h>

#import "CHTCollectionViewWaterfallLayout.h"
#define CELL_WIDTH 150.0
#define CELL_IDENTIFIER @"GCCategoryCell"
#import "CategoryCell.h"

@interface CategoryViewController () <UICollectionViewDataSource, CHTCollectionViewDelegateWaterfallLayout>
{
    NSMutableArray *cellHeights;
    NSMutableArray *arrCategory;
    UICollectionView *collectionView;
    CGFloat cellWidth;
}
@property (nonatomic, strong) NSMutableArray *cellHeights;
@property (nonatomic, strong) NSMutableArray * arrCategory;
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic) CGFloat cellWidth;

@implementation CategoryViewController
@synthesize cellHeights;
@synthesize arrCategory;
@synthesize collectionView;

@synthesize cellWidth;

- (void)viewDidLoad
{
       self.arrCategory = [[NSMutableArray alloc] init];

        self.cellWidth = CELL_WIDTH;    // Default if not setting runtime attribute
        CHTCollectionViewWaterfallLayout *layout = [[CHTCollectionViewWaterfallLayout alloc] init];
        layout.sectionInset = UIEdgeInsetsMake(0.0, 10, 10, 5.0);
        layout.headerHeight = 0;
        layout.footerHeight = 0;
        if (IS_IPAD) {
            layout.minimumColumnSpacing = 30;
            layout.minimumInteritemSpacing = 30;
        }
        else
        {
            layout.minimumColumnSpacing = 5;
            layout.minimumInteritemSpacing = 5;
        }
        CGRect frame;
        if (IS_IPAD) {
            frame = CGRectMake(0, lblTitle.frame.size.height, self.view.frame.size.width, self.view.frame.size.height-36.0);
        }
        else
        {
            frame = CGRectMake(0, lblTitle.frame.size.height, self.view.frame.size.width, self.view.frame.size.height-(Appdel.objTab.frame.size.height + 36.0));
        }
        self.collectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout];
        self.collectionView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
        self.collectionView.dataSource = self;
        self.collectionView.delegate = self;
        self.collectionView.backgroundColor = [UIColor clearColor];
        [self.collectionView registerClass:[GCCategoryCell class] forCellWithReuseIdentifier:CELL_IDENTIFIER];

        [self.view addSubview:self.collectionView];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self updateLayoutForOrientation:[UIApplication sharedApplication].statusBarOrientation];
}

#pragma mark --------------------------
#pragma mark Accessors
#pragma mark --------------------------

- (NSMutableArray *)cellHeights
{
    if (!cellHeights)
    {
        cellHeights = [[NSMutableArray alloc] init];
    }
    return cellHeights;
}

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
                                         duration:(NSTimeInterval)duration {
    [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation
                                            duration:duration];
    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"6.0")) {
        [self updateLayoutForOrientation:toInterfaceOrientation];
    }
}

- (void)updateLayoutForOrientation:(UIInterfaceOrientation)orientation
{
    CHTCollectionViewWaterfallLayout *layout = (CHTCollectionViewWaterfallLayout *)self.collectionView.collectionViewLayout;
    layout.columnCount = UIInterfaceOrientationIsPortrait(orientation) ? 2 : 3;
}

========================================================================

*** STEP 4:

// Call this method first to fill height array after you objects list
-(void)fillHeightArray
{
                for (NSMutableDictionary *dictGCCatInfo in self.arrCategory)
                {
                    NSNumber *width = [NSNumber numberWithFloat:200];
                    NSNumber *height = [NSNumber numberWithFloat:200];
                    
                    NSString *urlString = [dictGCCatInfo valueForKey:@"Photo"];
                    NSURL *imageFileURL = [NSURL URLWithString:urlString];
                    CGImageSourceRef imageSource = CGImageSourceCreateWithURL((__bridge CFURLRef)imageFileURL, NULL);
                    if (imageSource) {
                        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:NO], (NSString *)kCGImageSourceShouldCache, nil];
                        CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
                        if (imageProperties) {
                            width = (NSNumber *)CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth);
                            height = (NSNumber *)CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight);
                            CFRelease(imageProperties);
                        }
                    }
                    CFRelease(imageSource); // Added
                    CGSize size = CGSizeMake([width floatValue], [height floatValue]);
                    [self.cellHeights addObject:[NSValue valueWithCGSize:size]];
                }
[self.collectionView reloadData];
}

#pragma mark -----------------------------
#pragma mark UICollectionViewDataSource
#pragma mark -----------------------------

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return [arrCategory count];
}

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 1;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView1 cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    CategoryCell *cell = (CategoryCell *)[collectionView1 dequeueReusableCellWithReuseIdentifier:CELL_IDENTIFIER
                                                                                       forIndexPath:indexPath];
    NSString *urlString = [[arrCategory objectAtIndex:indexPath.row] valueForKey:@"Photo"];
    if (urlString)
    {
        [cell.photoView setProcessedImage:nil];
        //set image
        [cell.photoView setImageWithContentsOfURL:[NSURL URLWithString:urlString]];
    }
    else
    {
        [cell.photoView setImage:[UIImage imageNamed:@"no-image.png"]];
    }
    [cell.photoView setTag:indexPath.row];
    [cell.btnPhoto setTag:indexPath.row];
    cell.titleLabel.text = [[arrCategory objectAtIndex:indexPath.row] valueForKey:@"GiftCertificateCatName"];
    [cell.btnPhoto addTarget:self action:@selector(btnCategoryClicked:) forControlEvents:UIControlEventTouchUpInside];
    return cell;
}

#pragma mark ------------------------------------
#pragma mark CHTCollectionViewDelegateWaterfallLayout
#pragma mark ------------------------------------

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    CGSize size = [[self.cellHeights objectAtIndex:indexPath.row] CGSizeValue];
    return size;
}

Hope, you will fully enjoy this post about UICollectionView.

Best Regards,
Nilesh M. Prajapati

8 comments:

  1. - (NSMutableArray *)cellHeights
    {
    if (!cellHeights)
    {
    cellHeights = [[NSMutableArray alloc] init];
    }
    return cellHeights;
    }

    crashing after this... cellHeights is nil ... outbound index error

    ReplyDelete
    Replies
    1. Hi,
      Sorry for delay in reply as was busy with my current project. There's no longer need of this function. So, you can initialise once at the time of loading view.

      Delete
  2. #import "FXImageView.h" which file is this

    ReplyDelete
    Replies
    1. Hi,
      Sorry for that "FXImageView.h" is asynchronous image downloading & auto caching framework which was used by me. You can removed it from your code.

      Delete
  3. i use this example for Pinterest like layout. i have 10 objects with image size of 10*150 and 600*600. Five objects are 150 and five object with 600. but it take 150 for all the 10 objects. please help me for that.

    ReplyDelete
  4. i have 10 objects with image url and used this tutorial for Pinterest like layout. five images in that objects are size 150*150 and others five have 600*600. but my all cell are display with 150 size. please help me for that.

    ReplyDelete
    Replies
    1. As, I had mentioned in my code that you had to fetch heights of your object in "cellHeight" array first and then utilize that array for height as well. So, now you have to use this array for width & height of an object and you have to write your own formula for object's width & height in mobile device using actual height and width of an object by considering the column count as well.

      Delete
    2. I have mentioned about the formula which you can use with non-customized collection view. For above example you just need to use the size parameter directly from the array in below method :
      - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath

      Delete