I am using the default UITableViewCell which comes with UIImageView, titleLabel and also a description Label. I want to have a fixed size for my UIImageView so all the images are of the same size. At present it looks like this:
The row height for the cell is "Automatic" and here are the properties of the UITableView.
How can I make my UIImageView fixed size?
The images are downloaded asynchronously as shown below:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let dish = self.dishes[indexPath.row]
cell.textLabel?.text = dish.title
cell.imageView?.image = UIImage(named: "placeholder")
cell.detailTextLabel?.text = dish.description
cell.imageView?.setImageFor(url: URL(string: dish.imageURL)!) { image in
cell.imageView?.image = image
cell.imageView?.translatesAutoresizingMaskIntoConstraints = false
cell.imageView?.widthAnchor.constraint(equalToConstant: 50)
cell.imageView?.heightAnchor.constraint(equalToConstant: 50)
cell.setNeedsLayout()
}
return cell
}
UIImageView+Additions.swift
func setImageFor(url :URL, completion: #escaping (UIImage) -> ()) {
DispatchQueue.global().async {
let data = try? Data(contentsOf: url)
if let data = data {
let image = UIImage(data: data)
DispatchQueue.main.async {
completion(image!)
}
}
}
}
You are trying to change the height anchor and width anchor of your UIImageView inside your completion block handler. You should not do that, and if you need to, make sure you are updating your layout in the main thread.
Your UIImageView's top, bottom, leading, and trailing anchor might be equal to the superView, in your case, it's the tableViewCell and given that your UITableViewCells have auto height. These result to unequal size of your UIImageView.
Since you are using interface builder, try to layout your UIImageView like so:
As you can see, I have my fixed width and height of my UIImageView.
Lastly, don't forget to set your UIImageView's contentMode and tick clipToBounds to yes.
Related
I have stretched images displayed in TableViewCell. I need to find the height and width of image from image URL. I need to find the aspect ratio and fit the image on the cell according to the height of dynamic cell in Swift 4.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.ManCell) as! ManCell
let image = self.posts[indexPath.row]["thumbnail_images"]["medium_large"]["url"].stringValue
let cleanString = removeHtmlTagsFromResponse(index: indexPath.row)
cell.nutriTitle.text = self.posts[indexPath.row]["title"].stringValue
print("labels: \(self.posts[indexPath.row]["title"].stringValue)")
cell.nutriDetail.text = cleanString
let lCount = calculateHeightOfLable(cellLabel: cell.nutriDetail)
print("string: \(cleanString)")
print("HeightCount: \(lCount)")
cell.nutriImage.sd_setImage(with: URL(string: image), placeholderImage: UIImage(named: "no_image"))
let heightContentSize = cell.bounds.height
print("HeightContentSize= ",heightContentSize)
return cell
}
It looks like you are using SDWebImage to load your images from a URL. In the documentation you will find that there is another method on UIImage called: sd_setImageWithURL:placeholderImage:options:completed: which takes a completion handler with 4 parameters: image: UIImage, error: NSError, isCache: Bool, and url: URl. Use the first parameter to get the size of the image. Note that this is asynchronous; you don't get the size until the image is downloaded (assuming you don't already have it in the cache), so then cell may resize if you are dynamically sizing it.
I need to programmatically add two different types of cells to my UITableView. The content of these cells is not known until runtime. The first type is an HTML formatted string and the second is an image. There can be any combination of these cells and they can occur in any order.
In the IB, I've set up two prototype cells, one with the id 'htmlCell' and the other 'imageCell'. Here is the relevant code:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "htmlCell")
let thisSection = self.sections[indexPath.row]
if thisSection.type! == "html" {
let htmlString = thisSection.text
let htmlHeight = contentHeights[indexPath.row]
let webView:UIWebView = UIWebView(frame: CGRect(x:0, y:0, width:cell.frame.size.width, height:htmlHeight))
cell.addSubview(webView)
webView.tag = indexPath.row
webView.scrollView.isScrollEnabled = false
webView.isUserInteractionEnabled = false
webView.delegate = self
webView.loadHTMLString(htmlString!, baseURL: nil)
} else if thisSection.type! == "image" {
cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "imageCell")
let imageName = "logo"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image)
imageView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width-20, height: 200)
cell.addSubview(imageView)
return cell
}
return cell
}
func webViewDidFinishLoad(_ webView: UIWebView) {
if (contentHeights[webView.tag] != 0.0) {
// we already know height, no need to reload cell
return
}
let strHeight = webView.stringByEvaluatingJavaScript(from: "document.body.scrollHeight")
contentHeights[webView.tag] = CGFloat(Float(strHeight!)!)
tableView.reloadRows(at: [NSIndexPath(item: webView.tag, section: 0) as IndexPath], with: UITableViewRowAnimation.automatic)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return self.contentHeights[indexPath.row]
}
The content loads, but it's a bit of mess. Here is a screenshot to provide an example. In this case, the image should appear between the two webviews. But as you can see, the image lands directly on top of the second webview, with the two top borders aligned.
Also, when I click on the image, it completely disappears. I'm assuming that it goes behind the second webview. Just a guess.
The CGRects that I am using have somewhat arbitrary widths and heights at the moment. I suspect that this is where part of the issue lies. Finally, the webViewDidFinishLoad always returns a height of 667.0 regardless of the true height of the webview content.
Anyone know how I can make these views land in the correct sequence? Thank you.
There are many potential issues here, let's tackle a few of them and see if that get's you going :)
If you defined prototype cells in IB and your tableView is properly linked to the xib or storyboard file, then you should dequeue cells instead of construct them.
Try this:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell //declare but don't assign yet. Use let because after we assign once, we wont be reassigning.
...
if thisSection.type! == "html" {
cell = tableView.dequeueReusableCell(withIdentifier: "htmlCell", for: indexPath)
...
} else if thisSection.type! == "image" {
cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath)
...
}
return cell
}
Notice, we declare and return cell at the top level scope and assign the cell only when we know what type it will be. Now that you are dequeueing, the cells will be reused when appropriate and reduce the number of allocation made when a user scrolls making your app zippier.
Next, since you are adding subviews to your cells, you'll need to clear the subviews each time you dequeue a cell. This requires a bit of additional code but will make things a lot cleaner in the long run.
After each call to dequeueReusableCell... add:
cell.contentView.subviews.forEach({ $0.removeFromSuperview() })
So far so good. You've got the cells dequeuing cleanly, but now we need to make sure we setup the cells properly.
First, the html cell:
Remove the line:
let htmlHeight = contentHeights[indexPath.row]
The tableView is ultimately responsible for sizing the cells and the content will take up the full size of the cell. Replace the next 2 lines with this:
let webView = UIWebView(frame: cell.contentView.bounds)
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
cell.contentView.addSubview(webView)
The autoresizing mask makes sure that if the cell's content view size changes, the webview will be resized as well. Since the content view is managed by iOS, the webview will always be the width of the tableview and the height will be whatever you provide in the delegate method heightForRow. Notice also, we never add subviews directly on to the cell, always on to it's contentView.
Next, we'll do the same for the imageCell:
let image = UIImage(named: "logo")
let imageView = UIImageView(frame: cell.contentView.bounds)
imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
imageView.contentMode = .aspectFit //this makes sure the image take ups the full size of the image view, but no more. and this keeps the image's aspect ratio. Adjust this to other content modes if necessary.
imageView.image = image
cell.contentView.addSubview(imageView)
Ok, now we have cells that are being setup and leaving the sizing out of the equation. So let's get sizing working in the right place:
The biggest challenge that you'll face in building this is getting the cell heights right. Since the images are in your bundle, you'll know their size as soon as you instantiate the UIImage, however we don't want to do this more than absolutely necessary. You might want to come up with a more performant way of doing this later, but for now we'll create a UIImage and get it's height:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if thisSection.type! == "html" {
return self.contentHeights[indexPath.row]
} else if thisSection.type! == "image" {
let image = UIImage(named: "logo")
let imageHeight = (image.size.height * tableView.bounds.size.width) / image.size.width // The proportional size of the image stretched to the table view's width
return imageHeight
}
}
Your method for calculating the web view height and reloading cell might work, but it might appear clunky to the user. My suggestion at this point would be to return a fixed height for the web content. When the user taps a web cell, you can push a new view controller with a web view that can display all the content. Otherwise you can try having all of your html generate web content of roughly the same size.
Ok, this was long but hopefully gets you off to a good start :) I'm happy to follow up if you need more clarification.
I'm going through Stanford's cs193p. Assignment 4 has us create a custom UITableVIewCell and load a picture from the web into a UIImageView inside the cell.
My UIImageView and my Cell have their content mode set to Aspect Fit on the story board.And the ImageView is set on autolayout to be hugging the cell.
And yet when the picture first loads, it will bleed out of the UIImageView. When I click on it, it will correctly aspect fit.
I tried setting the content mode in code just before assigning the image, but that also didn't work. I also tried calling layoutSubviews() and setNeedsLayout right after assigning the image, and while that helps by actually showing the image (as opposed to showing nothing until the user clicks the cell), it still shows in the wrong size until the user clicks it.
This is the code for the cell:
import UIKit
class ImageTableViewCell: UITableViewCell {
#IBOutlet weak var pictureView: UIImageView!
var pictureURL: URL? {
didSet {
fetchImage()
}
}
fileprivate func fetchImage() {
if let url = pictureURL {
pictureView.image = nil
let queue = DispatchQueue(label: "image fetcher", qos: .userInitiated)
queue.async { [weak weakSelf = self] in
do {
let contentsOfURL = try Data(contentsOf: url)
DispatchQueue.main.async {
if url == self.pictureURL {
weakSelf?.pictureView?.image = UIImage(data: contentsOfURL)
weakSelf?.layoutSubviews()
print("loaded")
}
}
} catch let exception {
print(exception.localizedDescription)
}
}
}
}
}
This is the code that loads the cell on its TableViewController:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
switch indexPath.section {
case 0:
cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath)
if let imageCell = cell as? ImageTableViewCell {
imageCell.pictureURL = tweet?.media[indexPath.row].url
// other stuff not programmed yet
}
return cell
The code that gives me the cell's height:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0 && tweet != nil {
let media = tweet?.media[indexPath.row]
return tableView.frame.width / CGFloat(media!.aspectRatio)
}
return UITableViewAutomaticDimension
}
I'm sorry for pasting all this code, but I have no idea where the problem is so I'm putting everything I can this might be related.
You should set content mode first and then you should set the frame of your imageview, so once you should try to set content mode in awakeFromNib of tableview subclass or from cellforrowatindexpath before setting image to it!
Or you can set your content mode from interface builder (from storyboard!) - > select your imageview - > fro attribute inspector - > select mode(under view) to Aspect fit
Well, following an answer on reddit, I deleted the table view controller and remade it, setting all the outlets again. It worked, I guess it was a problem in Xcode?
So if you're having a problem like this, try remaking your storyboard.
I have a custom table cell inside a table view, which contains a horizontal stack view of image view and text field... The image view mode is ScaleAspectFit
When the view loads initially, the views are arranged as they should be, but when a cell is touched or the table view is scrolled, the image view resizes unexpectedly
Here are screenshots and my code
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ImagesTableViewCell", forIndexPath: indexPath) as! ImagesTableViewCell
// Configure the cell...
let pic = self.patient!.images[indexPath.row]
cell.imageViewPic.clipsToBounds = true
cell.imageViewPic.contentMode = .ScaleAspectFit
cell.imageViewPic.image = getImage(pic.imagePath)
cell.labelPic.text = pic.caption
cell.imageViewPic.tag = indexPath.row
cell.labelPic.tag = indexPath.row
return cell
}
I tried all Content Modes with no benefit
Edit2: here is getImage() function code as requested
func getImage(filename: String) -> UIImage {
let path = getDocumentsDirectory().stringByAppendingPathComponent(filename)
let image = UIImage(contentsOfFile: path)
return image!
}
Set the clipsToBounds property of UIImageView to true.
Like
set cell.imageViewPic.clipsToBounds=true
Also check the constraint of the imageview. It should be center vertical align,leading, fixed width and fixed height.
try below steps it may can help you.
render ImageView with fixe width and height
make clipSubviews = YES. in story board OR cell.imageViewPic.clipsToBounds = YES;
I am using SDWebImage library to download image and load into image view inside a table cell.
I want to resize the table cell after the image is downloaded. Please advice. Many thanks.
Cell is not resize correctly after image set into image view
Cell resize correctly after dequeue and load again
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(GagListTableViewCell.cellId, forIndexPath: indexPath) as! GagListTableViewCell
cell.layoutMargins = UIEdgeInsetsZero
let gagDataCommand = gagDataSource[indexPath.row]
if let caption = gagDataCommand.caption {
cell.captionLabel.text = caption
}
if let imageUrlStr = gagDataCommand.images?.normal {
if let imageUrl = NSURL(string: imageUrlStr) {
cell.gagImage.sd_setImageWithURL(imageUrl, placeholderImage: UIImage(named: AppConstants.imagePlaceHolderName), completed: { (image, error, cacheType, url) in
//guess I need to redraw the layout here
})
} else {
//fail to get image url, set placeholder image to the cell instead
cell.gagImage.image = UIImage(named: AppConstants.imagePlaceHolderName)
}
}
return cell
}
You could use automatic dimension for tableview cell.
tableView.rowHeight = UITableViewAutomaticDimension