我有一个stackView,其中包含了几个labelView,每个labelView中写了两个字。我想让它们在整个labelView的宽度上用一个省略号分开。结果是:一个词靠近左边,另一个词靠近右边,它们之间有点。注意:如果单词长度较长,标签可能会占用好几行。
编辑
在这里填满我的堆栈查看
for ingredient in ingredients {
let textLabel = UILabel()
textLabel.backgroundColor = UIColor.yellow // just for my needs
textLabel.widthAnchor.constraint(equalToConstant: ingredientsStackView.frame.width).isActive = true
textLabel.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
textLabel.text = ingredient.getName() + " " + String(ingredient.getAmount()) + " " + ingredient.getMeasure()
textLabel.textAlignment = .left
textLabel.numberOfLines = 0
textLabel.lineBreakMode = .byWordWrapping
ingredientsStackView.addArrangedSubview(textLabel)
}
ingredientsStackView.translatesAutoresizingMaskIntoConstraints = false
而这样子
但我想要这样的东西
你可以看到点与点之间 ingredientName
和 ingredientAmount
.
我有一个想法,通过 CGFloat
转换 此处,但这个问题已被关闭。
一种技术是使用 size()
或 boundingRect(with:options:context:)
来计算大小,重复计算越来越多的点,直到你达到所需的宽度。
但这忽略了一个微妙的(但我认为是重要的)方面,即所有行的点都应该完美地排列起来。如果它们不一致,就会令人惊讶地分心。
所以,我倾向于定义一个能做到这一点的视图,对一些共同的祖先视图坐标系进行模数计算。而且,我个人会将这些点渲染为 UIBezierPath
.
例如: 。
class EllipsesView: UIView {
let spacing: CGFloat = 3
let radius: CGFloat = 1.5
var color: UIColor {
UIColor { traitCollection in
switch traitCollection.userInterfaceStyle {
case .dark: return .white
default: return .black
}
}
}
let shapeLayer: CAShapeLayer = {
let layer = CAShapeLayer()
layer.strokeColor = UIColor.clear.cgColor
return layer
}()
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
override func layoutSubviews() {
super.layoutSubviews()
shapeLayer.fillColor = color.cgColor
let point = convert(bounds.origin, to: window)
let diff = radius * 3 + spacing
let offset = diff - point.x.truncatingRemainder(dividingBy: diff)
let rect = CGRect(x: bounds.minX + offset, y: bounds.maxY - radius * 2, width: bounds.width - offset, height: radius * 2)
let path = UIBezierPath()
var center = CGPoint(x: rect.minX + radius, y: rect.midY)
while center.x + radius < rect.maxX {
path.addArc(withCenter: center, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
center.x += diff
}
shapeLayer.path = path.cgPath
}
}
private extension EllipsesView {
func configure() {
layer.addSublayer(shapeLayer)
}
}
然后你可以添加你的两个标签, 将椭圆视图的底部与标签的底部基线对齐。
let stringPairs = [("foo", "$1.37"), ("foobar", "$0.42"), ("foobarbaz", "$10.00"), ("foobarbazqux", "$100.00")]
for stringPair in stringPairs {
let container = UIView()
container.translatesAutoresizingMaskIntoConstraints = false
let leftLabel = UILabel()
leftLabel.translatesAutoresizingMaskIntoConstraints = false
leftLabel.text = stringPair.0
leftLabel.setContentHuggingPriority(.required, for: .horizontal)
container.addSubview(leftLabel)
let ellipsesView = EllipsesView()
ellipsesView.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(ellipsesView)
let rightLabel = UILabel()
rightLabel.translatesAutoresizingMaskIntoConstraints = false
rightLabel.font = UIFont.monospacedDigitSystemFont(ofSize: rightLabel.font.pointSize, weight: .regular)
rightLabel.text = stringPair.1
rightLabel.setContentHuggingPriority(.required, for: .horizontal)
container.addSubview(rightLabel)
NSLayoutConstraint.activate([
// horizontal constraints
leftLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor),
ellipsesView.leadingAnchor.constraint(equalTo: leftLabel.trailingAnchor, constant: 3),
rightLabel.leadingAnchor.constraint(equalTo: ellipsesView.trailingAnchor, constant: 3),
rightLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor),
// align last baseline of three subviews
leftLabel.lastBaselineAnchor.constraint(equalTo: ellipsesView.bottomAnchor),
leftLabel.lastBaselineAnchor.constraint(equalTo: rightLabel.lastBaselineAnchor),
// vertical constraints to container
leftLabel.topAnchor.constraint(greaterThanOrEqualTo: container.topAnchor),
rightLabel.topAnchor.constraint(greaterThanOrEqualTo: container.topAnchor),
ellipsesView.topAnchor.constraint(equalTo: container.topAnchor),
leftLabel.bottomAnchor.constraint(equalTo: container.bottomAnchor),
])
verticalStackView.addArrangedSubview(container)
}
这样就产生了椭圆,但它们也都完美地排列在一起了。
谢谢 @koen. 他给了我另一个问题的链接,也帮助了我。但我看到他删除了那条评论,也许是因为Rob的答案。我也没能保存下来。
我的想法是在根垂直堆栈上添加3个视图的水平堆栈。
let stackView = UIStackView()
ingredientsStackView.addArrangedSubview(stackView)
stackView.leadingAnchor.constraint(equalTo: ingredientsStackView.leadingAnchor,
constant: 0.0).isActive = true
stackView.trailingAnchor.constraint(equalTo: ingredientsStackView.trailingAnchor,
constant: 0.0).isActive = true
接下来为这些视图创建并设置约束条件
let nameLabel = UILabel()
let ellipsesLabel = UILabel()
let amountAndMeasureLabel = UILabel()
for label in [nameLabel, ellipsisLabel, amountAndMeasureLabel] {
label.font = UIFont(name: "Montserrat-Medium", size: 14)
stackView.addArrangedSubview(label)
}
NSLayoutConstraint.activate([
nameLabel.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 0.0),
nameLabel.trailingAnchor.constraint(equalTo: ellipsisLabel.leadingAnchor, constant: 0.0),
nameLabel.widthAnchor.constraint(equalToConstant: nameLabel.intrinsicContentSize.width),
amountAndMeasureLabel.leadingAnchor.constraint(equalTo: ellipsisLabel.trailingAnchor, constant: 0.0),
amountAndMeasureLabel.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: 0.0),
amountAndMeasureLabel.widthAnchor.constraint(equalToConstant: amountAndMeasureLabel.intrinsicContentSize.width)
])
之后 不要忘了 呼叫 stackView.layoutIfNeeded()
然后在中间的视图中填充点,直到完全填满。
while ellipsisLabel.intrinsicContentSize.width <= ellipsisLabel.frame.width {
ellipsisLabel.text?.append(".")
}
整段代码
private func setIngredients(ingredients: [Ingredient], ingredientsStackView: UIStackView) {
for ingredient in ingredients {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .fill
stackView.distribution = .fill
ingredientsStackView.addArrangedSubview(stackView)
stackView.leadingAnchor.constraint(equalTo: ingredientsStackView.leadingAnchor,
constant: 0.0).isActive = true
stackView.trailingAnchor.constraint(equalTo: ingredientsStackView.trailingAnchor,
constant: 0.0).isActive = true
let nameLabel = UILabel()
nameLabel.text = ingredient.name
nameLabel.lineBreakMode = .byWordWrapping
nameLabel.contentMode = .left
nameLabel.numberOfLines = 0
let ellipsisLabel = UILabel()
ellipsisLabel.text = ""
let amountAndMeasureLabel = UILabel()
amountAndMeasureLabel.text = String(ingredient.amount) + " " + ingredient.measure
amountAndMeasureLabel.contentMode = .left
for label in [nameLabel, ellipsisLabel, amountAndMeasureLabel] {
label.font = UIFont(name: "Montserrat-Medium", size: 14)
stackView.addArrangedSubview(label)
}
NSLayoutConstraint.activate([
nameLabel.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 0.0),
nameLabel.trailingAnchor.constraint(equalTo: ellipsisLabel.leadingAnchor, constant: 0.0),
nameLabel.widthAnchor.constraint(
equalToConstant: nameLabel.intrinsicContentSize.width),
amountAndMeasureLabel.leadingAnchor.constraint(equalTo: ellipsisLabel.trailingAnchor, constant: 0.0),
amountAndMeasureLabel.trailingAnchor.constraint(equalTo: stackView.trailingAnchor, constant: 0.0),
amountAndMeasureLabel.widthAnchor.constraint(
equalToConstant: amountAndMeasureLabel.intrinsicContentSize.width)
])
stackView.layoutIfNeeded()
while ellipsisLabel.intrinsicContentSize.width <= ellipsisLabel.frame.width {
ellipsisLabel.text?.append(".")
}
}
ingredientsStackView.translatesAutoresizingMaskIntoConstraints = false
}
但是在看到@Rob的回答之前,我就做了这个。谢谢你 可能和我的相比,实现起来比较困难,你可以选择哪个更适合你。我觉得他的方案更合理,更简洁,所以作为答案我选择他的帖子。