Im implementing a UITableViewCell
for social media post which includes username, userImage, text, media like image post posted by the user and like, comment buttons etc. Here the media will be optional and if there is any image posted, I will unhide the UIView
contains imageView and adjust the UIImageView
height based on aspect ratio of the image coming from the API response.
Here is my code for the UITableViewCell
class:
class PostsTableViewCell: UITableViewCell {
@IBOutlet weak var ivProfilePic: UIImageView!
@IBOutlet weak var ivPost: UIImageView!
@IBOutlet weak var lblName: UILabel!
@IBOutlet weak var lblPostContent: UILabel!
@IBOutlet weak var viewPost: UIView!
@IBOutlet weak var heighIvPost: NSLayoutConstraint!
var postImage: UIImage? {
didSet {
if let image = postImage {
configureCellWhenPostImageIsAvailable(image: image)
}
viewPost.isHidden = postImage == nil
layoutIfNeeded()
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
viewPost.isHidden = true
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override func prepareForReuse() {
super.prepareForReuse()
viewPost.isHidden = true
heighIvPost.constant = 162 // default height of UIImageView
ivPost.image = nil
}
// To calculate aspect ratio & set heightIvPost constraint value
func configureCellWhenPostImageIsAvailable(image: UIImage) {
let hRatio = image.size.height / image.size.width
let newImageHeight = hRatio * viewPost.bounds.width
heighIvPost.constant = newImageHeight
ivPost.image = image
ivPost.layoutIfNeeded()
}
}
This is my cellForRowAt
function in the main UIViewController:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PostsTableViewCell", for: indexPath) as! PostsTableViewCell
let data = posts[indexPath.row]
if let userImage = data.memberProfilePic {
cell.ivProfilePic.kf.setImage(with: userImage)
}
cell.lblName.text = data.memberName
if let postText = data.postContent {
cell.lblPostContent.isHidden = false
cell.lblPostContent.text = postText
}
if let postImage = data.postImage { // data.postImage contains URL for image and if not nil then unhide viewPost and set Image
cell.viewPost.isHidden = false
cell.ivPost.kf.setImage(with: postImage)
if let image = cell.ivPost.image {
cell.configureCellWhenPostImageIsAvailable(image: image)
cell.layoutIfNeeded()
}
}
return cell
}
Here is my data model just in case:
class PostEntity: NSObject {
var postContent: String?
var postImage: URL?
var memberName: String?
var memberProfilePic: URL?
override init() {
}
init(jsonData: JSON){
postContent = jsonData["postContent"].stringValue
postImage = jsonData["postImages"].url
memberName = jsonData["member_name"].stringValue
memberProfilePic = jsonData["member_profile"].url
}
}
When I run this code, my requirement is if there is any image in post ie.. data.postImage != nil
, it should display image with correct aspect ratio however what I get is:
- When
UITableView
is loaded, the cells that are loaded show images with correct aspect ratio. - When I scroll down, the UIImageView will not show images in correct aspect ratio but default one.
- When I scroll back up, I think because of
prepareForReuse
, it again displays images in correct aspect ratio.
Only problem I face is when o scroll down and new cells are created, it won't show correct aspect ratio if data.postImage != nil
.
Here is the video link for further clarification: https://youtube.com/shorts/vcRb4u_KAVM?feature=share
In the video above you can see, at start all images have perfect aspect ratio but when I scroll down and reach robot and car image, they are of default size i.e. 162, but when I scroll down and scroll back up to them, they get resized to desired results.
I want to remove that behaviour and have correct aspect ratio based on image size.
CodePudding user response:
The problem is...
When a table view calls cellForRowAt
, the row height gets set -- usually, be constraints on the content of the cell. If you change the height after the cell has been displayed, it is up to you to inform the table view that it needs to re-calculate the height of the row.
So, you can add a closure to your cell class like this:
class PostsTableViewCell: UITableViewCell {
// closure
var layoutChange: ((PostsTableViewCell) -> ())?
// the rest of your cell code...
func configureCellWhenPostImageIsAvailable(image: UIImage) {
let hRatio = image.size.height / image.size.width
let newImageHeight = hRatio * viewPost.bounds.width
heighIvPost.constant = newImageHeight
ivPost.image = image
// not needed
//ivPost.layoutIfNeeded()
// use the closure to inform the table view the row height has changed
self.layoutChange?(self)
}
}
then, in your controller's cellForRowAt
:
cell.layoutChange = { [weak self] theCell in
guard let self = self,
let cellIndexPath = tableView.indexPath(for: theCell)
else { return }
// you probably want to update something in your data
// maybe:
var data = self.posts[cellIndexPath.row]
data.profilePicDownloaded = true
self.posts[cellIndexPath.row] = data
// tell the table view to re-cacluclate the row heights
self.tableView.performBatchUpdates(nil, completion: nil)
}
return cell