iOS: UIImageView changes with change in UITableView.rowHeight, how to avoid? - ios

This is really a n00b question, I am learning iOS while trying to build an app
I need to show an image, label on UITableViewCell. The following code does that for me
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.imageView.image = [self getImageNameForRow:indexPath.row];
cell.textLabel.text = self.features[(NSUInteger) indexPath.row];
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
return cell;
}
The problem is that image size comes bigger that I expect. so I tried to increase the height of row as
self.tableView.rowHeight = 80;
But then the image also scales up.
How can I keep my image size fixed while increasing (or changing) the size of the row?

The problem is that you are using a default table view cell style. That style comes with a built-in textLabel and an imageView, and the latter is a UIImageView with constraints so that it is resized to fill the height of the cell. But you have also said
cell.imageView.contentMode = UIViewContentModeScaleAspectFit
Which means that as the image view grows, the image grows with it - exactly what you are seeing.
The solution, as I explain here, is to size the image down to the actual size that you want it - and set the image view's contentMode to center. Something like this:
UIImage* im = [self getImageNameForRow:indexPath.row];
UIGraphicsBeginImageContextWithOptions(CGSizeMake(36,36), YES, 0);
[im drawInRect:CGRectMake(0,0,36,36)];
UIImage* im2 = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
cell.imageView.image = im2;
cell.imageView.contentMode = UIViewContentModeCenter;
Change that 36,36 to the size you actually want.
This is good practice anyway. It is a terrible waste of memory to hold onto an image at a larger size than needed for actual display (the amount of wasted memory grows exponentially, because area is on the order of the square of a single dimension). So you should always size images down to their actual display size. There's lots and lots of code on Stack Overflow showing many other ways to do that.

I believe your main problem here is the image is too large. If the image were only 40x40, it would appear as half the tableViewCell's height (when it's 80). IIRC the UIImageView in that UITableViewCell stretches to the height of the cell, and images will always fill it if they're large enough.
Three things you could do:
1) Shrink the size of the image to the size you want.
2) Change the frame of the imageView manually like so:
cell.imageView.image = [self getImageNameForRow:indexPath.row];
CGPoint center = cell.imageView.center;
CGRect frame = cell.imageView.frame;
frame.size.width = 40;
frame.size.height = 40;
cell.imageView.frame = frame;
cell.imageView.center = center;
I'm not entirely certain if you need to cache the center and re-set it after the frame change or not (the UITableViewCell might do this automatically).
3) Make a custom UITableViewCell subclass that has a fixed size UIImageView. I've detailed how to do this on my blog here.
I recommend 1 or 3.

Related

How to adjust the UIImageview under custom tableview cell

I have a custom tableview and under each cell i have image-view. I am receiving images of different sizes from the service and i want to adjust my frame according to that.
Whenever there is a picture smaller than the frame size i want to adjust the height according to its height.
So i am comparing the current frame size and image size and trying to update the imageview frame,but its not working.
cell.image.contentMode = UIViewContentModeScaleAspectFit;
if(cell.image.image.size.height<cell.image.frame.size.height)
{
cell.image.frame = CGRectMake(cell.image.frame.origin.x, cell.image.frame.origin.y,cell.image.frame.size.width, cell.image.image.size.height);
}
what needs to be done to overcome the problem??
You would require to change cell height according to image in UITableView datasource like this
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (image.size.width > CGRectGetWidth(self.view.bounds)) {
CGFloat ratio = image.size.height / image.size.width;
return CGRectGetWidth(self.view.bounds) * ratio;
} else {
return image.size.height;
}
}
EDIT
Incase you are looking out for resizing image and not tableview cell then these are Resize UIImage with aspect ratio? and Resize UIImage with aspect ratio? very good references. Also you should understand UIImageView Scaling Explained Visually
If the code above is in cellForRowAtIndexPath and the cell that you have initialized inside cellForRowAtIndexPath is an instance of ItemCell (for instance), then in your ItemCell class, you will have to override layoutSubviews and check the size of the image subview inside layoutSubviews and adjust the height of the frame of the cell accordingly inside layoutSubviews. This ensures that every time for cellForRowAtIndexPath is called, the layout of the UITableViewCell subclass (ItemCell) happens before the cell gets rendered by cellForRowAtIndexPath.
Btw, the fact that you have set the contentMode of the cell to ScaleAspectFit means that regardless of what you do to the frame of the imageView inside the cell, the image will always be scaled to fit the entire view available to it, i.e. in this case, the dimensions of the imageView inside the cell. Maybe you should try moving the contentMode line
cell.image.contentMode = UIViewContentModeScaleAspectFit;
after the conditional where you set the frame size of the imageView :
if(cell.image.image.size.height<cell.image.frame.size.height)
{
cell.image.frame = CGRectMake(cell.image.frame.origin.x, cell.image.frame.origin.y,cell.image.frame.size.width, cell.image.image.size.height);
}
cell.image.contentMode = UIViewContentModeScaleAspectFit;
Ofcourse, this would probably not solve the problem if you aren't overriding layoutSubview to adjust the dimensions of the imageView frame there.

UIImageView not correctly resizing in self sizing cells iOS 8

I'm trying to achieve something similar to the 9gag app where the cells automatically resize based in the size of the image. The width hugs the sides but the height is resized as needed.
I have a Label that consists of the title above it as well as a UIImageView and below the image I have share icons etc. I've set the constraints for the image to be the UIImageView and to the lower share icons as well as to both leading and trailing spaces. It seems like I've tried every combination of switching "Mode" of the view (i.e. aspect fill, aspect fit) when I do aspect fit it sizes the images correctly but adds a bunch of padding to both the lower and upper parts of the image (screenshot included below)
The label and icons all have the appropriate constraints set (as far as I can tell)
I've searched for hours trying to find someone with a similar issue but to no avail I can't seem to find anything in regards to self sizing cells that use images that may vary in size.
Can anyone point me in the right direction or know of the solution to my problem? All help is very much appreciated as always!
As fas as I've seen, you can't really do image resizing from Interface Builder. You would have to manually write the code in your ViewController.m file. Have something like this:
- (void) adjustImageSize{
CGSize imageSize = self.imageView.image.size;
CGSize imageViewSize = self.imageView.frame.size;
float imageAspectRatio = imageSize.width/imageSize.height;
CGRect frame = self.statusImage.frame;
frame.size.height =imageViewSize.width/imageAspectRatio;
self.imageView.frame = frame;
// set your Cell's frame here, based on your other constraints (labels/buttons etc), after you have resized your image
}
You can call this from your tableView datasource function. And then add this delegate function to tell the tableView the dynamic height of each cell.
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
return cell.frame.size.height;
}
This is how I did dynamic resizing of images for a NewsFeed-based app. Hope this helps.
This occurs due to the UIImageView intrinsic content size (image size).
While your image view's width gets resized by leading and trailing space constraints, nothing limits its height so it remains equal to the original image height.
The solution is to calculate an aspect ratio of the image and set it as a ratio constraint to the UIImageView width
#interface PostImageViewCell()
#property (nonatomic, strong) NSLayoutConstraint *aspectConstraint;
#end
#implementation PostImageViewCell
- (void) setAspectConstraint:(NSLayoutConstraint *)aspectConstraint {
if (_aspectConstraint != nil) {
[self.postImage removeConstraint:_aspectConstraint];
_aspectConstraint = nil;
}
if (aspectContraint != nil) {
[self.postImage addConstraint:aspectConstraint];
_aspectConstraint = aspectConstraint
}
}
- (void)setPicture:(UIImage *)image {
CGFloat aspect = image.size.width / image.size.height;
self.aspectConstraint = [NSLayoutConstraint constraintWithItem:self.postImage
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.postImage
attribute:NSLayoutAttributeHeight
multiplier:aspect
constant:0.0];
[self.postImage setImage:image];
}
#end
I have the same problem.
I noticed that the cell will calculate its size based on the image in your imageView and that it ignores that the UIImageView will change the image's size to make it fit its bounds.
Example: Your image has the size 1000x1000. If you set your content mode to 'Aspect Fit' the UIImageView will display the image with the width of itself. So the displayed image might have the width of your iPhone - e.g. 750px and the height 750px (it's a square).
Now the cell will resize itself as if the imageView would display the image in its original format; 1000x1000. Which leads to unwanted white space.
My workaround right now is to size down the image manually but this is very costly and it also creates some other problems with my UI.
Did you find a better solution?

How to layout custom UITableViewCell with varying height

I have a UITableViewCell subclass which has an image, title and description.
I am supposed to resize the cell height according to the description content length i.e. if it spans more than 5 lines, I should extend it (+other subviews like image etc) till it lasts.
The next coming cells should begin only after that.
I have my UITableViewCell subclass instantiated from xib which has a fixed row height = 160.
I know this is pretty standard requirement but I am unable to find any guidelines.
I already extended layoutSubViews like this:
- (void) layoutSubviews
{
[self resizeCellImage];
}
- (void) resizeCellImage
{
CGRect descriptionRect = self.cellDescriptionLabel.frame;
CGRect imageRect = self.cellImageView.frame;
float descriptionBottomEdgeY = descriptionRect.origin.y + descriptionRect.size.height;
float imageBottomEdgeY = imageRect.origin.y + imageRect.size.height;
if (imageBottomEdgeY >= descriptionBottomEdgeY)
return;
//push the bottom of image to the bottom of description
imageBottomEdgeY = descriptionBottomEdgeY;
float newImageHeight = imageBottomEdgeY - imageRect.origin.y;
imageRect.size.height = newImageHeight;
self.cellImageView.frame = imageRect;
CGRect cellFrame = self.frame;
cellFrame.size.height = imageRect.size.height + imageRect.origin.y + 5;
CGRect contentFrame = self.contentView.frame;
contentFrame.size.height = cellFrame.size.height - 1;
self.contentView.frame = contentFrame;
self.frame = cellFrame;
}
It pretty much tells that if description is taller than image, we must resize the image as well as cell height to fit the description.
However when I invoke this code by doing this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.cellDescriptionLabel.text = #"Some long string";
[cell.cellDescriptionLabel sizeToFit];
[cell setNeedsLayout];
return cell;
}
It appears that while cell frame is changed due to layoutSubViews call, other cells do not respect it. That is, they appear on the same position had the previous cell would not have resized itself.
Two questions:
How to make it possible what I want?
Am I doing right by calling setNeedsLayout within cellForRowAtIndexPath?
P.S.: I know heightForRowAtIndexPath holds key to changing the cell height, but I feel that the data parsing (not shown here) that I do as part of cellForRowAtIndexPath would be an overkill just to calculate height. I need something that can directly tell the UITableViewCell to resize itself according to content needs.
-tableView:heightForRowAtIndexPath: is by design how variable sized cells are calculated. The actual frame of a cell is of no importance and is changed by the table view to fit its needs.
You are sort of thinking of this backwards. The delegate tells the table view how cells need to be drawn, then the table view forces cells to fit those characteristics. The only thing you need to provide to the cell is the data it needs to hold.
This is because a table view calculates all the heights of all the cells before it has any cells to draw. This is done to allow a table view to size it's scroll view correctly. It allows for properly sized scroll bars and smooth quick-pans through the table view. Cells are only requested when a table view thinks a cell needs to be displayed to the screen.
UPDATE: How Do I Get Cell Heights
I've had to do this a couple of times. I have my view controller keep a cell which is never used in the table view.
#property (nonatomic) MyTableViewCell *standInCell;
I then use this cell as a stand in when I need measurements. I determine the base height of the cell without the variable sized views.
#property (nonatomic) CGFloat standInCellBaseHeight;
Then in -tableView:heightForRowAtIndexPath:, I get the height for all my variable sized views with the actual data for that index path. I add the variable sized heights to my stand in cell base height. I return that new calculated height.
Note, this is all non-autolayout. I'm sure the approach would be similar, but not identical to this, but I have no experience.
-tableView:heightForRowAtIndexPath: is the preferred way to tell tableview the size of its cells. You may either precalculate and cache it in a dictionary and reuse, or alternatively in ios7, you can use -tableView:estimatedHeightForRowAtIndexPath: to estimate the sizes.
Take a look at this thread - https://stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights, the answer points to a very good example project here - https://github.com/caoimghgin/TableViewCellWithAutoLayout.
Sorry, but as far as I know you have to implement tableView:heightForRowAtIndexPath:. Warning, in iOS 6 this gets called on every row in you UITableView right away, I think to draw the scrollbar. iOS7 introduces tableView:estimatedHeightForRowAtIndexPath: which if implemented allows you to just guess at the height before doing all the calculation. This can help out a lot on very large tables.
What I found works well is just have your tableView:heightForRowAtIndexPath: call cellForRowAtIndexPath: to get the cell for that row, and then query that cell for it's height cell.bounds.size.height and return that.
This works pretty well for small tables or in iOS7 with tableView:estimatedHeightForRowAtIndexPath implemented.

Difficulty changing the size of image in UITableViewCell

Problem:
I use the following code inside the method cellForRowAtIndexPath to set the size of the image for the cell, yet at runtime the image gets blown up to the maximum height and width that the table row will allow.
UIImage *_image = [imageDictionary objectForKey:#"image"]; // Get image data
[_image drawInRect:CGRectMake(0,0,50,50)]; // set size
[cell.imageView setImage: _image]; // assign image to cell
cell.imageView.frame = CGRectMake(cell.imageView.frame.origin.x,cell.imageView.frame.origin.y,50,50);
return cell;
Question: Is there a more robust method of controlling the size of the image in a UITableViewCell? The approach I'm taking comes from several other posts but for some reason its being ignored in my code.
Side-note: I'm using Xcode 5 and developing on an iOS 7 platform.
Use UITableViewCell contentView .
The content view of a UITableViewCell object is the default superview for content displayed by the cell. If you want to customize cells by simply adding additional views, you should add them to the content view so they will be positioned appropriately as the cell transitions into and out of editing mode.
Example:
UIImage *_image = [imageDictionary objectForKey:#"image"]; // Get image data
[_image drawInRect:CGRectMake(0,0,50,50)]; // set size
UIImageView *imageView = [[UIImageView alloc] initWithImage: _image];
[imageView setFrame:yourFrame];
[cell.contentView addSubview:imageView];
This doesn't answer the question but your underlying problem is your approach. You should be customizing your cells by subclassing UITableViewCell. To add to that it's a lot easier to manipulate cell contents as views than to play around with the default picture and text label they give you. To carify, the contents of the cell sit on a view known as contentView accessible as cell.contentView. You can add text labels, buttons, and images as subviews to any location with any size you want the same way you would do with any view added as a subview.

Rearranging Cell Subviews When UITableViewCell Resizes

I have a UITableViewCell that is implemented using storyboard that looks like:
Here is what the cell should look like without an image:
I have been fiddling with the constraints and banging my head trying to figure this out but have had no luck. I have a pretty good understanding of constraints and how to add them programmatically but have had no luck with this specific problem and feel like I am just adding layout constraints to the cell willy-nilly with no logical thought process. The cell represents a newsfeed post which may or may not have an image in the main image view at the top, and should behave as follows. If the cell doesn't have an image in it the bottom bar with the like and comment counts, moves up to align with the top of the cell. I achieved this behaviour by setting a constraint that kept the smaller image view, post title, post time and the post content a set distance away from the bottom of the cell. This approach works and when the cell is resized in the heightForRowAtIndexPath method the subviews move appropriately. The problem comes when the text in the post content is larger then a single line. The height of the cell adjusts correctly but the top of the text view stays at the same location and grows downward and overflows into the next cell. When I place the constraints to align the four subviews with the top of the cell I run into issues when there is no image and the post content is larger then a single line. In this case, the cell resizes to be smaller than its original size and the subviews stay at the distance specified by the constraint. The smaller image, post title, time and content are clipped and don't display. This is such an odd problem with so many different cases. I have been working at this for almost two days and could really use someone else's thoughts on how to solve this issue. I hope this isn't too confusing, thanks for the help!
I have one way to solve this, but I'm sure there are many others. I gave both image views a fixed height constraint. The small image view and the top label (Post Title) have fixed heights to the top of the cell -- both of these as well as the height constraint of the large image view have IBOutlets to them so they can be changed in code. The bottom label (Post Content) has its number of lines set to 0, and has an IBOutlet to its height constraint (all the labels had the standard 21 point height to start). In code, I check for the existence of an image at each indexPath, and change the constraints accordingly.
- (void)viewDidLoad {
UIImage *image1 = [UIImage imageNamed:#"House.tiff"];
[super viewDidLoad];
self.theData = #[#{#"pic":image1, #"post":#"short post"},#{#"post":#"short post"},#{#"pic":image1, #"post":#"Long long post with some extra stuff, and even some more"},#{#"post":#"Long long post with some extra stuff, and even some more"}];
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.theData.count;
}
-(CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat ivHeight = (self.theData[indexPath.row][#"pic"])? 215 : 0; // 215 is the fixed height of the large image view
CGSize labelSize = [self.theData[indexPath.row][#"post"] sizeWithFont:[UIFont systemFontOfSize:17] constrainedToSize:CGSizeMake(152, CGFLOAT_MAX)];
return 140 + ivHeight + labelSize.height; // the 140 was determined empirically to get the right spacing between the 3 labels and the bottom bar
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RDCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.label.text = self.theData[indexPath.row][#"post"];
cell.iv.image = self.theData[indexPath.row][#"pic"];
if(self.theData[indexPath.row][#"pic"] == nil){
cell.heightCon.constant = 0; // heightCon is the outlet to the large image view's height constraint
cell.ivTopCon.constant = 8; // ivTopCon is the outlet to the small image view's spacing to the top of the cell
cell.labelTopCon.constant = 8; // labelTopCon is the outlet to thetop label's spacing to the top of the cell
}else{
cell.heightCon.constant = 215; // this number and the following 2 are taken from the values in IB
cell.ivTopCon.constant = 185;
cell.labelTopCon.constant = 233;
}
CGSize labelSize = [self.theData[indexPath.row][#"post"] sizeWithFont:[UIFont systemFontOfSize:17] constrainedToSize:CGSizeMake(152, CGFLOAT_MAX)];
cell.labelHeightCon.constant = labelSize.height;
return cell;
}
Hey #rdelmar thanks for the solution! Eventually I ended up just designing two different cells in the storyboard file with different reuse identifiers but the same subclass. I then checked in the cellForRowAtIndexPath method if the cell had content or not, and assigned the correct identifier. If this is the incorrect way of doing this, or will cause problems down the road please let me no in the comments.

Resources