Make tableview cell height dynamically after download image from server - ios

I have one cell in which i show images download from server now. Right now i show image with static height but now i want to make dynamic like facebook and instagram. For example if i upload image 320 * 100 then my cell need to become 300*100. and also suggest server side changes if required
I am using afnetworking image loading classes.
Here is my cell design .
EDIT:
I tried given solution but now issue is that cell resize with jerk when second time it's come in cellforindexpath method. This will happen in first cell only.

I have done similar task, showing the images on the table and resize the tableview cell so that the image is shown along the fullscreen width
Height For Row At IndexPath
var cachedHeight = [IndexPath : CGFloat]()
override func tableView(_ tableView: UITableView, heightForRowAtindexPath: IndexPath) -> CGFloat {
let default_height : CGFloat = 332.0
// lookup for cached height
if let height = cachedHeight[indexPath]{
return height
}
// no cached height found
// so now try for image so that cached can be calculated and cached
let cache : SDImageCache = SDImageCache.shared()
let image : UIImage? = cache.imageFromDiskCache(forKey: self.viewModel?.getProductImage(of: indexPath.row, at: 0))
if let image = image{
let baseHeight : CGFloat = CGFloat(332 - 224) // cell height - image view height
let imageWidth = self.tableView.frame.size.width
let imageHeight = image.size.height * imageWidth / image.size.width
cachedHeight[indexPath] = imageHeight + baseHeight
return cachedHeight[indexPath]!
}
//
// even if the image is not found, then
// return the default height
//
return default_height
}
Cell For Row At IndexPath
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sdCache = SDImageCache.shared()
let cell = tableView.dequeueReusableCell(withIdentifier: "MyXYZCell", for: indexPath) as! AuctionListTableViewCell
let imageURL = self.viewModel?.getProductImage(of: indexPath.row, at: 0)
if let imageURL = imageURL {
if (sdCache.imageFromCache(forKey: imageURL) != nil) {
//
// image data already persist on disk,
// cell height update required
//
cell.auctionImageView.sd_setImage(with: URL.init(string: imageURL), completed: nil)
}
else
{
cell.auctionImageView.sd_setImage(with: URL.init(string: imageURL), completed: { (image, err, cacheType, url) in
self.tableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.none)
})
}
}
//other cell customization code
return cell
}
I have used SDWebImage. You can use it, or find similar api in AFNetworking.
Keynotes:
Here cachedHeight is used to cache the height of the cell indexed by the IndexPath, because reading the image from the disk is quiet I/O exhaustive task, which results lag in the table view scroll.
In heightForRow i checked that, is the image is in cache, if in cache, then calculate the height, store it into the cachedHeight, otherwise return the default height. (My default height is calculated for my placeholder image)
In the cellForRowAtIndexPath i have checked is the image is in cache. If the image is in cache, then no reload is required as the height is already calculated. Otherwise i attempt a network fetch, when the fetch completed i request to tableview to reload that cell.
Hope it helps, Happy coding.

I have solved this problem.
If you have to make imageview height and width dynamic then ask server to send image width and Height in API response.
Once you get both, according to image width and screen width calculate the image height.
Take constraint of image height. And assign calculated height to image height constraint.
To calculate image height use below formula:-
imageHeight = (screen_width * actual_image_height) / actual_image_width

Related

UITableView dynamic cell heights only correct after some scrolling when resizing cell according to uiimage view

As you can see in the 1st Image . No text visible in UITableviewCell.
After I scroll it appears and resize the cell. I want to resize cell according to UIImage. I am fetching image  from API using SDWebImage.
I am putting code I have done in UITableView Cell for row method .
cell.imgViewSlide.sd_setImage(with: URL(string:
self.arrSliderImage[indexPath.row]), placeholderImage: UIImage(named: "HomeSliderImage"), options: .continueInBackground) { (image, error, type, url) in
let screenBounds = UIScreen.main.bounds
let pixelWidth = Int((image?.size.width)!)
let pixelHeight = Int((image?.size.height)!)
let height = ((pixelHeight * Int(screenBounds.width)) / pixelWidth)
cell.heightImage.constant = CGFloat(integerLiteral: height)
}
I have applied plenty of solution but not working correctly.
First of all, as you are fetching the Images from dynamic URL using SDWebImage, Initially you need to set a static height for the image of your tableView Cell in the storyboard.
Define a constraint Outlet for this image height in your cell, and then you should define the height of the image according to the aspect Ratio.
cell.imgViewSlideHeight.constant = CGFloat((aspectRatio) * Double(self.frame.width))
Here self is the UITableViewCell reference and imgViewSlideHeight is the cell Image Height Constraint.

Swift - Difficulty with different sized images in TableViewCell

I am loading a number of remote images with Kingfisher and having significant difficulty getting them to load correctly into a Tableview with cells of dynamic heights. My goal is to have the images always be the full width of the screen and of a dynamic height, how can this be achieved?
I asked a related question previously which led to understanding the basic layout using a stack view: SnapKit: How to set layout constraints for items in a TableViewCell programatically
So I've built something like the following:
With the following code (some parts removed for brevity):
// CREATE VIEWS
let containerStack = UIStackView()
let header = UIView()
let headerStack = UIStackView()
let title = UILabel()
let author = UILabel()
var previewImage = UIImageView()
...
// KINGFISHER
let url = URL(string: article.imageUrl)
previewImage.kf.indicatorType = .activity
previewImage.kf.setImage(
with: url,
options: [
.transition(.fade(0.2)),
.scaleFactor(UIScreen.main.scale),
.cacheOriginalImage
]) { result in
switch result {
case .success(_):
self.setNeedsLayout()
UIView.performWithoutAnimation {
self.tableView()?.beginUpdates()
self.tableView()?.endUpdates()
}
case .failure(let error):
print(error)
}
}
...
// LAYOUT
containerStack.axis = .vertical
headerStack.axis = .vertical
headerStack.spacing = 6
headerStack.addArrangedSubview(title)
headerStack.addArrangedSubview(author)
header.addSubview(headerStack)
containerStack.addArrangedSubview(header)
containerStack.addSubview(previewImage)
addSubview(containerStack)
headerStack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(20)
}
containerStack.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
Without a constraint for imageView, the image does not appear.
With the following constraint, the image does not appear either:
previewImage.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
make.top.equalTo(headerView.snp.bottom).offset(20)
}
With other attempts, the image is completely skewed or overlaps the labels/other cells and images.
Finally, following this comment: With Auto Layout, how do I make a UIImageView's size dynamic depending on the image? and this gist: https://gist.github.com/marcc-orange/e309d86275e301466d1eecc8e400ad00 and with these constraints make.edges.equalToSuperview() I am able to get the images to display at their correct scales, but they completely cover the labels.
Ideally it would look something like this:
100 % working solution with Sample Code
I just managed to acheive the same layout with dynamic label contents and dynamic image dimensions. I did it through constraints and Autolayout. Take a look at the demo project at this GitHub Repository
As matt pointed out, we have to calculate the height of each cell after image is downloaded (when we know its width and height). Note that the height of each cell is calculated by tableView's delegate method heightForRowAt IndexPath
So after each image is downloaded, save the image in array at this indexPath and reload that indexPath so height is calculated again, based on image dimensions.
Some key points to note are as follows
Use 3 types of cells. One for label, one for subtitle and one for Image. Inside cellForRowAt initialize and return the appropriate
cell. Each cell has a unique cellIdentifier but class is same
number of sections in tableView == count of data source
number of rows in section == 3
First row corresponds to title, second row corresponds to subtitle and the 3rd corresponds to the image.
number of lines for labels should be 0 so that height should be calculated based on content
Inside cellForRowAt download the image asynchrounously, store it in array and reload that row.
By reloading the row, heightForRowAt gets called, calculates the required cell height based on image dimensions and returns the height.
So each cell's height is calculated dynamically based on image dimensions
Take a look at Some code
override func numberOfSections(in tableView: UITableView) -> Int {
return arrayListItems.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//Title, SubTitle, and Image
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
//configure and return Title Cell. See code in Github Repo
case 1:
//configure and return SubTitle Cell. See code in Github Repo
case 2:
let cellImage = tableView.dequeueReusableCell(withIdentifier: cellIdentifierImage) as! TableViewCell
let item = arrayListItems[indexPath.section]
//if we already have the image, just show
if let image = arrayListItems[indexPath.section].image {
cellImage.imageViewPicture.image = image
}else {
if let url = URL.init(string: item.imageUrlStr) {
cellImage.imageViewPicture.kf.setImage(with: url) { [weak self] result in
guard let strongSelf = self else { return } //arc
switch result {
case .success(let value):
print("=====Image Size \(value.image.size)" )
//store image in array so that `heightForRowAt` can use image width and height to calculate cell height
strongSelf.arrayListItems[indexPath.section].image = value.image
DispatchQueue.main.async {
//reload this row so that `heightForRowAt` runs again and calculates height of cell based on image height
self?.tableView.reloadRows(at: [indexPath], with: .automatic)
}
case .failure(let error):
print(error) // The error happens
}
}
}
}
return cellImage
default:
print("this should not be called")
}
//this should not be executed
return .init()
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
//calculate the height of label cells automatically in each section
if indexPath.row == 0 || indexPath.row == 1 { return UITableView.automaticDimension }
// calculating the height of image for indexPath
else if indexPath.row == 2, let image = arrayListItems[indexPath.section].image {
print("heightForRowAt indexPath : \(indexPath)")
//image
let imageWidth = image.size.width
let imageHeight = image.size.height
guard imageWidth > 0 && imageHeight > 0 else { return UITableView.automaticDimension }
//images always be the full width of the screen
let requiredWidth = tableView.frame.width
let widthRatio = requiredWidth / imageWidth
let requiredHeight = imageHeight * widthRatio
print("returned height \(requiredHeight) at indexPath: \(indexPath)")
return requiredHeight
}
else { return UITableView.automaticDimension }
}
Related.
Another approach that we can follow is return the image dimensions from the API request. If that can be done, it will simplify things a lot. Take a look at this similar question (for collectionView).
Self sizing Collection view cells with async image downloading.
Placholder.com Used for fetching images asynchronously
Self Sizing Cells: (A Good read)
Sample
It’s relatively easy to do what you’re describing: your image view needs a width constraint that is equal to the width of the “screen” (as you put it) and a height constraint that is proportional to the width constraint (multiplier) based on the proportions of the downloaded image (aka “aspect ratio”). This value cannot be set in advance; you need to configure it once you have the downloaded image, as you do not know its proportions until then. So you need an outlet to the height constraint so that you can remove it and replace it with one that has the correct multiplier when you know it. If your other constraints are correct in relation to the top and bottom of the image view, everything else will follow as desired.
These screen shots show that this approach works:
(Scrolling further down the table view:)
It isn’t 100% identical to your desired interface, but the idea is the same. In each cell we have two labels and an image, and the images can have different aspect ratios but those aspect ratios are correctly displayed - and the cells themselves have different heights depending upon that.
This is the key code I used:
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! Cell
// in real life you’d set the labels here too
// in real life you’d be fetching the image from the network...
// ...and probably supplying it asynchronously later
let im = UIImage(named:self.pix[indexPath.row])!
cell.iv.image = im
let con = cell.heightConstraint!
con.isActive = false
let ratio = im.size.width/im.size.height
let newcon = NSLayoutConstraint(item: con.firstItem, attribute: con.firstAttribute, relatedBy: con.relation, toItem: con.secondItem, attribute: con.secondAttribute, multiplier: ratio, constant: 0)
newcon.isActive = true
cell.heightConstraint = newcon
return cell
There's a straight forward solution for your problem if you don't want to change your layout.
1- define your cell
2- put the UIImageView and other UI elements you like inside your cell and add these constraints for the image view:
-top,leading,trailing,bottom to superview
-height constraints and add outlet to your code (for example :heightConstraint)
3-Change the content fill to : aspect fit
4- Load your images via kingfisher or any other way you like, once you pass your image, check the size and calculate the ratio : imageAspectRatio = height/width
5-Set the heightConstraint.constant = screenWidth * imageAspectRatio
6-call layoutIfNeeded() for the cell and you should be ok!
*This solution works with any UI layout composition including stack views, the point is having a constraint on the images and letting the tableview figure it out how to calculate and draw constraints.
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
#IBOutlet weak var sampleImageView: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func configure(image:UIImage) {
let hRatio = image.size.height / image.size.width
let newImageHeight = hRatio * UIScreen.main.bounds.width
heightConstraint.constant = newImageHeight
sampleImageView.image = image
sampleImageView.layoutIfNeeded()
}
}
Result :

DecorationView gets resized when AutoSizing cells are enabled in UICollectionViewFlowLayout

Environment:
UICollectionView that looks like UITableView
Custom UICollectionViewFlowLayout subclass to define the frame of the DecorationView
Self-Sizing cells enabled
Expected behavior:
A DecorationView that should be placed as a background for every section of the UICollectionView
Observed Behavior:
The DecorationView collapses to an arbitrary size:
Seems that UICollectionView tries to calculate an automatic size for the DecorationView. If I disable Self-Sizing cells, the decoration view is being placed exactly at the expected place.
Is there any way to disable Self-Sizing for DecorationView ?
In my UICollectionViewFlowLayout subclass I simply take the first and last cells in the section and stretch the background to fill the space underneath them. The problem is that UICollectionView does not respect the size calculated there:
override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let collectionView = collectionView else {
return nil
}
let section = indexPath.section
let attrs = UICollectionViewLayoutAttributes(forDecorationViewOfKind: backgroundViewClass.reuseIdentifier(),
with: indexPath)
let numberOfItems = collectionView.numberOfItems(inSection: section)
let lastIndex = numberOfItems - 1
guard let firstItemAttributes = layoutAttributesForItem(at: IndexPath(indexes: [section, 0])),
let lastItemAttributes = layoutAttributesForItem(at: IndexPath(indexes: [section, lastIndex])) else {
return nil
}
let startFrame = firstItemAttributes.frame
let endFrame = lastItemAttributes.frame
let origin = startFrame.origin
let size = CGSize(width: startFrame.width,
height: -startFrame.minY + endFrame.maxY)
let frame = CGRect(origin: origin, size: size)
attrs.frame = frame
attrs.zIndex = -1
return attrs
}
It's possible that the frames of your decoration views are not being updated (i.e. invalidated) after the frames of your cells have been self-sized. The result is that the width of each decoration view remains at its default size.
Try implementing this function, which should invalidate the layout of the decoration view for each section every time the layout of an item in that section is invalidated:
override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
let invalidatedSections = context.invalidatedItemIndexPaths?.map { $0.section } ?? []
let decorationIndexPaths = invalidatedSections.map { IndexPath(item: 0, section: $0) }
context.invalidateDecorationElements(ofKind: backgroundViewClass.reuseIdentifier(), at: decorationIndexPaths)
super.invalidateLayout(with: context)
}

How to resize an UIImageview, enclosed within a UITableview's custom cell, depending upon dynamic image URL's height?

I'm having a UIImageview and few other components within a custom UITableviewcell, in a separate xib file, which I'm loading into my UITableview, which is in a UIView subclass and assigning image from URL dynamically.
Now the problem is, I want to resize the UIImageview and the Tableview row's height depending upon the size of image, obtained from the URL, dynamically. I got that working using the following code,
if let bpImageUrls = singleComponent["picture_link"]{
cell.newsImage.sd_setShowActivityIndicatorView(true)
cell.newsImage.sd_setIndicatorStyle(.gray)
cell.newsImage.sd_setImage(with: URL(string: bpImageUrls as! String))
let imageData = NSData(contentsOf:URL(string: bpImageUrls as! String)!)
let myImage = UIImage(data: imageData! as Data)
let aspectRatio = ( myImage?.size.height)! / ( myImage?.size.width)!
cell.imageHeightConstraint.constant = UIScreen.main.bounds.size.width * aspectRatio
}
It works like a charm, but the problem is, this will slow down the scrolling event of the UITableview, when I tried to do download the image asynchronously and update the view in the main thread, it's not working.
The view is tied with autolayout like in this image, I'd been struggling with this for a while now. Any insights will be helpful. Thanks.
When you load the image asynchronously the table view has already finished the layout for the cell. Just changing the cell's height constraint according to the image height does not change the height of the cell in the table view.
You have to make the table view recalculate the height of the cell after changing the height constraint. So when an image is loaded and the height constraint of the cell changed, tell the table view to reload that cell by calling:
if let indexPath = tableView.indexPath(for: cellWithImage) {
tableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
}

Images in UIImageView not showing in UITableView when circular mask is applied to the ImageView unless scroll

Thanks in advance for the help.
I have a UITableView within a main view contoller. Within the prototype cell, I have a UIImageview. In the code below everything works until I add the 5 lines to apply a circular mask and border. Once I do that, the images will not load unless I scroll the cells. The mask and border do get applied perfectly however. Will be great when it works... but until then.
Certainly this has been seen before. I'm a swift/objective-C newbie.
Working in swift for this one.
Code below;
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("mixerCell", forIndexPath: indexPath) as! MixerTableViewCell
// set label background color to clear
cell.textLabel?.backgroundColor = UIColor.clearColor()
// set highlight selection to none
cell.selectionStyle = .None
// set image for cell
let imageView = cell.viewWithTag(1) as! UIImageView
// put circular mask and border. This is the problem code that causes initial load of the tableview images to show up blank.
imageView.layer.cornerRadius = imageView.frame.size.width / 2;
imageView.clipsToBounds = true;
let color1 = UIColor(white: 1.0, alpha: 0.5).CGColor as CGColorRef
imageView.layer.borderWidth = 2;
imageView.layer.borderColor = color1
// assign image
imageView.image = UIImage(named: mixerSounds[indexPath.row])
return cell
}
initial view load
after scroll
your code is perfectly working for me. Here i am using Xcode-7. i think you are using Xcode-6.3 or less version. just upgrade it to Xcode- 7. and if you are using the same then just check your heightforRowAtIndexpath or other delegates there should be some issue.
thanks
Try changing the below lines,
// replace this
let imageView = cell.viewWithTag(1) as! UIImageView
// to
let imageView = cell.yourImageViewName
/* yourImageViewName is the outlet
reference name you have given in the
MixerTableViewCell custom class.
*/
Edit 2: Just for debugging purposes,
hardcode the image name and check if the image appears on the all the cells.
imageView.image = UIImage(named: "first1.png")
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!
{
let cellIdentifier = "cell"
var cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as UITableViewCell
cell.image_View.image = UIImage(named: mixerSounds[indexPath.row])
println("The loaded image: \(image)")
cell.image_View.layer.masksToBounds = false
cell.image_View.layer.borderColor = UIColor.blackColor().CGColor
cell.image_View.layer.cornerRadius = image.frame.height/2
cell.image_View.clipsToBounds = true
return cell
}
Give imageview outlet to cell and not give imageview name because by default name is imageview so take diffrent name
It looks like the problem is using clipToBounds = true I am facing the same issue while making circular UIImageView inside UITableViewCell
I didn't find the exact solution but for now I found a way to do this
if (indexPath.row == indexPath.length && !isTableReloaded)
{
let dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(0.000000001 * Double(NSEC_PER_SEC)))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
self.reloadTableView()
})
}
func reloadTableView()
{
isTableReloaded = true
self.tableViewContacts.reloadData()
}
Here isTableReloaded is a Bool type var which is initialized to false in viewDidLoad()
and the if condition is to be placed at the last of cellForRowAtIndexPath but before return statement
This will resolve our problem but do not rely on this as this is not the best approach.
Please post solution for this if any one found the better approach.
Here is a perfect and state away solution for circular image in UITableview Cell.
Simply modify your UITableviewCell (custom cell) class with below code.
override func awakeFromNib() {
super.awakeFromNib()
imgEvent.layer.frame = (imgEvent.layer.frame).insetBy(dx: 0, dy: 0)
imgEvent.layer.borderColor = UIColor.gray.cgColor
imgEvent.layer.cornerRadius = (imgEvent.frame.height)/2
imgEvent.layer.masksToBounds = false
imgEvent.clipsToBounds = true
imgEvent.layer.borderWidth = 0.5
imgEvent.contentMode = UIViewContentMode.scaleAspectFill
}
It will also helps to solve the problem of image circular only after scrolling table..(if any!)
let width = cell.frame.size.width
cell.img.layer.cornerRadius = width * 0.72 / 2
0.72 is the ratio of the cell width to image width, for eg. cellWidth = 125 and imageWidth = 90, so 125/90 would give 0.72. Do similar calculation for your image.
First: Images doesn't load until you scroll, because when cellForRowAtIndexPath methods called the constraints doesn't set for image until now, so when scrolling the constraints was added and the image appears, so if you set proportional width and height for imageView (width==height) in cell then
do that
let w = tableview.frame.width*(proportional value like 0.2)
imageView.layer.cornerRadius = w / 2
imageView.clipsToBounds = true;

Resources