I'm trying to create dynamically sized UITableViewCells, changing the height based on the aspect ratio of an image downloaded from a server.
For example, if an image's height is double its width, I want the UITableViewCell's height to be double the screen width so that the image can take up the full width of the screen and maintain the aspect ratio.
What I've tried to do is add constraints to the cell and use UITableViewAutomaticDimension to calculate the height, but the problem I'm facing is that I cannot know the aspect ratio of the image until it is downloaded, and therefore the cells start off small and then once the tableView is refreshed manually the cell appears with the right size.
I don't feel like reloading each individual cell when it's image is downloaded is a great way to do things either.
Is this approach the best way to do it? I can't for the life of me think how else to do this, as I can't know the aspect ratio from within the cell itself when it's being initialized.
For achieve this I use first a dictionary [Int:CGFloat] to keep the calculated heigths of cells then in the heightForRowAtIndexpath method use the values keeped in your dictionary, in your cellForRowAtIndexpath method you should download your image, calculate the aspect ratio, multiply your cell width or your image width by your aspect ratio and put the height calculated in correspondent IndexPath number in your dictionary
In code something like this, this is an example of code using alamofire to load the images
var rowHeights:[Int:CGFloat] = [:] //declaration of Dictionary
//My heightForRowAtIndexPath method
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = self.rowHeights[indexPath.row]{
return height
}else{
return defaultHeight
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "ImageCell") as? ImageCell
{
let urlImage = Foundation.URL(string: "http://imageurl")
cell.articleImage.af_setImage(withURL: urlImage, placeholderImage: self.placeholderImage, filter: nil, imageTransition: .crossDissolve(0.3), completion: { (response) in
if let image = response.result.value{
DispatchQueue.main.async {
let aspectRatio = (image! as UIImage).size.height/(image! as UIImage).size.width
cell.articleImage.image = image
let imageHeight = self.view.frame.width*aspectRatio
tableView.beginUpdates()
self.rowHeights[indexPath.row] = imageHeight
tableView.endUpdates()
}
}
})
}
I hope this helps
Related
What auto layout constraints need to be set so that an image inside a tableview cell expands to the width borders of the cell as in Twitter app?
I can not get it done and wonder which constraints and settings are necessary.
You should set Leading, Trailing, Top and Bottom constraints. Thus width will be resized according to the screen width and height will be set depending on the aspect ratio of the image, since your cell has dynamic height.
I just focused on the width constraints (leading & trailing), but you can also adjust the other constraints in a similar fashion for top & bottom.
Additionally, you may want to alter your image view to support this, but that will depend upon the image you use. I selected Aspect Fill to make sure it used the entire width. If your image is not properly sized though, this setting will crop the top & bottom of it.
Notice in the Add New Constraints window that the left & right boxes have a solid red line, which indicates they were selected.
You can adjust the imageView as needed to provide some padding if you want, such as was seen in your linked image as well. The method for setting constraints would still be the same though, just the values in the boxes would differ.
First, you should know that UIImageView in cell is not work as you think when you set the cell's height with 'UITableViewAutomaticDimension'.
If you set the 'height constraint' and change it's value will work.
So, set the 'leading', 'trailing', 'top', 'bottom' and 'height' constraints with your image view
And if your image downloaded, get it's size and do the math and set the 'height constraint' with it.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat
{
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "list")
if let iv = cell?.contentView.viewWithTag(1) as? UIImageView
{
// this UIImageView.image(url: URL, placeHolder: UIImage, complete: (UIImage)->()) is my custom function.
// you can use SDWebImage's function
iv.image(url: URL(string: "http://imageurl")!, placeHolder: nil, complete: { (img) in
// calculate imageview's height
let ivHeight = (iv.frame.size.width * img.size.height) / img.size.width
// get height constraint and set the value.
for lc in iv.constraints
{
if lc.firstAttribute == .height
{
// this will change cell's height automatically
lc.constant = ivHeight
break
}
}
})
}
return cell!
}
Sorry about my bad english. :(
So I have been trying to adjust the size of my tableView Cells according to the size of an image that I get from an API call that I make.
http://i.imgur.com/yUmISx1.png
The images that I get are in a 5:4 ratio.
What i've done in the screenshot is put a fixed height under
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let viewWidth = self.view.frame.size.width
let imageHeight = (viewWidth * 5) / 4
return imageHeight
}
(I've ignored small things like cell Paddings and stuff)
Even though the image doesn't look too bad, i'm not happy with what i've done. I have also tried
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if image != nil {
return image.size.height
} else {
return DEFAULT_HEIGHT
}
}
Which gives me a cell size where the image's height is being stretched. What is the correct way to do what i'm trying to achieve?
EDIT: I wanted to find a way so that the image automatically adjusts itself.
For example,
Right now, the images that I receive from the backend are in the ratio 4:5.
Hypothetical situation if they change it to a 1:1 ratio, I would want it to adjust itself. Any suggestion?
Also, my storyboard is going bonkers when i add the aspect ratio.
TIA!
Why don't you use Autolayout for your cell, put the ImageView inside the cell and give it trailing and leading constraints of 0(or your margin) and an aspect ratio constraint of 5:4?
I have a TableView that retrieves images from Parse. The image aspect ratios vary. I have it set up so that the width of the image is changed to match the width of the users device and the height will adjust to match the aspect ratio.
My problem is, whenever I run the application, the first three cells run great, but the fourth cell just takes the height from the first cell, so the image has a letter box effect. Same with the fifth and sixth cell and so on (fifth takes height of 2nd, 6th takes height of 3rd, seventh takes height of 4th which has the height of the 1st, and so on).
What would be the best way to fix this?
If you need to see more code than this, please let me know. I wasn't sure if anything else was relevant.
TableViewController
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! HomeScreenTableViewCell
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
//image retrieved from Parse
imageFiles[indexPath.row].getDataInBackgroundWithBlock { (data, error) -> Void in
if let downloadedImage = UIImage(data: data!)
{
cell.postedImage.image = downloadedImage
}
}
cell.postId = postId[indexPath.row]
cell.username.text = usernames[indexPath.row]
cell.delegate = self
return cell
}
Are you implementing the heightForRowAtIndexPath method? Since you have variable heights, you have to also implement that method so whenever a new cell is created or reused, the correct height is set.
You can store all heights in an array or just get it from the array images that Parse is returning.
It'll be something like this in Objective C but I'm pretty sure you can do it on Swift too.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return images[indexPath.row].height;
}
Hope this helps.
if you want to maintain the aspect ration of the UIImageView you have to implement the heightForRow function in UITableViewDelegate like this
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
//imageView original width is 320
//imageView original height is 200
let screenWidth = UIScreen.mainScreen().bounds.width
return (screenWidth * 200) / 320
}
I am populating a UITableView with images selected by the user. I'd like to the have thumbnails in the table cells all be the same size without affecting the aspect ratio so as not to stretch/skew the images, which sounds to me like ScaleAspectFill, however none of the UIViewContentMode selections seem to have an effect. There me conflicting methods, notable heightForRowAtIndexPath, but removing this makes my cells too small. The following are my didSelectRowAtIndexPath and heightForRowAtIndexPath methods, along with a screen shot from the simulator of my current code (using simulator stock images). Any help is appreciated.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//sets cell based on meme in array
let cell = tableView.dequeueReusableCellWithIdentifier("memeCell") as! UITableViewCell
let meme = self.memes[indexPath.row]
// Set the name and image
cell.textLabel?.text = meme.topText + " " + meme.bottomText
cell.imageView?.frame = CGRectMake(0, 0, 200, 100)
cell.imageView?.contentMode = UIViewContentMode.ScaleToFill //ScaleAspectFill is best, ScaleToFill used to exagerate that its not working
//cell.imageView?.backgroundColor = UIColor.grayColor() //legacy code, will be removed at commit
cell.imageView?.image = meme.origImage
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
//cell row won't be large enough unless this method is called
return 75.0
}
You can add a subclass of UITableViewCell, then overrides layoutSubviews method:
override func layoutSubviews() {
super.layoutSubviews()
self.imageView.frame = CGRect(0,0,200,100)
}
Check out https://github.com/mattgemmell/MGImageUtilities
You can use the crop function in UIImage+ProportionalFill, that scales the image proportionally to completely fill the required size, cropping towards its center, to crop all images to the same size before assigning the image.
You can use it like this:
cell.imageView?.image = meme.origImage. imageCroppedToFitSize(CGSize(width: 200, height: 100))
I have a UITableViewCell with an UIImageView inside it. What constraints should I add so that the cell height dynamically changes according to the image height. The images in the ImageView of varying heights, so I don't want to fix the height of UIImageView
There is a delegate to define the height of each row. I don't know if there is another solution which is faster.
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
//get UIImage of the image for this row
//and return image height
if let img = UIImage(contentsOfFile: "") {
return img.size.height
} else {
return DEFAULT_HEIGHT
}
}
If you have to download from internet, you should define a default_heigth for that delegate call-back. Then, when the image is downloaded, you call reloadTable on the associated index path with new height from downloaded image