如何在一本书的内容 table 中向 LabelView 添加点(椭圆)

How to add dots (ellipses) to LabelView as in the table of contents of a book

我有 stackView,其中包含几个 labelView,每个 labelView 都写有两个词。我希望它们在 labelView 的整个宽度上用省略号分隔。结果:一个词靠近左边,另一个词在右边,它们之间有点。注意:如果字数较长,label可能会占几行。

编辑

在这里填充我的 stackView

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

看起来像这样

但我想要这样的东西

您可以在 ingredientNameingredientAmount 之间看到点。

我有一个想法通过 CGFloat 转换 here 来实现这个,但是这个问题被关闭了。

一种技术是使用 size() or 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", ".37"), ("foobar", "[=11=].42"), ("foobarbaz", ".00"), ("foobarbazqux", "0.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。他给了我一个 link 另一个对我也有帮助的问题。但我看到他删除了该评论,可能是因为 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 回答之前就这样做了。谢谢!与我的相比,它可能更难实施,您可以选择更适合您的。我认为他的解决方案更合理、更简洁,所以我会选择他的 post.

作为答案