使用 UIStackView 时,UIImageView 在 UITableViewCell 内被挤压

Posted

技术标签:

【中文标题】使用 UIStackView 时,UIImageView 在 UITableViewCell 内被挤压【英文标题】:UIImageView squished inside UITableViewCell when using UIStackView 【发布时间】:2018-04-17 14:50:59 【问题描述】:

UIStackView 中使用 UIImageView 时,我看到了一些奇怪的行为,我怀疑我忽略了一些明显的东西 - 尽管此时我已经尝试了很多东西。

我要做的很简单:从网上下载一张图片,在UIStackView 中设置UIImageViewUIStackView 还包含一个UILabel(而我的实际项目包含不少UILabels,因此是UIStackView 的原因)。下图有彩色背景来说明哪里出了问题,我还包含了完整的 Playground 代码,以便轻松复制/粘贴。

正在下载的图像大于屏幕的宽度/高度,我设置了contentMode = .scaleAspectFitUITableView 使用tableView.rowHeight = UITableViewAutomaticDimension.estimatedRowHeight。我已经设置了约束,并且还尝试在 UIImageView 上设置拥抱优先级,但无济于事。我不提前知道确切的单元格高度,单元格高度都会有所不同。

实际发生的情况是图像被调整得非常小,尽管UIImageView 上的前导/尾随约束、UIStackView 上的约束、单元格上的.rowHeight 和@987654339 上的.scaleAspectFit @。你甚至可以看到一小块(红色)标签,所以我知道UIStackView 被固定在UITableViewCellcontentView 上。

颜色键

橙色:UITableViewCell 背景 红色:UILabel 背景 绿色:UIImageView 背景

import UIKit
import PlaygroundSupport



class CustomView: UIView, UITableViewDataSource 

    var tableView = UITableView()
    var tableData = [String]()

    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
    

    override init(frame: CGRect) 
        super.init(frame: frame)
        backgroundColor = UIColor.gray

        tableView.dataSource = self
        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.estimatedRowHeight = 300
        tableView.tableFooterView = UIView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(tableView)
        tableView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
        tableView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
        tableView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        tableData = [String](repeating: "https://img.webmd.com/dtmcms/live/webmd/consumer_assets/site_images/article_thumbnails/video/wibbitz/wbz-breakfast-most-important-meal.jpg", count: 2)
        tableView.register(CustomCell.self, forCellReuseIdentifier: "cell")
    

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
        let imageURL = tableData[indexPath.row]
        if let url = URL.init(string: imageURL) 
            cell.cellImageView?.downloadImage(with: url,
                                              completion: 
                                                // ...
            )
        
        return cell
    


class CustomCell: UITableViewCell 

    var cellImageView: UIImageView?

    required init?(coder aDecoder: NSCoder) 
        fatalError() // since we're not using
    

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) 
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        setupCell()
    

    func setupCell() 

        backgroundColor = UIColor.orange
        clipsToBounds = true

        let tempLabel = UILabel()
        tempLabel.translatesAutoresizingMaskIntoConstraints = false
        tempLabel.backgroundColor = UIColor.red
        tempLabel.text = "this is some text"
        tempLabel.numberOfLines = 0
        tempLabel.font = UIFont.systemFont(ofSize: 20, weight: .bold)

        cellImageView = UIImageView()
        cellImageView?.clipsToBounds = true
        cellImageView?.contentMode = .scaleAspectFit
        cellImageView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        cellImageView?.backgroundColor = UIColor.green
        cellImageView?.translatesAutoresizingMaskIntoConstraints = false

        let stackView = UIStackView(arrangedSubviews: [tempLabel, cellImageView!])
        contentView.addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .vertical
        stackView.distribution = .fillProportionally
        stackView.alignment = .leading
        stackView.spacing = 10
        stackView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
        stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        cellImageView?.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
        cellImageView?.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true

    



extension UIImageView 

    func downloadImage(with url: URL, completion: @escaping () -> Void) 

        let task = URLSession.shared.dataTask(with: url)  [weak weakSelf = self] data, response, error in

            guard error == nil else 
                print("UIImageView: error downloading image: \(String(describing: error))")
                return
            

            guard let data = data, let downloadedImage = UIImage.init(data: data) else 
                print("UIImageView: issue with data from downloaded image.")
                return
            

            DispatchQueue.main.async 
                weakSelf?.image = downloadedImage
                completion()
            
        
        task.resume()
    


let containerView = CustomView(frame: CGRect(x: 0, y: 0, width: 400, height: 600))
containerView.backgroundColor = UIColor.blue

PlaygroundPage.current.liveView = containerView
PlaygroundPage.current.needsIndefiniteExecution = true

【问题讨论】:

【参考方案1】:

您似乎认为堆栈视图中的图像视图会以某种方式从内向外调整单元格的高度。它不会。如果您想使用自动单元格高度,请不要使用堆栈视图(或添加约束以显式调整堆栈视图的大小,但我怀疑这会给您带来想要的结果)。

您的代码中堆栈视图的目的是什么?堆栈视图所做的只是在困难的情况下为您设置约束。但这不是一个困难的情况。标签和图像视图一起可以很容易地手动配置。

【讨论】:

感谢您的回复。以上是实际项目的简化版本——实际项目有大约六个标签,所以理论上stackview会让事情更容易管理。因此,根据您的回复,听起来 stackviews 不会从其内容中获取内在内容大小,因此不会从内向外调整大小。 但实际上堆栈视图是问题。用你的简化版试试,你会看到的。 你是对的。正如here 所证明的那样,Apple hard 推动 StackViews。 “一般来说,使用堆栈视图来管理尽可能多的布局。仅当您无法单独使用堆栈视图实现目标时才使用创建约束。”我不明白为什么要使用 StackView似乎无法从其内容中传递内在内容大小,因此使用范围有限。 非常少数情况下,您会尝试使用其子视图的约束“从内到外”调整视图的大小。在这些情况下,堆栈视图通常只会妨碍您(除非您可以从外部调整堆栈视图本身的大小,而您在此处无法做到这一点)。因此,Apple 说您通常可以使用堆栈视图并没有错——尽管事实上我不同意他们过度推销它们的方式。对我来说,堆栈视图仅适用于一个用例,即解决“平均分配”问题(即使那样你也可以在没有它们的情况下做到这一点)。【参考方案2】:

你只需要在这个方法中给出你估计的行高

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat

    return 300;   //Choose your custom row height

【讨论】:

感谢您的回复。tableView.rowHeight = UITableViewAutomaticDimensiontableView.estimatedRowHeight = 300 使这种方法变得不必要我很确定。我也不希望牢牢设置单元格高度+我不知道提前。

以上是关于使用 UIStackView 时,UIImageView 在 UITableViewCell 内被挤压的主要内容,如果未能解决你的问题,请参考以下文章

Swift UIStackView 利用 UIStackView 的全宽

UIStackView 内的视图未使用 AutoLayout 调整大小

当 UIStackView 更改其排列的子视图时,UITableViewCell 不会调整大小

如何使用 UIStackview 实现这个视图?

UIStackView中的圆形按钮(iOS Swift)

在 iOS 9 上更改隐藏属性时 UIStackView 不会动画