It seems that there's currently no easy way to:
- Fetch UICollectionViewCell prototype cell sizes runtime from Storyboard(s).
- Manage sizes of prototype cells just in one place (rather than having to enter them in the Storyboard cell prototype and implement
sizeForItemAtIndexPath
).
A method proposed here (for UITableViews) does not work, because using dequeueReusableCellWithReuseIdentifier
in sizeForItemAtIndexPath
will cause an indefinite loop.
However, I've managed to do this the following way:
Add a unique (across all UICollectionViewCell
s in every storyboard) reuse identifier into each of your UICollectionView
prototype cells in all Storyboards.
Add a Run script
Build Phase to your project with the script that pulls out UICollectionViewCell
frame sizes from all Storyboards.
output=${PROJECT_DIR}/StoryboardPrototypeCellSizes.h
printf "@{" > $output
for storyboard in $(find ${PROJECT_DIR} -name "*.storyboard")
do
echo "Scanning storyboard $storyboard..."
delimiter=
for line in $(xpath $storyboard "//collectionViewCell/@reuseIdentifier[string-length()>0] | //collectionViewCell/rect" 2>&-)
do
case $line in
reuseIdentifier*)
reuseIdentifier=$(sed 's/[^"]*"\([^"]*\)".*/\1/' <<< $line)
;;
width*)
if [ -n "$reuseIdentifier" ]; then
width=$(sed 's/[^"]*"\([^"]*\)".*/\1/' <<< $line)
fi
;;
height*)
if [ -n "$reuseIdentifier" ]; then
height=$(sed 's/[^"]*"\([^"]*\)".*/\1/' <<< $line)
fi
;;
esac
if [ -n "$reuseIdentifier" ] && [ -n "$width" ] && [ -n "$height" ]; then
printf "$delimiter@\"$reuseIdentifier\" : [NSValue valueWithCGSize:CGSizeMake($width, $height)]" >> $output
unset reuseIdentifier
unset width
unset height
delimiter=,\\n
fi
done
done
printf "};\n" >> $output
This creates a header file called StoryboardPrototypeCellSizes.h
with a following example content:
@{@"TodayCell" : [NSValue valueWithCGSize:CGSizeMake(320, 80)],
@"SpecialDayCell" : [NSValue valueWithCGSize:CGSizeMake(320, 42)],
@"NameDayCell" : [NSValue valueWithCGSize:CGSizeMake(320, 30)]};
Add a helper method to return the UICollectionViewCell
reuse identifier in the view controller controlling your UICollectionView
:
- (NSString *)cellReuseIdentifierAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.item) {
case 0: return @"TodayCell";
case 1: return @"SpecialDayCell";
case 2: return @"NameDayCell";
}
return nil;
}
Be sure to use the same reuse identifier in cellForItemAtIndexPath
:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell =
[collectionView dequeueReusableCellWithReuseIdentifier:
[self cellReuseIdentifierAtIndexPath:indexPath]
forIndexPath:indexPath];
...
Finally implement sizeForItemAtIndexPath
:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *storyboardPrototypeCellSizes =
#import "StoryboardPrototypeCellSizes.h"
return [(NSValue *)storyboardPrototypeCellSizes[
[self cellReuseIdentifierAtIndexPath:indexPath]
] CGSizeValue];
}
This solution allows you to define UICollectionViewCell
prototype cell sizes only once in the Storyboard(s) and also doesn't do any non-App-Store-compliant at runtime.
****Edit:**** You can also fetch UICollectionReusableView sizes by adding another script with the same content and replacing "collectionViewCell" with "collectionReusableView", and renaming the header file to, for example, StoryboardReusableViewSizes.h