多行 UILabel 的 UIStackView 分布和对齐

UIStackView distribution and alignment of a multiline UILabel

我正在努力解决一些基本的 UIStackView 分布和对齐问题。

我有一个 UICollectionViewCell,它在 contentView 子视图中有一个水平的 UIStackView。这个 UIStackView 有一个用于三个标签本身的垂直 UIStackView,当然还有 UIImageView。

这是下面屏幕截图的代码片段:

    func createSubViews() {

    // contains the UIStackview with the 3 labels and the UIImageView
    containerStackView = UIStackView()
    containerStackView.axis = .horizontal
    containerStackView.distribution = .fill
    containerStackView.alignment = .top
    contentView.addSubview(containerStackView)

    // the UIStackView for the labels
    verticalStackView = UIStackView()
    verticalStackView.axis = .vertical
    verticalStackView.distribution = .fill
    verticalStackView.spacing = 10.0
    containerStackView.addArrangedSubview(verticalStackView)

    categoryLabel = UILabel()
    categoryLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
    categoryLabel.textColor = UIColor.lightGray
    verticalStackView.addArrangedSubview(categoryLabel)

    titleLabel = UILabel()
    titleLabel.numberOfLines = 3
    titleLabel.lineBreakMode = .byWordWrapping
    verticalStackView.addArrangedSubview(titleLabel)

    timeLabel = UILabel()
    timeLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
    timeLabel.textColor = UIColor.lightGray
    verticalStackView.addArrangedSubview(timeLabel)

    // UIImageView
    imageView = UIImageView()
    imageView.contentMode = .scaleAspectFill
    imageView.clipsToBounds = true
    imageView.layer.cornerRadius = 5
    layer.masksToBounds = true
    containerStackView.addArrangedSubview(imageView)
}

我想要实现的是,"time label"(“3 天前”)始终位于每个 UICollectionViewCell 的底部(与 UIImageView 的底部对齐),无论标题如何标签线。

我玩过各种 UIStackView 分布,约束 "time label" 和 "title label" 的拥抱优先级。

但无论如何我都做不对。有什么提示吗?

问题出在垂直堆栈视图上。您显然想说:中间标签的顶部应该紧贴 MyCustomLabel 的底部,但 3 天前的底部应该紧贴整个底部。这不是你可以对堆栈视图说的话。

即使这不是您想说的,您仍然需要使垂直堆栈视图占据单元格的整个高度,您将如何做到这一点?在您展示的代码中,您根本不这样做;实际上,基于该代码,您的堆栈视图 大小为零 ,这将导致各种问题。

因此,我建议您去掉所有堆栈视图,直接配置布局。您的布局很容易使用自动布局约束进行配置。

更新

由于您设置的是 titleLabel.numberOfLines = 3,一种方法是在标题文本中添加三个换行符。这将迫使 titleLabel 始终消耗其三行的完整高度,迫使 timeLabel 到底部。

也就是说,当你设置titleLabel.text时,这样做:

titleLabel.text = theTitle + "\n\n\n"

原创

如果让其中一个标签垂直拉伸,拉伸标签的文本将在拉伸标签的边界内垂直居中,这不是您想要的。所以我们不能让标签垂直拉伸。因此我们需要引入一个可以拉伸但不可见的填充视图。

如果 padding view 被压缩到零高度,stack view 仍然会在它前后放置间距,导致 titleLabeltimeLabel 之间的 double-spacing,你也不想要

所以我们需要使用填充视图来实现所有间距。将 verticalStackView.spacing 更改为 0.

categoryLabel 之后、titleLabel 之前,将名为 padding1 的通用 UIView 添加到 verticalStackView。将其高度限制为 10。

titleLabel 之后、timeLabel 之前,将名为 padding2 的通用 UIView 添加到 verticalStackView。将其高度限制为大于或等于 10,以便它可以拉伸。

categoryLabeltitleLabeltimeLabel的垂直拥抱优先级设置为必需,这样它们就不会垂直拉伸。

verticalStackView 的高度限制为 containerStackView 的高度,以便在需要时拉伸一个或多个其排列的子视图以填充可用的垂直 space。唯一可以拉伸的排列子视图是 padding2,因此它会拉伸,将标题文本保持在顶部附近,将时间文本保持在底部。

此外,将您的 containerStackView 限制在 contentView 的范围内并设置 containerStackView.translatesAutoresizingMaskIntoConstraints = false

结果:

这是我的游乐场:

import UIKit
import PlaygroundSupport

class MyCell: UICollectionViewCell {

    var containerStackView: UIStackView!
    var verticalStackView: UIStackView!
    var categoryLabel: UILabel!
    var titleLabel: UILabel!
    var timeLabel: UILabel!
    var imageView: UIImageView!

    func createSubViews() {

        // contains the UIStackview with the 3 labels and the UIImageView
        containerStackView = UIStackView()
        containerStackView.axis = .horizontal
        containerStackView.distribution = .fill
        containerStackView.alignment = .top
        contentView.addSubview(containerStackView)

        // the UIStackView for the labels
        verticalStackView = UIStackView()
        verticalStackView.axis = .vertical
        verticalStackView.distribution = .fill
        verticalStackView.spacing = 0
        containerStackView.addArrangedSubview(verticalStackView)

        categoryLabel = UILabel()
        categoryLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
        categoryLabel.textColor = UIColor.lightGray
        verticalStackView.addArrangedSubview(categoryLabel)

        let padding1 = UIView()
        verticalStackView.addArrangedSubview(padding1)

        titleLabel = UILabel()
        titleLabel.numberOfLines = 3
        titleLabel.lineBreakMode = .byWordWrapping
        verticalStackView.addArrangedSubview(titleLabel)

        let padding2 = UIView()
        verticalStackView.addArrangedSubview(padding2)

        timeLabel = UILabel()
        timeLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
        timeLabel.textColor = UIColor.lightGray
        verticalStackView.addArrangedSubview(timeLabel)

        // UIImageView
        imageView = UIImageView()
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        imageView.layer.cornerRadius = 5
        layer.masksToBounds = true
        containerStackView.addArrangedSubview(imageView)

        categoryLabel.setContentHuggingPriority(.required, for: .vertical)
        titleLabel.setContentHuggingPriority(.required, for: .vertical)
        timeLabel.setContentHuggingPriority(.required, for: .vertical)
        imageView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        containerStackView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            contentView.leadingAnchor.constraint(equalTo: containerStackView.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: containerStackView.trailingAnchor),
            contentView.topAnchor.constraint(equalTo: containerStackView.topAnchor),
            contentView.bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor),
            verticalStackView.heightAnchor.constraint(equalTo: containerStackView.heightAnchor),
            padding1.heightAnchor.constraint(equalToConstant: 10),
            padding2.heightAnchor.constraint(greaterThanOrEqualToConstant: 10),
            ])
    }
}

let cell = MyCell(frame: CGRect(x: 0, y: 0, width: 320, height: 110))
cell.backgroundColor = .white
cell.createSubViews()
cell.categoryLabel.text = "MY CUSTOM LABEL"
cell.titleLabel.text = "This is my title"
cell.timeLabel.text = "3 days ago"
cell.imageView.image = UIGraphicsImageRenderer(size: CGSize(width: 110, height:110)).image { (context) in
    UIColor.blue.set()
    UIRectFill(.infinite)
}

PlaygroundPage.current.liveView = cell