动态调整UITableView中UIImageView的尺寸

26

我想实现一个带有自定义单元格(TableViewCell)的TableView,其中包含一张图片。

为了让这个过程简单化,我只是使用自动布局(如下图所示)在tableviewcell中放置了一个UIImageView。

enter image description here

我的目标是在一个UIImageView中显示图片,但这些图片的尺寸可以是任何不一致的(竖屏、横屏或正方形)...

因此,我想要显示一个具有固定宽度(设备宽度)和符合图片比例的动态高度的图像。我想要像这样的效果:
enter image description here

不幸的是,我没有成功实现这个结果。

以下是我的实现方式 - (我正在使用Haneke从URL加载存储在Amazon S3中的图像):

class TestCellVC: UITableViewCell {

    @IBOutlet weak var selfieImageView: UIImageView!
    @IBOutlet weak var heightConstraint: NSLayoutConstraint!

    func loadItem(#selfie: Selfie) {        
        let selfieImageURL:NSURL = NSURL(string: selfie.show_selfie_pic())!

        self.selfieImageView.hnk_setImageFromURL(selfieImageURL, placeholder: nil, format: HNKFormat<UIImage>(name: "original"), failure: {error -> Void in println(error)
            }, success: { (image) -> Void in
                // Screen Width
                var screen_width = UIScreen.mainScreen().bounds.width

                // Ratio Width / Height
                var ratio =  image.size.height / image.size.width

                // Calculated Height for the picture
                let newHeight = screen_width * ratio

                // METHOD 1
                self.heightConstraint.constant = newHeight

                // METHOD 2
                //self.selfieImageView.bounds = CGRectMake(0,0,screen_width,newHeight)

                self.selfieImageView.image = image
            }
        )
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // Register the xib for the Custom TableViewCell
        var nib = UINib(nibName: "TestCell", bundle: nil)
        self.tableView.registerNib(nib, forCellReuseIdentifier: "TestCell")

        // Set the height of a cell dynamically
        self.tableView.rowHeight = UITableViewAutomaticDimension
        self.tableView.estimatedRowHeight = 500.0

        // Remove separator line from UITableView
        self.tableView.separatorStyle = UITableViewCellSeparatorStyle.None

        loadData()
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCellWithIdentifier("TestCell") as TestCellVC


        cell.loadItem(selfie: self.selfies_array[indexPath.row])
        // Remove the inset for cell separator
        cell.layoutMargins = UIEdgeInsetsZero
        cell.separatorInset = UIEdgeInsetsZero

        // Update Cell Constraints
        cell.setNeedsUpdateConstraints()
        cell.updateConstraintsIfNeeded()
        cell.sizeToFit()

        return cell
    }
}

我的动态高度计算是正确的(我已经打印过了)。我尝试了两种方法(在代码中描述),但它们都没有起作用:

  1. 设置UIImageView的高度自动布局约束
  2. 修改UIImageView的框架

查看方法1和2的结果: 图片 图片2


它显示的是什么?设置图像的新高度约束是正确的,但据我所知,没有任何东西告诉您的表格单元格根据图像的高度更改其高度。 - NRitH
嗨!我已经添加了两种方法的结果链接。我应该在哪里告诉表格单元格更改其高度?我认为,如果我没有错的话,Haneke的函数“self.selfieImageView.hnk_setImageFromURL”是异步的,所以在加载之前我无法真正获得单元格的高度。 - fabdarice
你能解决这个问题吗?我也遇到了同样的情况。 - Pablo Ruan
@fabdarice,你有什么解决方案吗? - Ankit Kumar Gupta
你找到了任何解决方案吗?@fabdarice - Utku Dalmaz
@fabdarice,你找到解决方案了吗?我也遇到了发布新图片时类似的问题。我必须滚动才能使它们正确地调整大小。 - Luke Irvin
7个回答

30

在storyboard中为你的UIImageView添加trailing、leading、bottom和top约束。当你加载图片后,你只需要为你的UIImageView添加一个比例约束即可。你的图片单元将会看起来像这样:

class ImageTableViewCell: UITableViewCell {

    @IBOutlet weak var customImageView: UIImageView!

    internal var aspectConstraint : NSLayoutConstraint? {
        didSet {
            if oldValue != nil {
                customImageView.removeConstraint(oldValue!)
            }
            if aspectConstraint != nil {
                customImageView.addConstraint(aspectConstraint!)
            }
        }
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        aspectConstraint = nil
    }

    func setCustomImage(image : UIImage) {

        let aspect = image.size.width / image.size.height

        let constraint = NSLayoutConstraint(item: customImageView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: customImageView, attribute: NSLayoutAttribute.height, multiplier: aspect, constant: 0.0)
        constraint.priority = 999

        aspectConstraint = constraint

        customImageView.image = image
    }
}

您可以在这里查看工作示例 DynamicTableWithImages


要使用此解决方案,只需在出列单元格时调用“cell.setCustomImage(image:<image_name>)”来为单元格设置图像。 - Elardus Erasmus
宇宙最佳答案。非常感谢您的GitHub存储库! - Aluminum
7
如果图像是异步获取的,这将不会刷新单元格高度-我认为OP遇到了这个问题。 - Jonny
1
对于Swift 4,您必须使用constraint.priority = UILayoutPriority(rawValue: 999)。 - Guilherme Carvalho
2
如果您的图像是异步获取的,请查看 https://dev59.com/O1sX5IYBdhLWcg3wUuCa#34079027。 - Liron Yahdav
显示剩余4条评论

20

enter image description here

加载图片到tableView时,每个图片的纵横比可能不同。使用SDWebImage,我们可以从url获取图片,如下:

 testImageView.sd_setImage(with: url, placeholderImage: nil, options: [], completed: { (downloadedImage, error, cache, url) in
            print(downloadedImage?.size.width)//prints width of image
            print(downloadedImage?.size.height)//prints height of image
        })

UITableViewCell中,将imageView的约束设置为topbottomleadingtrailing999优先级的固定高度约束。根据图片更改height-constraint-constant

 var cellFrame = cell.frame.size
 cellFrame.height =  cellFrame.height - 15
 cellFrame.width =  cellFrame.width - 15
 cell.feedImageView.sd_setImage(with: url, placeholderImage: nil, options: [], completed: { (theImage, error, cache, url) in
            cell.feedImageHeightConstraint.constant = self.getAspectRatioAccordingToiPhones(cellImageFrame: cellFrame,downloadedImage: theImage!)

        })

计算单元格框架并根据此找到相应的高度。

  func getAspectRatioAccordingToiPhones(cellImageFrame:CGSize,downloadedImage: UIImage)->CGFloat {
        let widthOffset = downloadedImage.size.width - cellImageFrame.width
        let widthOffsetPercentage = (widthOffset*100)/downloadedImage.size.width
        let heightOffset = (widthOffsetPercentage * downloadedImage.size.height)/100
        let effectiveHeight = downloadedImage.size.height - heightOffset
        return(effectiveHeight)
    }

在Github上的示例


如果您有任何要补充的,请随时评论或建议编辑 :) - Jack
简单 - 在图像的标签方面添加约束条件。 - Jack
1
您可以在 https://www.instagram.com/developer/endpoints/media/ 进行查看,其中给出了 "low_resolution": { "url": "http://distillery.s3.amazonaws.com/media/2011/01/28/0cc4f24f25654b1c8d655835c58b850a_6.jpg", "width": 306, "height": 306 }, - Jack
很高兴能够帮助到您 :) - Jack
1
你节省了我的时间 :) 非常感谢! - Khush
显示剩余4条评论

4
如果您希望UIImageView告诉UITableView它需要多高的空间,那么您需要配置表视图以启用自动行计算。您可以通过将estimatedRowHeight设置为固定正值并将rowHeight设置为UIViewAutomaticDimension来实现这一点。这是第一部分。
现在,您需要配置UIImageView以根据表视图所需的宽度和图像的纵横比请求高度。这将是第二部分。
首先,将contentMode设置为.AspectFit。这将导致视图根据任何尺寸调整图像的外观。但是,这仍然不会导致它使用自动布局请求所需的高度。相反,它将请求intrinsicContentSize的高度,这可能与调整大小后的图像非常不同。因此,第二步,向UIImageView添加一个约束,形式为width = height * multiplier,其中乘数基于图像本身的纵横比。
现在,表视图将需要正确的宽度,contentMode机制将确保图像正确地调整大小而不失真,而纵横比约束将确保图像视图通过自动布局需要正确的高度。
这很有效。缺点是,每次将新图像分配给图像视图时,您都需要更新纵横比约束,如果图像视图在正在被重用的单元格中,则更新频率可能很高。
另一种方法是仅添加高度约束,并在包含图像视图的视图的layoutSubviews中更新它。这样做的性能更好,但代码模块化较差。

嗨@algal,首先,感谢您的回答,非常感激。 如果您查看我的代码,我已经按照您刚才描述的每个步骤完全执行了。我没有完全执行的唯一部分是乘法器部分。原因是,如果我添加一个带有乘法器的自动布局约束(宽度=高度*乘法器),我无法以编程方式修改乘法器,因为它是只读参数。 我还尝试了另一种方法(在上面的方法1中描述),但不幸的是没有成功。 - fabdarice
2
另外,关于您上面的代码,即使您正在异步加载图像,也应尽可能确定纵横比并立即设置纵横比约束。否则,直到图像加载完成之前,所有表视图的布局计算都将是最初的推测和错误的,并且当图像到达时,您可能需要触发新的布局并且您可能会看到跳动滚动。但在配置单元格时无需强制进行布局传递。 - algal
3
你说得对,我最终在服务器端计算了尺寸,以便在显示单元格之前设置高度约束。这最终解决了我的问题。谢谢。 - fabdarice
1
兄弟,你的代码现在会很棒的 :( 你能把它放到gist或其他地方吗? - JJPP
1
@RobinEllerkmann 你需要配置表视图以使用自动行高计算。然后它将通过AL约束延迟到表视图单元格所需的大小。然后,您需要配置单元格以要求由其包含的图像视图暗示的大小。然后在代码中,您需要确保它所需的大小不是图像的内在大小,而是适当的纵横比调整大小。因此,这里有一系列连接。图像大小-> imageView大小->单元格大小->表视图调整大小。 - algal
显示剩余7条评论

3

Swift 4.0

您可以使用简单的代码来管理tableView单元格中imageView的高度,使行高根据imageView的高度进行调整。

   func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if let height = self.rowHeights[indexPath.row]{
            return height
        }else{
            return defaultHeight
        }
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return imageArray.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let identifier = "editImageCell"
        let customCell : EdiPostImageCell = (self.imagesTableView.dequeueReusableCell(withIdentifier: identifier)) as! EdiPostImageCell
        let aspectRatio = (imageArray[indexPath.row]).size.height/(imageArray[indexPath.row]).size.width
        customCell.editlImageView.image = imageArray[indexPath.row]
        let imageHeight = self.view.frame.width*aspectRatio
        self.imagesTableView.beginUpdates()
        self.rowHeights[indexPath.row] = imageHeight
        tableView.endUpdates()
    }

那肯定行不通,因为你没有返回单元格类型,而且你还忘了tableView.beginUpdates(),更不用说其他缺失的部分了... - klapinski

1
如果您正在使用SDWebImage库,则可以在下载后管理图像,并使用与显示相同高度和宽度的缓存图像。
                guard let myImageWidth = downloadedImage?.size.width else { return }
                guard let myImageHeight = downloadedImage?.size.height else { return }
                
                let myViewWidth = cell.imageViewPost.frame.size.width
                let ratio = myViewWidth / myImageWidth
                let scaledHeight = myImageHeight * ratio
                
                self.tableView.beginUpdates()
            
                let transformer = SDImageResizingTransformer(size: CGSize(width: myViewWidth, height: scaledHeight), scaleMode: .fill)
                cell.imageViewPost.sd_setImage(with: url, placeholderImage: nil, context: [.imageTransformer: transformer])
                cell.imageViewPost.heightAnchor.constraint(equalToConstant: scaledHeight).isActive = true
                self.tableView.endUpdates()

0

从您的imageView中删除高度约束。选择以“aspect”开头的图像视图的contentMode,例如:aspect fill或aspect fit(根据您的要求进行调整)。

注意:您还需要设置行的高度,以便imageView适合其中。使用

heightForRowAtIndexPath

设置自定义行高。


3
您好,我已经尝试了您的方法,但它不起作用。原因是我使用的Haneke函数“self.selfieImageView.hnk_setImageFromURL”用于加载图片是异步的,因此在加载之前我无法真正获取单元格的高度。 - fabdarice

-1
确保在Storyboard中设置了top-bottom的约束。 将缩略图/图像保存为本地存储的数据(我正在使用Core Data)。 使用缩略图,计算图像的宽高比。 在heightForRowAt方法中按照需求自定义行高。
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        let entity = self.fetchedResultController.object(at: indexPath as IndexPath) as! Message
        var newh:CGFloat = 100.00

        if let fileThumbnails =  entity.file_thumbnails as? NSArray{
            if fileThumbnails.count != 0{
                fileThumbnails.map { (thumbNail) in
                    newh = self.getImageHeight(image:UIImage(data: thumbNail as! Data)! , h: UIImage(data: thumbNail as! Data)!.size.height , w: UIImage(data: thumbNail as! Data)!.size.width)
                }

            }

        }

        if entity.fileStatus == "DeletedMedia" {
            newh = 100
        }
        if entity.fileStatus == nil{
            newh = 0.0
        }
        print ("newH " , newh)
        return newh
    }
    func getImageHeight(image : UIImage, h : CGFloat, w : CGFloat) -> CGFloat {
        let aspect = image.size.width / image.size.height
        var newH = (messagesTV.frame.size.width) / aspect
        // customise as you want in newH
        if(newH > 500){
            newH = 500
           //maximum height 500
        }
        if(newH < 100){
            newH = 100
            //minimum height = 100
        }
        return newH
    }

如果本地存储中的缩略图被删除,则会显示占位符图像。您可以自定义newH变量以获得所需的输出。

如果您想更改参数,例如固定宽度,请在getImageHeight()方法中更改newH变量。 例如:var newH = (messagesTV.frame.size.width * 0.6) / aspect 我将根据我在此示例中提到的宽度获取具有纵横比的高度,我的固定宽度是屏幕宽度的60%。希望这可以帮助您!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接