【问题标题】:Dynamic UIImageView Size Within UITableViewUITableView 内的动态 UIImageView 大小
【发布时间】:2015-11-23 18:07:06
【问题描述】:

我想用一个显示图像的自定义 TableViewCell 实现一个 TableView。

为了简单起见,我只是使用自动布局将 UIImageView 放在 tableviewcell 中(如下图所示)。

我想要的是在 UIImageView 中显示图像,但是这些图像尺寸可以是任何尺寸并且不一致(纵向、横向、正方形)...

因此,我希望显示具有固定宽度(设备宽度)和尊重图像比例的动态高度的图像。我想要这样的东西:

很遗憾,我没能达到这个结果。

这是我实现它的方式 - (我正在使用 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 的 Height Autolayout 约束
  2. 修改 UIImageView 的框架

在此处查看方法 1 和 2 的结果:

【问题讨论】:

  • 它显示的是什么?设置图像的新高度约束是正确的,但是 AFAICT,没有什么可以告诉您的表格单元格根据图像的高度更改其高度。
  • 嗨!我已经添加了两种方法结果的链接。我会在哪里告诉表格单元格改变它的高度?我想,如果我没记错的话,Haneke 的函数“self.selfieImageView.hnk_setImageFromURL”是异步的,所以在加载之前我并没有真正得到 Cell 的高度。
  • 你能解决这个问题吗,我也是这种情况?
  • @fabdarice,您为此使用的任何解决方案?
  • 您找到任何解决方案了吗? @fabdarice

标签: ios swift uitableview uiimageview autolayout


【解决方案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()

【讨论】:

    【解决方案2】:
    1. 确保您在情节提要中设置了上下约束。
    2. 将缩略图/图像作为数据保存到本地存储(我使用的是核心数据)
    3. 使用拇指图像,计算图像的纵横比
    4. 在 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
        }
    

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

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

    【讨论】:

      【解决方案3】:

      Swift 4.0

      您可以使用简单的代码根据tableView单元格中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.beingUpdates(),更不用说任何其他缺乏......
      【解决方案4】:

      在将图像加载到 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 的约束设置为topbottomleadingtrailingfixed height constraint,优先级为 999 并根据图片更改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)
          }
      

      Example on Github

      【讨论】:

      • 如果您有什么要补充的,请随时发表评论或提出修改建议:)
      • 如果图片上方还有标签怎么办。使用此代码,标签似乎位于图像下方。
      • 简单 - 在各个图像中添加标签约束。
      • 你可以在instagram.com/developer/endpoints/media查看它提供"low_resolution": { "url": "http://distillery.s3.amazonaws.com/media/2011/01/28/0cc4f24f25654b1c8d655835c58b850a_6.jpg", "width": 306, "height": 306 },
      • 你节省了我的时间 :) 非常感谢!
      【解决方案5】:

      在情节提要中,将尾随、前导、底部和顶部约束添加到您的 UIImageView。加载图像后,您需要做的就是向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: )' 为单元格设置图像。
      • 全宇宙最佳答案。非常感谢您的 GitHub 存储库!
      • 不幸的是,如果异步获取图像,这不会刷新单元格高度 - 我认为 OP 有这个问题。
      • 对于 Swift 4 你必须使用 constraint.priority = UILayoutPriority(rawValue: 999)
      • 如果您的图片是异步获取的,请查看stackoverflow.com/a/34079027/62
      【解决方案6】:

      如果你想让 UIImageView 告诉 UITableView 它需要多高,那么你需要配置表格视图来启用自动行计算。为此,您可以将estimatedRowHeight 设置为一个固定的正值并将rowHeight 设置为UIViewAutomaticDimension。这是第一部分。

      现在您需要配置 UIImageView 以根据表格视图所需的宽度和图像的纵横比请求高度。这将是第二部分。

      首先,将 contentMode 设置为 .AspectFit。这将导致视图根据它所采用的任何尺寸调整图像外观的大小。但是,这仍然不会导致它通过自动布局请求所需的高度。相反,它将请求intrinsicContentSize 的高度,这可能与调整大小的图像完全不同。因此,其次,向 UIImageView 添加一个约束条件,其格式为 width = height * multiplier,其中乘数基于图像本身的纵横比。

      现在表格视图将需要正确的宽度,contentMode 机制将确保图像正确调整大小而不会变形,纵横比约束将通过自动布局确保图像视图需要正确的高度。

      这行得通。它的缺点是每次将新图像分配给图像视图时都需要更新纵横比约束,如果图像视图位于正在重复使用的单元格中,这可能会很常见。

      另一种方法是只添加一个高度约束并在包含图像视图的视图的 layoutSubviews 中更新它。这具有更好的性能,但代码模块化更差。

      【讨论】:

      • 嗨@algal,首先,感谢您的回答,非常感谢。如果您查看我的代码,我会逐步完成您刚才描述的所有操作。我唯一没有完全做的部分是乘数部分。其背后的原因是,如果我使用乘数(宽度 = 高度 * 乘数)添加自动布局约束,我无法以编程方式修改乘数,因为它是只读参数。我也尝试了替代方法(在上面的方法 1 中描述),不幸的是没有成功。
      • 另外,关于您上面的代码,即使您异步加载图像,您也应该尝试确定纵横比并尽可能立即设置纵横比约束。否则,在图像加载之前,所有表格视图的布局计算最初都是推测性的并且是错误的,当图像到达时,您可能需要触发新的布局,并且您可能会看到跳跃的滚动。但是在配置单元格时不需要强制布局传递。
      • 你是对的,我最终在服务器端计算尺寸,这样我就可以在显示单元格之前设置 heightConstraint。这最终解决了我的问题。谢谢
      • 老兄,你的代码现在会很棒:(你能把它放在要点上吗?
      • @RobinEllerkmann 您需要配置表格视图以使用自动行高计算。然后它将通过 AL 约束遵循表格视图单元格所需的大小。然后您需要配置您的单元格以要求其包含的图像视图所隐含的大小。然后在代码中,您需要确保它需要的大小不是图像的固有大小,而是适当调整纵横比的大小。所以这里有一个连接链。图像大小 -> imageView 大小 -> 单元格大小 -> 表格视图调整大小。
      【解决方案7】:

      从你的 imageView 中移除高度限制。选择以“aspect”开头的图像视图的 contentMode,例如:aspect fill 或 aspect fit(根据您的要求进行匹配)。

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

      heightForRowAtIndexPath
      

      设置自定义行高。

      【讨论】:

      • 您好,我试过你的方法,但它不起作用。原因是因为我用来加载图像的 Haneke 函数“self.selfieImageView.hnk_setImageFromURL”是异步的,所以在加载之前我并没有真正得到 Cell 的高度。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-22
      • 2011-03-17
      • 1970-01-01
      • 2012-10-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多