Im working on a chat app, so the height of the rows varies. I am using separate cells for plain txt msg and msg with image (and maybe text). I allow the user to select an image from the phone. I display that image in a separate VC where he can enter text if he chooses and send it. I have a model for the msg which means I do conversion between base64 and image formats. I have tried to simplify to the max my cell class to understand the following problem: the image inside the image view appears zoomed beyond what the normal phone zoom would allow; and the height of the cell is immense. On the cell class that I need to use I have more items and constraints but the basic logic of interest here is below:
fileprivate func configureMsgsTable() {
tableView.contentInsetAdjustmentBehavior = .automatic
tableView.backgroundColor = .clear
tableView.keyboardDismissMode = .interactive
tableView.separatorStyle = .none
tableView.showsVerticalScrollIndicator = false
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 100
tableView.sectionFooterHeight = 0.0
}
these are the functions I use for encoding/decoding:
fileprivate func convertImageToBase64String (img: UIImage) -> String {
let imageData:NSData = img.jpegData(compressionQuality: 0.50)! as NSData
let imgString = imageData.base64EncodedString(options: Data.Base64EncodingOptions.lineLength64Characters)
return imgString
}
fileprivate func convertBase64StringToImage (imageBase64String:String) -> UIImage {
let imageData = Data.init(base64Encoded: imageBase64String, options: Data.Base64DecodingOptions.ignoreUnknownCharacters)
let image = UIImage(data: imageData!)
return image!
}
and this is the cell class.
class MsgWithImg: UITableViewCell {
//MARK: - Observer.
internal var valuesToDisplay: NewProjectGrpMsgModel! {
didSet {
imgView.image = convertBase64StringToImage(imageBase64String: valuesToDisplay.msgAttachment)
}
}
//MARK: - Properties.
fileprivate let imgView: UIImageView = {
let imgView = UIImageView(frame: .zero)
imgView.clipsToBounds = true
imgView.translatesAutoresizingMaskIntoConstraints = false
imgView.contentMode = .scaleAspectFill
imgView.layer.cornerRadius = 0
return imgView
}()
//MARK: - Init.
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .clear
clipsToBounds = true
selectionStyle = .none
setupViews()
}
required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}
fileprivate func setupViews() {
contentView.addSubview(imgView)
let imgViewConstraints = [
imgView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 3),
imgView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -3),
imgView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
imgView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -45)
]
NSLayoutConstraint.activate(imgViewConstraints)
}
}
I spent some time thinking that this is an auto layout problem; or the fact that the table row height is automatic. that's why I built this test cell class with only the image view. but I think this problem is of a different nature. I did read quite a few answers to what I could find relevant on this website but I cannot determine what the problem is and the console does not print out anything.
The problem is that if you don't give a UIImageView both a width and a height, its intrinsicContentSize becomes the size of the image assigned to it.
With your code as-is, you've given the image view a width by constraining its Leading and Trailing anchors, but you haven't given it a height -- either by itself or by the cell's height (since you want auto-sizing cells).
So, if we use these four images:
The resulting table view looks like this:
And here's what's happening on an iPhone 13 (note: all sizes are rounded)...
For the 100x200 image:
your Leading/Trailing constraints make the image view frame 345-pts wide
no height set, so auto-layout uses the image size (200-pts), setting the image view frame 200-pts tall
image view is set to .scaleAspectFill, so the scaled size is 345 x 690
For the 100x300 image:
your Leading/Trailing constraints make the image view frame 345-pts wide
no height set, so auto-layout uses the image size (300-pts), setting the image view frame 300-pts tall
image view is set to .scaleAspectFill, so the scaled size is 345 x 1035
For the 600x200 image:
your Leading/Trailing constraints make the image view frame 345-pts wide
no height set, so auto-layout uses the image size (200-pts), setting the image view frame 200-pts tall
image view is set to .scaleAspectFill, so the scaled size is 600 x 200
For the 800x600 image:
your Leading/Trailing constraints make the image view frame 345-pts wide
no height set, so auto-layout uses the image size (600-pts), setting the image view frame 600-pts tall
image view is set to .scaleAspectFill, so the scaled size is 800 x 600
It may be clearer if we set the image view to .scaleAspectFit (with a red background so we can see the frame):
As a general rule, it is common to give the image view a fixed size (or proportional size), and use .scaleAspectFit to show the complete images. Or, also common, to use a pre-processor to generate "thumbnail" sized images for the table view cells.
Related
I faced weird issue while handling UICollectionView
I Created simple custom UICollectionViewCell, which has only one imageView and Label:
There's default placeholder image for Cell's imageView and updating imageView.image from collectionView(_:cellForItemAt:). But When image is set, all subview of cell disappears:
(Cells are not disappear at same time because downloading & setting image is async)
Note: Sample data I used is not wrong (Same data works for TableView in same app)
Why this happens and how can I fix it?
this is Sample data I used:
let movies = [
MovieFront(title: "Spider-Man: No Way Home", posterPath: "1g0dhYtq4irTY1GPXvft6k4YLjm.jpg", genre: "Genre", releaseDate: "2021-12-15", ratingScore: 8.4, ratingCount: 3955),
MovieFront(title: "Spider-Man: No Way Home", posterPath: "1g0dhYtq4irTY1GPXvft6k4YLjm.jpg", genre: "Genre", releaseDate: "2021-12-15", ratingScore: 8.4, ratingCount: 3955),
MovieFront(title: "Spider-Man: No Way Home", posterPath: "1g0dhYtq4irTY1GPXvft6k4YLjm.jpg", genre: "Genre", releaseDate: "2021-12-15", ratingScore: 8.4, ratingCount: 3955),
MovieFront(title: "Spider-Man: No Way Home", posterPath: "1g0dhYtq4irTY1GPXvft6k4YLjm.jpg", genre: "Genre", releaseDate: "2021-12-15", ratingScore: 8.4, ratingCount: 3955)
]
this is my part of ViewController:
lazy var collectionView = { () -> UICollectionView in
// FlowLayout
var flowLayout = UICollectionViewFlowLayout()
flowLayout.headerReferenceSize = CGSize(width: self.preferredContentSize.width, height: 180)
flowLayout.sectionInset = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
flowLayout.minimumInteritemSpacing = 20
flowLayout.minimumLineSpacing = 20
// Collection View
var collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: flowLayout)
collectionView.register(DiscoverCollectionViewCell.self, forCellWithReuseIdentifier: identifiers.discover_collection_cell)
collectionView.register(DiscoverCollectionHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: identifiers.discover_collection_header)
collectionView.backgroundColor = UIColor(named: Colors.background)
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Discover"
collectionView.dataSource = self
collectionView.delegate = self
self.view.backgroundColor = UIColor(named: Colors.background)
self.view.addSubview(collectionView)
collectionView.snp.makeConstraints { $0.edges.equalTo(self.view.safeAreaLayoutGuide) }
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// Sample Cell
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifiers.discover_collection_cell, for: indexPath) as? DiscoverCollectionViewCell else { return DiscoverCollectionViewCell() }
let movie = movies[indexPath.row]
cell.movieTitle.text = movie.title
DispatchQueue.global().async {
guard let imageURL = URL(string: "https://image.tmdb.org/t/p/original/\(movie.posterPath)") else { return }
guard let imageData = try? Data(contentsOf: imageURL) else { return }
DispatchQueue.main.sync {
cell.posterImage.image = UIImage(data: imageData)
}
}
return cell
}
and this is my custom CollectionViewCell, I used Snapkit, Then library:
class DiscoverCollectionViewCell: UICollectionViewCell {
//MARK: Create properties
lazy var posterImage = UIImageView().then {
$0.image = UIImage(named: "img_placeholder")
$0.contentMode = .scaleAspectFit
}
lazy var movieTitle = UILabel().then {
$0.font = UIFont.systemFont(ofSize: 15)
$0.textColor = .white
$0.numberOfLines = 2
$0.minimumScaleFactor = 10
}
override init(frame: CGRect) {
super.init(frame: frame)
// add to view
self.addSubview(posterImage)
self.addSubview(movieTitle)
//MARK: Add Constraints
posterImage.snp.makeConstraints { make in
make.top.left.right.equalToSuperview()
}
movieTitle.snp.makeConstraints { make in
make.top.equalTo(posterImage.snp.bottom).offset(5)
make.bottom.greaterThanOrEqualToSuperview()
make.leading.equalTo(posterImage.snp.leading)
make.trailing.equalTo(posterImage.snp.trailing)
}
self.backgroundColor = .blue
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Two issues with your cell's layout...
// add to view
self.addSubview(posterImage)
self.addSubview(movieTitle)
//MARK: Add Constraints
posterImage.snp.makeConstraints { make in
make.top.left.right.equalToSuperview()
}
You should always add UI elements to the cell's .contentView, not to the cell itself.
You did not constrain the bottom of the image view.
// add to ContentView!
self.contentView.addSubview(posterImage)
self.contentView.addSubview(movieTitle)
//MARK: Add Constraints
posterImage.snp.makeConstraints { make in
make.top.left.right.bottom.equalToSuperview()
}
Edit
You were missing a couple things from your post (including how you're setting your cell / item size), so while the above changes do fix the image not showing at all, it's not quite what you're going for.
I'm assuming you're setting the flow layout .itemSize somewhere, so your original constraints - without adding .bottom. to the image view constraints - were close...
When you add an image to a UIImageView, the intrinsicContentSize becomes the size of the image. Your constraints are controlling the width, but...
This constraint on your label:
make.bottom.greaterThanOrEqualToSuperview()
means "put the Bottom of the label at the Bottom of its superview or farther down!"
When your image loads, it sets the image view Height to its own Height and pushes the label way down past the bottom of the cell.
That line needs to be:
make.bottom.equalToSuperview()
That will prevent the Bottom of the label from moving.
Next, you need to tell auto-layout "don't compress or stretch the label vertically":
// prevent label from stretching vertically
movieTitle.setContentHuggingPriority(.required, for: .vertical)
// prevent label from compressing vertically
movieTitle.setContentCompressionResistancePriority(.required, for: .vertical)
Without that, the label will be compressed down to Zero height.
I find it very helpful to add comments so I know what I'm expecting to happen:
override init(frame: CGRect) {
super.init(frame: frame)
// add to ContentView
self.contentView.addSubview(posterImage)
self.contentView.addSubview(movieTitle)
//MARK: Add Constraints
posterImage.snp.makeConstraints { make in
// constrain image view to
// Top / Left / Right of contentView
make.top.left.right.equalToSuperview()
}
// prevent label from stretching vertically
movieTitle.setContentHuggingPriority(.required, for: .vertical)
// prevent label from compressing vertically
movieTitle.setContentCompressionResistancePriority(.required, for: .vertical)
movieTitle.snp.makeConstraints { make in
// constrain Top of label to Bottom of image view
// because we've set Hugging and Compression Resistance on the label,
// this will "pull down" the bottom of the image view
make.top.equalTo(posterImage.snp.bottom).offset(5)
// constrain Bottom of label to Bottom of contentView
// must be EQUAL TO
//make.bottom.greaterThanOrEqualToSuperview()
make.bottom.equalToSuperview()
// Leading / Trailing equal to image view
make.leading.equalTo(posterImage.snp.leading)
make.trailing.equalTo(posterImage.snp.trailing)
}
self.backgroundColor = .blue
}
Now we get this result:
and after the images download:
One final thing - although you may have already done something to address this...
As you see in those screenshots, setting .numberOfLines = 2 on a label does not force a 2-line height... it only limits it to 2 lines. If a Movie Title is short, the label height will be shorter as seen in the 2nd cell.
One way to fix that would be to constrain the label height to something like 2.5 lines by adding this to your init:
if let font = movieTitle.font {
movieTitle.snp.makeConstraints { make in
make.height.equalTo(font.lineHeight * 2.5)
}
}
That will give this output:
Although I am not sure, Because collection view cells are being reused the init of cells only gets called at the first time, not the time when image data is getting loaded from the server.
Try moving your layout-related code(specifically adding subviews and constraining them) in a different method of the cell and call it every time image gets loaded.
I have the following horizontal UIStackView
Yellow background
Leading, top and trailing same as safe area
Alignment = Top
Distribution = Fill Proportionally
UILabel as children
If I add UILabel as its children, it will automatically wrap height content as below
UIImage as children
I replace UILabel with 3 identical UIImageView.
Every UIImageView has the following properties
Content mode = Aspect Fit
It looks as following
I was wondering, how can I make the horizontal UIStackView wrap to its height content?
I expect the UIStackView's will wrap the UIImageView's content height. As an outcome, we will not observe any yellow background of UIStackView.
May I know, how can I achieve so?
So far, I have tried to play around with
Content hugging priority of both UIStackView and UIImageView
Content compression resistance priority for both UIStackView and UIImageView
Still not able to achieve my desired outcome.
The following is the original image source, which is 606x404
You cannot get this result with only Storyboard setup -- you will need some code.
Based on your image showing your desired output, you want each "row" to:
have 3 images
show them at original aspect-ratio
small spacing between images (such as 4-pts)
fit so the widths and heights maintain the ratios
First comment - forget Distribution = Fill Proportionally on the stack views.
For this layout, the stack view should be:
Axis: Horizontal
Alignment: Fill
Distribution: Fill
Spacing: 4
So, what we need to do is constrain each imageView's aspect ratio based on its image's aspect ratio (height / width).
Here is a full example... it uses these 6 images:
We'll also embed the horizontal stack view(s) in a vertical stack view, using the same settings (expect Axis: Vertical).
class AdjustStackViewController: UIViewController {
var vStack = UIStackView()
var picNames: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// six images, named "p1" - "p6"
for i in 1...6 {
picNames.append("p\(i)")
}
// vertical stack view
vStack.axis = .vertical
vStack.spacing = 4
// use auto-layout
vStack.translatesAutoresizingMaskIntoConstraints = false
// add it to the view
view.addSubview(vStack)
// respect safe-area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain vStack
// Top + 20
// Leading and Trailing 0
vStack.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
vStack.leadingAnchor.constraint(equalTo: g.leadingAnchor),
vStack.trailingAnchor.constraint(equalTo: g.trailingAnchor),
])
fillStacks()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// re-fill images in new (shuffled) order
fillStacks()
}
func fillStacks() -> Void {
// remove existing horizontal stack views (if needed)
vStack.arrangedSubviews.forEach { v in
v.removeFromSuperview()
}
let shuffledImages = picNames.shuffled()
// two horizontal stack views each with 3 images
var picNum: Int = 0
for _ in 1...2 {
let hStack = UIStackView()
hStack.spacing = 4
vStack.addArrangedSubview(hStack)
for _ in 1...3 {
// make sure we can load the images
guard let img = UIImage(named: shuffledImages[picNum]) else {
fatalError("Could not load images!")
}
// create Image View
let imgView = UIImageView()
// set the image
imgView.image = img
// proportional constraint based on image dimensions
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor, multiplier: img.size.height / img.size.width).isActive = true
// add to hStack
hStack.addArrangedSubview(imgView)
// increment pic number
picNum += 1
}
}
}
}
Here's how it looks:
When you run this, each time you tap the view it will shuffle the order of the images, so you can see how the layout adapts.
I'm trying to make a custom tableView cell that has a stackView from an array images.
I used interface builder and added a horizontal stackView with:
Center X
Center Y
Top +12
Bottom +12
I'm using a loop to add Arranged Subview and it works fine. But the images are super small, even though they shouldn't be due to their CGRect. But they are getting adjusted by the stackView.
The idea was to keep the stack centered, while also using the top/bottom to adjust the hight of the cell.
I've reasoned that the issue I'm facing with the images being small is because the stackView is initialized empty. So the stackViews hight is very tiny. And because imageViews are being added inside of it, they are then being resized to fit.
How can you get a cell/stackView to adjust their hight after being displayed?
Note: I've also been having issues with trying to add a top & bottom constraint to the imageView programmatically.
for pictogram in pictograms {
let imageView = UIImageView()
let size = self.bounds.width / CGFloat(pictograms.count + 1)
print("size: ", size)
imageView.frame = CGRect(x: 0, y: 0, width: size, height: size)
imageView.contentMode = .scaleAspectFit
imageView.image = pictogram.image
pictogramStack.addArrangedSubview(imageView)
}
You are using a UIStackView in the wrong way. Stack views will arrange the subviews for you - no need to be calculating widths and setting frames.
Layout your cell prototype like this:
Note that the Bottom constraint has Priority: 999. Auto-layout needs to make multiple "passes" to lay out stack views (particularly in table view cells). Using a priority of 999 for the bottom constraint will avoid Constraint Conflict error / warning messages.
Set the stack view properties like this:
With Distribution: Fill Equally we don't have to do any let size = self.bounds.width / CGFloat(pictograms.count + 1) kind of calculations.
Also, to make design-time a little easier, give the stack view a Placeholder intrinsic height:
That will have no effect at run-time, but allows you to clearly see your cell elements at design time.
Now, when you "fill" the stack view with image views, no .frame = setting, and the only constraint you need to add is Height == Width:
NSLayoutConstraint.activate([
// image view should have 1:1 ratio
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
])
Here is a complete example:
class HorizontalImagesStackCell: UITableViewCell {
#IBOutlet var theStack: UIStackView!
func addImages(_ pictograms: [String]) -> Void {
// cells are reused, so remove any previously added image views
theStack.arrangedSubviews.forEach {
$0.removeFromSuperview()
}
pictograms.forEach { s in
// make sure we can load the image
if let img = UIImage(named: s) {
// instantiate an image view
let imageView = UIImageView()
// give it a background color so we can see its frame
imageView.backgroundColor = .green
// scale aspect fit
imageView.contentMode = .scaleAspectFit
// set the image
imageView.image = img
NSLayoutConstraint.activate([
// image view should have 1:1 ratio
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
])
// add it to the stack
theStack.addArrangedSubview(imageView)
}
}
}
}
class ImagesInStackTableViewController: UITableViewController {
// we'll display 5 rows of images
// going from 5 images to 4 images ... to 1 image
let myData: [Int] = [5, 4, 3, 2, 1]
let myImages: [String] = [
"img1", "img2", "img3", "img4", "img5"
]
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HCell", for: indexPath) as! HorizontalImagesStackCell
// get the first n number of images
let a: [String] = Array(myImages.prefix(myData[indexPath.row]))
cell.addImages(a)
return cell
}
}
Using these 5 images:
We get this result (image views are set to .scaleAspectFit and have green backgrounds so we can see the frames):
ModernBoldButton is a subclass of UIButton, here is a snippet of it:
private func commonInit() {
insertSubview(blurView, at: 0)
if let imageView = imageView {
bringSubviewToFront(imageView)
}
if let titleLabel = titleLabel {
bringSubviewToFront(titleLabel)
}
backgroundColor = .clear
clipsToBounds = true
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.width / 2
}
I have four UIButton's embedeed in stack view and as you can see on the screenshot, all of the buttons have incorrect shapes, they should look like a circle.
I suspect that I should set the cornerRadius somewhere else in my code, but where?
In order for them to look like a circle, your bounds must be a square. It appears that this is not the case for your buttons (the width is larger than the height).
You could add some constraints to your buttons in order to maintain a 1/1 ratio.
Other than that, you're setting it in the right place.
Rounding to with/2 will completely rounded top and bottom sides (eye shape 👁)
Rounding to height/2 will completely rounded left and right sides (like this ())
So if you want a circle, you need to make sure both width and height sizes are the same like a square.
In order to doo it automatically, you can use autolayout and the stackView will take care of the sizing:
self.translatesAutoresizingMaskIntoConstraints = false
self.heightAnchor.constraint(equalTo: self.widthAnchor).isActive = true
Make sure to do it just once to avoid duplications.
I am trying to get a custom photo container with multiple UIImageViews to fit in my tableview cell. The view contains a variable number of images (1 ~ 9), and its height would change correspondingly from 1x to 3x imageHeight.
I used AutoLayout to define the top/bottom/leading/trailing margins with the tableview and the custom UIView inside, and to enable self-sizing cells, I have set
tableView.estimatedRowHeight = X
tableView.rowHeight = UITableViewAutomaticDimension
I initialize these cells with
tableView.register(nib: forCellReuseIdentifier:)
and in tableView(_ tableView: cellForRowAt:) method, I setup the cell with:
let cell = tableView.dequeueReusableCell(
withIdentifier: "test9cell",
for: indexPath) as! SocialFeedTableViewCell
cell.photoContainer.setup(with: urls)
cell.photoContainer.loadImages()
return cell
where setup() hooks each imageView in the container with a URL
func setup(with urls: [URL]) {
self.imageUrls = urls
for i in 0 ..< urls.count {
let imageView = UIImageView(frame: CGRect.zero)
self.addSubview(imageView)
self.imageViews.append(imageView)
}
self.setNeedsLayout()
}
func loadImages() {
self.imageViews.forEach { imageView in
imageView.frame = // Calculate position for each subview
imageView.sd_setImage(...) // Load web image asynchronously
}
}
Defining intrinsicContentSize for the view:
override var intrinsicContentSize {
let frameWidth = self.frame.size.width
var frameHeight: CGFloat
switch self.imageUrls.count { // range from 1...9
case 1...3:
frameHeight = frameWidth / 3
case 4...6:
frameHeight = frameWidth / 3 * 2
default:
frameHeight = frameWidth
return CGSize(frameWidth, frameHeight)
}
override func layoutSubviews() {
super.layoutSubviews()
self.imageViews.forEach { imageView in
imageView.frame = // Calculate position for each subview
}
}
The problem here is: after I set the initial intrinsicContentSize, the container's frame size changes in layoutSubviews() afterwards. Although by then I can position the imageView subviews correctly, the cell height will not be changed anymore.
Hope I am not making this problem more confusing. Could someone point out how would I resize the cell height AFTER modifying the contents of its UIView subview? Thanks!
There are a few things here that will be causing you some issues with dynamically sized cells.
You are adding multiple items to the cell but are not defining any auto layout constraints on the image views, so it does not know how to properly place / stack the items.
from the code above you are adding UIImageView with a frame of CGRectZero, without autulayout rules they will either stay zero or try to adjust to the contentsize when you add an image, but they wont adjust the cells height.
If you are loading images from the network they will likely be added/rendered after the tableviewcell has done its initial rendering. So you will likely need to cache the loaded images and reload the cell so that they can load in at the right time.
Now this last point is alot more complicated.
Dynamic UITableViewCell's calculate their height based on the autolayout rules of the content within them. You MUST have enough constraints from your content to the UITableViewCell's contentView property (to all edges) that give the cell enough information to place each item, calculate its overall height and width and therefore it is able to calculate the new height.
Using just one image isn't too bad for dynamic sized cells. but placing multiple items dynamically without any rules after the cell has initially rendered will not work.
You need to decide how these images should be laid out in your cell. once you have this you can look at adding the required constraints as you add the image views. Personally I would only add the image views once i have received each image.
Previously when I have done this I have cached the images once received and re-loaded the cell so that the image can be placed in the cell as it is rendered, allowing the tableview cell to calculate its height based off the image dimensions and constraints.
You may want to also consider using a collection view or merging the images together into a single image and using that in your cell. You could do this on device at runtime or server side if you have that kind of access to the images.