UITableViewCell with UIImage, width not updating on initial displayed cells - ios

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

Related

Can I set my autolayout constraints so that an imageview is set to the height of the image

I am building out a dynamic tableview cell. The height is dynamic based on the content that is loaded. I'm running into a hangup with height of the imageview. The images currently load aspect fill in an imageview that is 100% of the width. Is it possible to set an autolayout constraint on my imageview so that the imageview is hidden (has a height of 0) if there is no image for a tableview cell?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewTileCell *cell = [tableView dequeueReusableCellWithIdentifier:#"tileCell"];
if (!cell)
{
[tableView registerNib:[UINib nibWithNibName:#"TableViewTileCell" bundle:nil] forCellReuseIdentifier:#"tileCell"];
cell = [tableView dequeueReusableCellWithIdentifier:#"tileCell"];
}
cell.tileView.layer.borderColor = [UIColor blackColor].CGColor;
cell.tileView.layer.borderWidth = 1.0f;
[cell.contentView setBackgroundColor:[UIColor colorWithRed:0.96 green:0.96 blue:0.96 alpha:1.0]];
NSString *url=[self.resultsArray[indexPath.row] valueForKey:#"imageURL"];
cell.tileTitle.text = [self.resultsArray[indexPath.row] valueForKey:#"title"];
cell.tileDate.text = [self.resultsArray[indexPath.row] valueForKey:#"date"];
cell.tileContent.text = [self.resultsArray[indexPath.row] valueForKey:#"summary"];
if(![url isEqualToString:#""]){ //If we got a url value back load the image
[cell.tileImageview setImageWithURL:[NSURL URLWithString:url] placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
}
return cell;
}
Is it possible to set an autolayout constraint on my imageview so that the imageview is hidden (has a height of 0) if there is no image for a tableview cell?
Make the whole cell content a vertical UIStackView. In cellForRow, make the image view hidden. The stack view has the wonderful ability to change the constraints in exactly the way you describe: when the image view is hidden, the constraints will change and the image view will occupy zero space, with the other views occupying the whole space.
The alternative is not terrible: you simply have to do, yourself, what the UIStackView would do — remove the empty image view in code and adjust the constraints, in cellForRow. Swapping constraints and views together into and out of the interface is standard practice, and is easy to do.

UIView's autolayout size reported from wrong size class the first time

I have a UITableViewController with some custom cells and an UIView within each cell. I have implemented the controller's cellForRowAtIndexPath to configure the UIView in each cell. For this purpose I need to know the width of the UIView on screen. Since I am using autolayout and size classes to automatically change the size of the UIView based on device orientation, I have implemented an additional method of getting the width runtime.
The problem is that when the table view is presented the first time, my code reports width for UIVIew from a compact width size class even when I am using the device in the landscape orientation. The system renders all the views as should, but my code to get the width is not working. Scrolling new cells visible or an orientation change will remedy the situation immediately.
My code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// ...
CustomCell* cell = [tableView dequeueReusableCellWithIdentifier:#"CustomCell"];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:self options:nil];
cell = [nib firstObject];
} else {
// Clear custom content
NSArray *viewsToRemove = [cell.histogramView subviews];
for (UIView *v in viewsToRemove) {
[v removeFromSuperview];
}
}
[cell setNeedsLayout];
[cell layoutIfNeeded];
int width = ((CustomView*)cell.customView).getWidth;
NSLog(#"width = %d", width);
// ...
}
And then:
#implementation CustomView
- (int)getWidth {
[self setNeedsLayout];
[self layoutIfNeeded];
int width = self.frame.size.width;
return width;
}
#end
Edited to add:
The problem seems to be that at when cellForRowAtIndexPath is called the first time tableview appears, autolayout has not occurred for the cell. Forcing it with [cell setNeedsLayout] and [cell layoutIfNeeded] right after creating the cell does not do the trick either.
It seems my problem root cause is a potential duplicate of How to know the width of an UITableViewCell when using auto layout? So the problem has to do with fact that when my CustomCell is loaded from a nib, it will have the default frame. Special tricks should be done to force autolayout. However, the accepted answer does not work for cells that are initially out of visible area. Any takers on this?
I would suggest trying to use constraints and ratios instead of actual width of the frame, since, as stated by Marcus Adams, the frame will have the value you are actually looking for only after viewDidAppear.
For example if your cells contain a UILabel that is 1/3 the width of the cell, and a UIImageView that fills the space left, you can set constraints between them and the parent view.
Can your logic be changed like this?
Another suggestion would be to call reloadData on your table view after viewDidAppear, but that is definitely not a nice option UX-wise.
I found out the correct solution to this:
Create a custom cell like in the original question.
Set cell.contentMode = UIViewContentModeRedraw in tableview: cellForRowAtIndexPath:
Implement layoutSubviews for the custom cell like so:
- (void)layoutSubviews {
[super layoutSubviews];
NSLog(#"cell width == %f", self.bounds.size.width);
}
This way you will have access to the size of the cell as it will appear on screen.

Resize / scale content of UICollectionView cell within layout transition

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;
}];

ios autolayout issue with UILable text padding

I have a custom class created for custom cells for my table view. There's UILabel for messages. I'm using storyboard to set the constraints, pinning the label to the top and right side of the table cell, I found that doing this causes the label to resize to fit the content. Now I can't figure out how to add padding to the label because anything done in cellForRowAtIndexPath doesn't work since auto layout is selected. I've seen many examples that do CGRectMake with float values but they don't work and I think it's because of auto layout. Is there any solution for this?
I'm not even sure what code to show for this...so here goes something:
How I'm getting the height of the message...the cell is set for 220 width...cellForRowAtIndexPath:
static NSString *cellIdentifier = #"chatCell";
ChatCell *cell = (ChatCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[ChatCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
info = [receivedData objectAtIndex:indexPath.row];
message = [info objectForKey:#"message"];
CGSize constraint = CGSizeMake(220, 2000);
CGRect rect = [message boundingRectWithSize:constraint options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:[UIFont systemFontOfSize:12.0f]} context:nil];
CGSize size = rect.size;
With that information, I set the table row heights in heightForRowAtIndexPath. But adjusting the frame of the label cell.messageLabel gives me no result. Here's something else I tried in the cellForRowAtIndexPath:
UIView *messageFrame = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 220, size.height)];
[messageFrame addSubview:cell.messageLabel];
[cell addSubview:messageFrame];
But this is the result in the simulator:
Everything is moved up and the whole text doesn't show. Is there a simple solution for this or do I have to rethink my entire code?
ANSWER:
What I did was add a UIView in storyboard and put it's constraints to the bottom of the cell. Since I was adjusting the cell height based on content bounds function from ios7, setting the constraint to the bottom automatically stretched the UIView all the way to the bottom. Playing with the message label width, I got to have padding. Hope this helps someone.

Placing a UImageView in a cell programmatically

I've got a UITableViewCell *cell, which has a UIImageView *image and a UILabel *description subviews. Description label held the upper part of the cell, I need to locate the UIImageViewbetween the label and the bottom edge. Here's my code:
CGFloat bottomTextY = cell.labelDescription.frame.origin.y + cell.labelDescription.frame.size.height;
CGFloat newImageY = bottomTextY + (cell.frame.size.height - bottomTextY) / 2;
cell.image.center = CGPointMake(cell.image.center.x, newImageY);
But every time the position is different, it's not strictly between the label and the edge. Autolayout is off. Please, help me!)
To make your life easier, you should design your cell (prototype cell) within the storyboard. That cell will include your two fields (image and text). Then tag your fields and in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
add something that looks like :
UIImageView * imageView = (UIImageView *)[cell viewWithTag:CELL_TAG_PHOTO];
imageView .image = [self.photoSource objectForKey:key] ;
Of course, the tag (number) of each element has to be unique since this is how the elements are identified (define them with #define CELL_TAG_PHOTO (tag_number))
Set the frame of the UIImageView, not the center property.
Example:
cell.imageView .frame = CGRectMake(0, CGRectGetMaxY(cell.descriptionLabel.frame), cell.frame.size.width, cell.frame.size.height - CGRectGetMaxY(cell.descriptionLabel.frame));

Resources