I'm playing around with the transition of the CollectionViewLayout of the examples projects from https://github.com/hebertialmeida/HAPaperViewController and https://github.com/wtmoose/TLLayoutTransitioning and couldn't figure out how to autoresize the content of the cell between the small and large layout.
E.g. I have an UILabel within the CollectionViewCell which should be half the width of the cell at a defined (fixed) position. When the transition to the large layout is finished, the label should be also half of the cell (same relative position), but with a larger font size (or resized).
Use autolayout here or scale the contentView with CGAffineTransformMakeScale?
I've updated the "Resize" sample project in TLLayoutTransitioning to demonstrate how this can be done.
The approach involves updating the font size at every step of the transition using one of the TLLayoutTransitioning callbacks. You probably don't want to use an affine transform because you'll get blurry scaled text at the large size.
The first step is to define a method to set the label's font size. You can use any formula you like, but I've make the font scale proportionally to the cell's width:
- (void)updateLabelScale:(UILabel *)label cellSize:(CGSize)cellSize
{
CGFloat pointSize = cellSize.width * 17 / 128.f;
label.font = [UIFont fontWithName:label.font.fontName size:pointSize];
}
You'll want to call this method in cellForItemAtIndexPath to ensure that new labels appearing on screen are properly scaled:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [super collectionView:collectionView cellForItemAtIndexPath:indexPath];
UILabel *label = (UILabel *)[cell viewWithTag:1];
...
[self updateLabelScale:label cellSize:cell.bounds.size];
return cell;
}
The label's center is constrained to the center of the cell and the size is determined by the intrinsicContentSize. So, when you change the font, the label's size will automatically adjust to fit.
Finally, you'll use the updateLayoutAttributes callback to update the font size of visible cells based on the new layout attributes (you don't need to worry about cells that aren't visible because you're taking care of that in cellForRowAtIndexPath):
__weak ResizeCollectionViewController *weakSelf = self;
[layout setUpdateLayoutAttributes:^UICollectionViewLayoutAttributes *(UICollectionViewLayoutAttributes *pose, UICollectionViewLayoutAttributes *fromPose, UICollectionViewLayoutAttributes *toPose, CGFloat progress) {
CGSize cellSize = pose.bounds.size;
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:pose.indexPath];
if (cell) {
UILabel *label = (UILabel *)[cell viewWithTag:1];
[weakSelf updateLabelScale:label cellSize:cellSize];
}
return nil;
}];
Related
I am trying to set height of UILabel dynamically in the UITableView. During the launch height update is not reflected but as soon as I scroll down and scroll up back, update can be seen.
At Launch
After Scroll down and Scrolling back up again - This what I need. See the change in text in front of player icon. I need the complete text at launch itself.
Here is the code that I am trying to use:
- (void) updateMessageTextForRow:(long)row ofCell:(ESGameStreamCellView *)cell
{
NSString *item = _gameFeedItems[row];
NSString *title = item ?: NSLocalizedString(#"[No Title]", nil);
cell.message.preferredMaxLayoutWidth = cell.message.bounds.size.width;
// Update message label height
CGSize maximumLabelSize = CGSizeMake(296, FLT_MAX);
CGSize expectedLabelSize = [title sizeWithFont:cell.message.font
constrainedToSize:maximumLabelSize
lineBreakMode:cell.message.lineBreakMode];
//adjust the label the the new height.
CGRect newFrame = cell.message.frame;
newFrame.size.height = expectedLabelSize.height;
cell.message.frame = newFrame;
NSLog(#"Message = %#, Height: %f", title, cell.message.frame.size.height);
}
During Custom TableCellView Initialization
- (void)awakeFromNib {
// Initialization code
self.backgroundColor = [UIColor clearColor];
_message.lineBreakMode = NSLineBreakByWordWrapping;
_message.numberOfLines = 0;
}
Code for the row height
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
// Currently fixed height. Will be calculating dynamic height after adjusting the views.
return 300;
}
Are you using autoLayout ? I highly suspect that your constraints aren't set up properly: the UIImageView top should by tied with the titleLabel's bottom.
Also, you should use the new property for dynamic row : rowHeight and estimatedRowHeight.
You are getting the correct height after the cell's reuse : set the preferredMaxLayoutWidth property in the viewDidLayoutSubviews inside your custom cell class.
firstly, you should understand of the working flow of tableview delegate in objective c. Your cell height & position will be fixed after init. that's why you have to define each row's height in
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
// Currently fixed height. Will be calculating dynamic height after adjusting the views.
return 300;
}
From this point forward, your cell's height will be fixed, even if you re-config the frame.
The best practice is some article called "Dynamic height tableview cell" and you can easily find it here
http://www.raywenderlich.com/87975/dynamic-table-view-cell-height-ios-8-swift
I learn on above article (thanks for Joshua Greene) and re-write it to another library allow you to make a dynamic tableview easily. You can find it here
https://github.com/EugeneNguyen/XBMobile
it's not too perfect, but hope that if can help.
I have to show the list details in UITableview. But the listing is small for some cells and large/medium height for some other cells. (Like facebook feed) So Tableview have different height for each cell. How can i achieve this?
The list contains,
imageview,2 labels, textview (Label and textview lengths also increased based on its content. It will also affect the tableview cell height). So any help to achieve this would be greatful.
Thanks.
Create your cells with their own .xib. Add the labels, textview imageviews etc as you want. Make sure to pin all them with constraints horizontally and vertically to the leading/trailing/top and bottom of the cell.
Now the trick is to tell autolayout to layout the contentView of the cell, and return the minimum height to fit its content. Since heightForRow is executed before the cell is loaded in cellForRow, we need to use a fakeCell to calculate the height.
To do this lets say, in our controller, we have a tableView showing cells of class DetailsTableViewCell with a nib created for it DetailsTableViewCell.xib :
1) Create a #property in your controller for a fakeCell.
#property (nonatomic, strong) DetailsTableViewCell *fakeCell;
2) In viewDidLoad, initialize it from them nib:
self.fakeCell = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass:#"DetailsTableViewCell" owner:nil options:nil].firstObject;
3) In heightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
//grab fakeCell, style it with the data you want
[self.fakeCell styleWithDetails:self.details];
//and calculate the minimum height required to fit all content
return [self minimumCellHeightToSatisfyConstraints:self.fakeCell];
}
-(CGFloat)minimumCellHeightToSatisfyConstraints:(UITableViewCell *)cell {
//Makes sure cell's width is equal to tableView's width (can be wrong since they are in separated nibs)
cell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(self.tableView.bounds), CGRectGetHeight(cell.bounds));
//Force autolayout to recalculate all subview's frames
[cell layoutIfNeeded];
//Get the minimum height required for the cell to fit all its content
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;
}
Important: If you forget to pin all subviews in the .xib vertically from top to bottom to the cell, calling systemLayoutSizeFittingSize:UILayoutFittingCompressedSize will return 0 and won't work.
#define CELL_MARGIN 10.0f
-(CGFloat)heightForTableView:(NSString*)text widthOfTableview:(CGFloat)tableViewWidth{
CGSize constraint = CGSizeMake(tableViewWidth - (CELL_MARGIN * 2), 20000.0f);
CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:FONT_SIZE] constrainedToSize:constraint lineBreakMode:UILineBreakModeWordWrap];
CGFloat height = MAX(size.height, 44.0f);
return height + (CELL_MARGIN * 2);
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self heightForTableView:cell.titleLabel.text widthOfTableview:CGRectGetWidth(tableView.frame)];// cell.textlable.text is your lable.text
}
This code might helps you :)
I would like to dynamically adjust the width of a UIImage inside of a UITableViewCell, I'm using the storyboard to design the UITableViewCell, I just added a label and an image, the properties get updated correctly, I'm even loading the value of the width into the label to show that it's the correct value, for the image, I'm loading a background image that I want to repeat, but the image won't update the width initially, if I scroll up and down, the images are shown as expected, here's the code for the cellForRowAtIndexPath, I've also tried to put the code on the willDisplayCell method, same result
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"mycustomcell"];
int r = [[data objectAtIndex:indexPath.row] intValue];
UIImageView *img = (UIImageView *)[cell viewWithTag:2];
img.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"some_img" ofType:#"png"]]];
CGRect frame = img.frame;
frame.size.width = r*16;
img.frame = frame;
int n = img.frame.size.width;
UILabel *label = (UILabel *)[cell viewWithTag:1];
label.text = [NSString stringWithFormat:#"custom %d", n];
[cell setNeedsDisplay];
return cell;
}
I just want this to work initially as it works after scrolling, thoughts?
The dynamic resizing of contents of a tableview cell is a well known problem. While there are kludgy workarounds, I believe proper solution depends upon whether you're using autolayout or not:
If using auto layout, make sure that your cell's image view has a width constraint, and then you can change the constraint's constant:
for (NSLayoutConstraint *constraint in img.constraints)
{
if (constraint.firstAttribute == NSLayoutAttributeWidth)
constraint.constant = r*16;
}
Frankly, I'd rather use a custom UITableViewCell subclass and have an IBOutlet for the width constraint (e.g. imageWidthConstraint), and it saves you from having to enumerate through the constraints to find the right one, and you can simply:
cell.imageWidthConstraint.constant = r*16;
If not using auto layout, you should subclass UITableViewCell, use that for your cell prototype's base class, and then override layoutSubviews, and resize the image view there. See Changing bounds of imageView of UITableViewCell.
Regardless of which approach you adopt, using a UITableViewCell subclass eliminates the need to use viewForTag construct, which makes the view controller code a little more intuitive.
argh, removing Auto Layout fixed the problem
I am trying to create multi-line dynamic UILabels in UITableViewCells. I have a custom UITableViewCell that has a 'comment' label. The cell and the label are created in storyboard.
I can compute the heights of the UITableViewCells properly based on the multi-line data to be stored in the UILabel (using heightForRowAtIndexPath). However, my problem lies in the actual UILabel content. The UILabel content will display only 1 line of data on table load. However, once a cell containing multiline UILabel data moves offscreen and comes back on screen, the multi-line data appears properly in the UILabel with multiple lines. Is there any way to fix this so that the multi-line data appears properly on table load?
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cCell = (CustomCell *)cell;
MyObject = [myArray objectAtIndex:indexPath.row];
cCell.commentLabel.frame = CGRectMake(65.0f, 28.0f, 243.0f, 200.0f);
cCell.commentLabel.text = MyObject.multi_line_text_data;
cCell.commentLabel.adjustsFontSizeToFitWidth = NO;
cCell.commentLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
cCell.commentLabel.font = [UIFont systemFontOfSize:13.0];
cCell.commentLabel.lineBreakMode = NSLineBreakByWordWrapping;
cCell.commentLabel.numberOfLines = 0;
[cCell.commentLabel sizeToFit];
}
Thanks!
Since you're doing this in the storyboard, you can set the necessary label properties there (lineBreakMode and number of lines). Just give the label a specific width constraint and constraints to the top, bottom, and left sides of the cell. Then, in code use sizeWithFont:constrainedToSize:lineBreakMode: in heightForRowAtIndexPath: to calculate the appropriate height for the cell based on the content of the label -- the label, because of its constraints, will expand along with the cell to the proper size. Something like this:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGSize rowSize = [self.theData[indexPath.row] sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(260, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
return rowSize.height + 30;
}
Here, 260 was the width I gave my label in IB, and the 30 is a fudge factor (determined empirically) to account for padding above and below the label.
I met the same problems. Unchecking Autolayout can fix it.
I've frustrated myself with this question for a couple of days. I'm trying to add a UILabel to a UITableViewCell. I want the UILabel to span the entire width of the cell, minus 5 or 10 on both the right and left sides for looks. My problem is in programmatically determining the size of the cell's frame in which to place the label. No matter which UITableViewStyle I use, the cell.contentVew.frame.size.width value is nowhere near the width of the cell frame itself.
For example, in the table I am constructing, I can achieve my desired result by subclassing UITableViewCell and creating a UILabel with a manually determined width (through just trial and error) by:
CGRectMake(10, 12, 397, self.contentView.frame.size.height);
But it's that 397 number that's vexing me. I want a way to programmatically determine what it should be for any width table or style. This should be a simple process by just determining the width of the entire frame of the cell and then subtracting 10 or 20 so the UILabel's edges don't actually touch the edge of the cell.
However, if I set the tableViewStyle to UITableViewStyleDefault and then try:
NSLog(#"Width: %f", self.contentView.frame.size.width);
I get 320. If I set the style to any of the other three styles, the returned number is 302. Even the 320 number isn't anywhere near the width of the cell frame (as with my manually determined number of 397).
What value do I need to access that will return the entire width of the cell's drawing frame? I'm sure, as with most vexing problems, the solution will make me want to slap myself on the forehead, but I'm to the point where I'm ready for it now.
EDIT for more info:
One clarification to anyone interested. This question of mine pertains primarily to a Grouped style table. For a plain style, the answer to my question above can be determined simply in the cellForRowAtIndexPath method by:
CGFloat cellWidth = [tableView rectForRowAtIndexPath:indexPath].size.width;
The problem I'm having is that the rectForRowAtIndexPath method returns the width of the frame in which the cell is drawn, which is fine for a plain style table since the cell width is the entire width of the frame. However, in a grouped table, the width of the cell is less than the width of the frame in which it is drawn, so this method will return a number that is quite a bit wider than the width of the cell. It's possible that the width of the cell in a grouped table style is a fixed number less than the width of the table frame, so this might be the way to solve the problem. I'll investigate that and answer my own question here if that's the case.
I have determined my own answer, and I hope it helps anyone faced with the same issue. The calculation of the margin of a grouped tableView I found on this StackOverflow answer.
This code will provide a label within a tableView cell that spans the cell with a margin between the two edges of the cell, and centered vertically within the cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
UILabel *label;
CGFloat groupedStyleMarginWidth, tableViewWidth;
UIFont *labelFont = [UIFont boldSystemFontOfSize:17.0]; // Set to whatever you like
NSString *labelText = #"Test String";
// Calculate the margin between the cell frame and the tableView
// frame in a grouped table view style.
tableViewWidth = tableView.frame.size.width;
if (tableView.style == UITableViewStyleGrouped) {
if (tableViewWidth > 20)
groupedStyleMarginWidth = (tableViewWidth < 400) ? 10 : MAX(31, MIN(45, tableViewWidth*0.06));
else
groupedStyleMarginWidth = tableViewWidth - 10;
}
else
groupedStyleMarginWidth = 0.0;
if (cell == nil) {
CGRect tableViewRect;
CGRect labelRect;
CGFloat x, y, w, h, labelMargin;
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
// Retrieve the rect of the table view.
tableViewRect = [tableView rectForRowAtIndexPath:indexPath];
// Set whatever margin around the label you prefer.
labelMargin = 10;
// Determine rect values for the label.
x = tableRect.origin.x + labelMargin;
// Calculate width of label
w = tableRect.size.width - (groupedStyleMargin * 2) - (labelMargin * 2);
// Calculate height of table based on font set earlier.
h = [labelText sizeWithFont:font].height;
// Calculate y position for the label text baseline to center
// vertically within the cell.
y = (tableRect.origin.y / 2) - (h / 4);
labelRect = CGRectMake(x, y, w, h);
label = [[UILabel alloc] initWithFrame:labelRect];
label.text = labelText;
label.tag = 0;
[cell.contentView addSubview:stepLabel];
[label release];
}
else {
label = (UILabel *)[cell viewWithTag:0];
}
Sounds like this would best be handled by auto layout constraints nowadays.