Created
February 15, 2016 23:01
-
-
Save moritzh/8baf006b4120a889476c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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