Skip to content

Instantly share code, notes, and snippets.

@moritzh
Created February 15, 2016 23:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save moritzh/8baf006b4120a889476c to your computer and use it in GitHub Desktop.
Save moritzh/8baf006b4120a889476c to your computer and use it in GitHub Desktop.
//
// CellSizeCalculator.h
//
// Created by Moritz Haarmann on 19.09.12.
// Copyright (c) 2012 n/a. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface CSCLabelElement : NSObject
@property (nonatomic,copy) NSString* title;
@property (nonatomic,strong) UIFont* usedFont;
@end
@interface CSCFixedSizeElement : NSObject
@property (nonatomic) float verticalHeight;
@end
@interface CellSizeCalculator : NSObject
/**
* Contains the saved views for each cell registered.
*/
@property (nonatomic,strong) NSMutableDictionary* savedViews;
@property (nonatomic,strong) NSMutableDictionary* savedConstraints;
/**
* Takes the name of a xib containing one tableviewcell subclass.
*/
-(void)registerCellWithXib:(NSString*)xibName dynamicViews:(NSArray*)mappableView;
- (CGFloat)heightForCell:(NSString *)name withKeyValueDictionary:(NSDictionary *)keyValuesForLabels inTableView:(UITableView*)tableView withAccessory:(BOOL)accessory;
- (CGFloat)heightForCell:(NSString *)name withKeyValueDictionary:(NSDictionary *)keyValuesForLabels inTableView:(UITableView*)tableView;
- (void)layoutCell:(UITableViewCell *)cell;
/**
* Shared instance.
*/
+(CellSizeCalculator*)sharedInstance;
@end
//
// CellSizeCalculator.m
//
// Created by Moritz Haarmann on 19.09.12.
// Copyright (c) 2012 n/a. All rights reserved.
//
#import "CellSizeCalculator.h"
#import <CoreText/CoreText.h>
#import "UITableView+Padding.h"
static CellSizeCalculator *staticInstance;
@implementation CellSizeCalculator
- (id)init {
self = [super init];
if(self) {
self.savedConstraints = [NSMutableDictionary new];
self.savedViews = [NSMutableDictionary new];
}
return self;
}
/**
* Takes the name of a xib containing one tableviewcell subclass.
*/
- (void)registerCellWithXib:(NSString *)xibName dynamicViews:(NSArray *)mappableViews {
if ( [self.savedConstraints objectForKey:xibName]){
return;
}
UITableViewCell *loadedCell = [[[NSBundle mainBundle] loadNibNamed:xibName owner:nil options:nil] objectAtIndex:0];
assert(loadedCell);
NSMutableArray *ownConstraints = [NSMutableArray new];
NSMutableDictionary *temporaryNameToViewMapping = [NSMutableDictionary new];
// After the cell is loaded, we need to built a vertical list of items that consists of fixed spaces
// and the label placeholders for later use in the real layout.
NSMutableArray *collectedMappableViews = [NSMutableArray new];
for(NSString *viewPropertyName in mappableViews) {
UILabel *retrievedLabel = [loadedCell valueForKey:viewPropertyName];
assert(retrievedLabel);
[collectedMappableViews addObject:retrievedLabel];
[temporaryNameToViewMapping setObject:viewPropertyName forKey:[NSNumber numberWithFloat:retrievedLabel.frame.origin.y].stringValue];
}
// No we have an unordered list of elements which we need to sort.
NSArray *sortedViews = [collectedMappableViews sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"originY" ascending:YES]]];
int lastOffset = 0;
for(UILabel *label in sortedViews) {
int newOffset = label.frame.origin.y - lastOffset;
if(newOffset) {
CSCFixedSizeElement *fixedSize = [CSCFixedSizeElement new];
fixedSize.verticalHeight = newOffset;
[ownConstraints addObject:fixedSize];
}
lastOffset += newOffset;
CSCLabelElement *labelElement = [CSCLabelElement new];
labelElement.title = [temporaryNameToViewMapping valueForKey:[NSNumber numberWithFloat:label.frame.origin.y].stringValue];
labelElement.usedFont = label.font;
[ownConstraints addObject:labelElement];
lastOffset += label.frame.size.height;
}
// Bottom Padding
UILabel *lastElement = [sortedViews lastObject];
CGFloat distanceToBottom = loadedCell.frame.size.height - lastElement.frame.size.height - lastElement.frame.origin.y;
CSCFixedSizeElement *fixedSizeBottomPadding = [CSCFixedSizeElement new];
fixedSizeBottomPadding.verticalHeight = distanceToBottom;
[ownConstraints addObject:fixedSizeBottomPadding];
for(id anyConstraint in ownConstraints) {
if([anyConstraint isKindOfClass:[CSCFixedSizeElement class]]) {
DDLogVerbose(@"Fixed Size, Height: %f", [anyConstraint verticalHeight]);
} else {
DDLogVerbose(@"Label Class");
}
}
[self.savedConstraints setObject:ownConstraints forKey:NSStringFromClass([loadedCell class])];
[self.savedViews setObject:loadedCell forKey:NSStringFromClass([loadedCell class])];
}
- (CGFloat)heightForCell:(NSString *)name withKeyValueDictionary:(NSDictionary *)keyValuesForLabels inTableView:(UITableView*)tableView {
return [self heightForCell:name withKeyValueDictionary:keyValuesForLabels inTableView:tableView withAccessory:NO];
}
- (CGFloat)heightForCell:(NSString *)name withKeyValueDictionary:(NSDictionary *)keyValuesForLabels inTableView:(UITableView*)tableView withAccessory:(BOOL)accessory{
CGFloat viewWidth = tableView.frame.size.width - tableView.paddingSize*2;
if ( accessory) {
viewWidth -= 60;
}
NSArray *constraints = [self.savedConstraints objectForKey:name];
// assert(constraints);
// ios 5 quick fix.
if ( !constraints){
return 0;
}
assert(viewWidth);
UITableViewCell *savedCell = [self.savedViews objectForKey:name];
// assert(savedCell);
// Resize to the given width, so that autosizing works.
[savedCell setFrame:CGRectMake(0, 0, viewWidth, savedCell.frame.size.height)];
[savedCell layoutSubviews];
CGFloat calculatedHeight = 0;
BOOL skipNext = NO;
for(id anyConstraint in constraints) {
// Simply add Vertical Heights
if ( skipNext ){
// One field skipped because a label was empty..
skipNext = NO;
}
else if([anyConstraint isKindOfClass:[CSCFixedSizeElement class]]) {
calculatedHeight += [anyConstraint verticalHeight];
} else if([anyConstraint isKindOfClass:[CSCLabelElement class]]) {
CSCLabelElement *labelConstraint = (CSCLabelElement *)anyConstraint;
NSString *labelText = (NSString *)[keyValuesForLabels objectForKey:labelConstraint.title];
// Have a value for the current label?
if(labelText && labelText.length > 0) {
// calculate the height of the label given the current width
UILabel *labelToUse = [savedCell valueForKey:[anyConstraint title]];
CGSize sizeWithText;
if ( labelToUse.numberOfLines < 2 ){
sizeWithText = [labelText sizeWithFont:labelToUse.font];
} else {
sizeWithText = [labelText sizeWithFont:labelToUse.font constrainedToSize:CGSizeMake(labelToUse.frame.size.width, CGFLOAT_MAX) lineBreakMode:labelToUse.lineBreakMode];
}
calculatedHeight += sizeWithText.height;
} else {
// SKip next.
skipNext = YES;
}
}
}
return calculatedHeight;
}
- (void)layoutCell:(UITableViewCell *)cell {
NSString* cellName = NSStringFromClass([cell class]);
NSArray* constraints = [self.savedConstraints objectForKey:cellName];
if (!constraints){
return;
}
assert(constraints);
CGFloat calculatedHeight = 0;
BOOL skipNext = NO;
for(id anyConstraint in constraints) {
// Simply add Vertical Heights
if ( skipNext ){
// One field skipped because a label was empty..
skipNext = NO;
}
else if([anyConstraint isKindOfClass:[CSCFixedSizeElement class]]) {
calculatedHeight += [anyConstraint verticalHeight];
} else if([anyConstraint isKindOfClass:[CSCLabelElement class]]) {
CSCLabelElement *labelConstraint = (CSCLabelElement *)anyConstraint;
UILabel* labelInCell = [cell valueForKey:labelConstraint.title];
// Have a value for the current label?
if(labelInCell.text.length > 0) {
// calculate the height of the label given the current width
CGSize sizeWithText;
if ( labelInCell.numberOfLines < 2 ){
sizeWithText = [labelInCell.text sizeWithFont:labelInCell.font];
} else {
sizeWithText = [labelInCell.text sizeWithFont:labelInCell.font constrainedToSize:CGSizeMake(labelInCell.frame.size.width, 10000) lineBreakMode:labelInCell.lineBreakMode];
}
CGRect newLabelFrame = labelInCell.frame;
newLabelFrame.origin.y = calculatedHeight;
newLabelFrame.size.height = sizeWithText.height;
labelInCell.frame = newLabelFrame;
calculatedHeight += sizeWithText.height;
} else {
// SKip next.
skipNext = YES;
}
}
}
CGRect cellFrame = cell.frame;
cellFrame.size.height = calculatedHeight;
// cell.frame = cellFrame
}
+ (CellSizeCalculator *)sharedInstance {
if(staticInstance == NULL) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
staticInstance = [[CellSizeCalculator alloc] init];
});
}
return staticInstance;
}
@end
@implementation CSCFixedSizeElement
@synthesize verticalHeight;
@end
@implementation CSCLabelElement
@synthesize title;
@synthesize usedFont;
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment