将 iOS UILabel 包装为受约束的 UIView 中的块



我在UIView中有几个UILabels。

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

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

下面是一个基于适合父视图宽度的"换行"标签的示例。

您可以直接在游乐场页面中运行它...点击红色的"点击我"按钮以切换标签中的文本,以查看它们如何"适合"。

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

最新更新