【问题标题】:Wrapping an iOS UILabel as a block within a constrained UIView将 iOS UILabel 包装为受约束的 UIView 中的块
【发布时间】:2018-02-01 15:55:07
【问题描述】:

我在 UIView 中有几个 UILabel。

我将包含视图限制为 100 像素。如果两个 UILabel 的固有宽度均为 75px(因为它们的内容),我希望第二个标签低于第一个标签,因为如果不包装自己的文本就无法显示。

iOS 中是否存在支持该行为的包含视图?

【问题讨论】:

  • 可以使用UICollectionView 做到这一点,但除非您有多个实例,否则这似乎有点矫枉过正。另一种选择是检查代码中的标签宽度(当您更改文本时),并相应地定位它们。
  • 我从未使用过 UICollectionView 和 FlowLayout 看起来很有趣,但看起来它可以让集合中的“行”流动,而我试图将“列”连续流动
  • 假设您将集合视图的宽度设置为 100... 如果所有单元格的宽度均为 20px,则每行将获得 4 个单元格。如果第一个单元格的宽度为 80 像素,而第二个单元格的宽度超过 20 像素,则第二个单元格将放置在下一行的开头。
  • 虽然...如果您从未使用过集合视图,我想我建议您计算尺寸。很简单:在 Label-A 中获取文本的宽度,在 Label-B 中获取文本的宽度,如果总和(加上所需的间距?)小于 100,则将 B 放在与 A 相同的 Y 处,并将 X 放在 A 的宽度处+ 间距。如果大于 100,则将 B 置于 X=0 处,并且 Y = A 的高度(“换行”到下一行“)。

标签: ios uiview autolayout


【解决方案1】:

这是一个基于适合父视图宽度的“包装”标签的示例。

您可以直接在 Playground 页面中运行它...点击红色的“点击我”按钮来切换标签中的文本,看看它们如何“适合”。

import UIKit
import PlaygroundSupport

// String extension for easy text width/height calculations
extension String {
    func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
        return ceil(boundingBox.height)
    }

    func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
        return ceil(boundingBox.width)
    }
}

class TestViewController : UIViewController {

    let btn: UIButton = {
        let b = UIButton()
        b.translatesAutoresizingMaskIntoConstraints = false
        b.setTitle("Tap Me", for: .normal)
        b.backgroundColor = .red
        return b
    }()

    let cView: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .blue
        return v
    }()

    let labelA: UILabel = {
        let v = UILabel()
        // we will be explicitly setting the label's frame
        v.translatesAutoresizingMaskIntoConstraints = true
        v.backgroundColor = .yellow
        return v
    }()

    let labelB: UILabel = {
        let v = UILabel()
        // we will be explicitly setting the label's frame
        v.translatesAutoresizingMaskIntoConstraints = true
        v.backgroundColor = .cyan
        return v
    }()

    // spacing between labels when both fit on one line
    let spacing: CGFloat = 8.0

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(btn)

        btn.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)

        btn.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        btn.topAnchor.constraint(equalTo: view.topAnchor, constant: 20.0).isActive = true

        // add the "containing" view
        view.addSubview(cView)

        // add the two labels to the containing view
        cView.addSubview(labelA)
        cView.addSubview(labelB)

        // constrain containing view Left-20, Top-20 (below the button)
        cView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0).isActive = true
        cView.topAnchor.constraint(equalTo: btn.bottomAnchor, constant: 20.0).isActive = true

        // containing view has a fixed width of 100
        cView.widthAnchor.constraint(equalToConstant: 100.0).isActive = true

        // constrain bottom of containing view to bottom of labelB (so the height auto-sizes)
        cView.bottomAnchor.constraint(equalTo: labelB.bottomAnchor, constant: 0.0).isActive = true

        // initial text in the labels - both will fit "on one line"
        labelA.text = "First"
        labelB.text = "Short"

    }

    func updateLabels() -> Void {
        // get the label height based on its font
        if let h = labelA.text?.height(withConstrainedWidth: CGFloat.greatestFiniteMagnitude, font: labelA.font) {
            // get the calculated width of each label
            if let wA = labelA.text?.width(withConstrainedHeight: h, font: labelA.font),
                let wB = labelB.text?.width(withConstrainedHeight: h, font: labelB.font) {

                // labelA frame will always start at 0,0
                labelA.frame = CGRect(x: 0.0, y: 0.0, width: wA, height: h)

                // will both labels + spacing fit in the containing view's width?
                if wA + wB + spacing <= cView.frame.size.width {
                    // yes, so place labelB to the right of labelA (put them on "one line")
                    labelB.frame = CGRect(x: wA + spacing, y: 0.0, width: wB, height: h)
                } else {
                    // no, so place labelB below labelA ("wrap" labelB "to the next line")
                    labelB.frame = CGRect(x: 0.0, y: h, width: wB, height: h)
                }
            }
        }
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        updateLabels()
    }

    @objc func didTap(_ sender: Any?) -> Void {
        // toggle labelB's text
        if labelB.text == "Short" {
            labelB.text = "Longer text"
        } else {
            labelB.text = "Short"
        }
        // adjust size / position of labels
        updateLabels()
    }

}


let vc = TestViewController()
vc.view.backgroundColor = .white
PlaygroundPage.current.liveView = vc

【讨论】:

    猜你喜欢
    • 2023-02-01
    • 2017-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-13
    • 1970-01-01
    相关资源
    最近更新 更多