更复杂的 AutoLayout 方程

More complicated AutoLayout equations

您好,请看下面的模型:

我想知道如何从上面创建约束:

V2.top = C1.top + n * V1.height

因为这与约束的 default equation 不同:

item1.attribute1 = multiplier × item2.attribute2 + constant

我知道我可以只使用 AutoResizingMask 但它会在我的代码中造成真正的混乱,因为我的代码非常复杂,而且我也不那么喜欢 AutoResizingMask

(顺便说一句,请只回答Swift!)

谢谢

使用辅助视图可以解决。 在这种情况下,辅助视图只是用于调整大小的 UIView,没有自己的可见内容。设置其 alpha = 0 或 hidden = true。

  • 设置helperView.top = c1.top
  • 设置helperView.height = v1.height
  • 设置v2.top = helperView.bottom + 5

您还需要为助手视图设置宽度和行距,但它们的值并不重要。

您可以使用 UILayoutGuide 执行此操作 -- 来自 Apple 的 docs:

The UILayoutGuide class is designed to perform all the tasks previously performed by dummy views, but to do it in a safer, more efficient manner.

为了获得您想要的布局,我们可以:

  • 添加布局指南 C1
  • 将其顶部限制为C1顶部
  • 使用“n”乘数
  • 将其高度限制为V1高度
  • V2顶部限制到指南的底部

这里有一个完整的例子来演示:

class GuideViewController: UIViewController {
    
    // a label on each side so we can
    //  "tap to change" v1 Height and "n" multiplier
    let labelN = UILabel()
    let labelH = UILabel()
    
    let containerView = UIView()
    let v1 = UILabel()
    let v2 = UILabel()

    // a layout guide for v2's Top spacing
    let layG = UILayoutGuide()

    // we'll change these on taps
    var n:CGFloat = 0
    var v1H: CGFloat = 30

    // constraints we'll want to modify when "n" or "v1H" change
    var v1HeightConstraint: NSLayoutConstraint!
    var layGHeightConstraint: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .white
        
        v1.text = "V1"
        v2.text = "V2"
        v1.textAlignment = .center
        v2.textAlignment = .center

        containerView.backgroundColor = .systemTeal
        v1.backgroundColor = .green
        v2.backgroundColor = .yellow
        
        [containerView, v1, v2].forEach {
            [=10=].translatesAutoresizingMaskIntoConstraints = false
        }
        
        containerView.addSubview(v1)
        containerView.addSubview(v2)
        
        view.addSubview(containerView)

        // add the layout guide to containerView
        containerView.addLayoutGuide(layG)

        // respect safe area
        let safeG = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // let's give the container 80-pts Top/Bottom and 120-pts on each side
            containerView.topAnchor.constraint(equalTo: safeG.topAnchor, constant: 80.0),
            containerView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 120.0),
            containerView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -120.0),
            containerView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor, constant: -80.0),

            // v1 Leading / Trailing / Bottom 20-pts
            v1.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20.0),
            v1.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20.0),
            v1.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -20.0),

            // just use v2's intrinisic height
            
            // v2 Leading / Trailing 20-pts
            v2.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20.0),
            v2.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20.0),

            // layout Guide Top / Leading / Trailing
            layG.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 0.0),
            layG.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 0.0),
            layG.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 0.0),

            // and constrain v2 Top to layout Guide Bottom
            v2.topAnchor.constraint(equalTo: layG.bottomAnchor, constant: 0.0),
            
        ])

        // layout Guide Height equals v1 Height x n
        layGHeightConstraint = layG.heightAnchor.constraint(equalTo: v1.heightAnchor, multiplier: n)
        layGHeightConstraint.isActive = true
        
        // v1 Height
        v1HeightConstraint = v1.heightAnchor.constraint(equalToConstant: v1H)
        v1HeightConstraint.isActive = true

        // "tap to change" labels
        [labelN, labelH].forEach {
            [=10=].translatesAutoresizingMaskIntoConstraints = false
            [=10=].backgroundColor = UIColor(white: 0.9, alpha: 1.0)
            [=10=].textAlignment = .center
            [=10=].numberOfLines = 0
            view.addSubview([=10=])
            let t = UITapGestureRecognizer(target: self, action: #selector(tapHandler(_:)))
            [=10=].addGestureRecognizer(t)
            [=10=].isUserInteractionEnabled = true
        }
        NSLayoutConstraint.activate([
            labelN.topAnchor.constraint(equalTo: containerView.topAnchor),
            labelN.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 8.0),
            labelN.trailingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: -8.0),
            labelN.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),

            labelH.topAnchor.constraint(equalTo: containerView.topAnchor),
            labelH.leadingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 8.0),
            labelH.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -8.0),
            labelH.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
        ])

        updateInfo()
    }

    @objc func tapHandler(_ gr: UITapGestureRecognizer) -> Void {
        guard let v = gr.view else {
            return
        }
        
        // if we tapped on the "cylcle N" label
        if v == labelN {
            
            n += 1
            if n == 6 {
                n = 0
            }
            
            // can't change multiplier directly, so
            //  de-Activate / set it / Activate
            layGHeightConstraint.isActive = false
            layGHeightConstraint = layG.heightAnchor.constraint(equalTo: v1.heightAnchor, multiplier: n)
            layGHeightConstraint.isActive = true

        }
        
        // if we tapped on the "cylcle v1H" label
        if v == labelH {
            
            v1H += 5
            if v1H > 50 {
                v1H = 30
            }

            v1HeightConstraint.constant = v1H

        }

        updateInfo()
    }
    
    func updateInfo() -> Void {
        var s: String = ""
        
        s = "Tap to cycle \"n\" from Zero to 5\n\nn = \(n)"
        labelN.text = s
        
        s = "Tap to cycle \"v1H\" from 30 to 50\n\nv1H = \(v1H)"
        labelH.text = s
        
    }
}

当你运行它时,它看起来像这样:

每次点击左侧,它都会将 n 乘数变量从零循环到 5,并更新约束条件。

每次点击右侧,v1H 高度变量会从 30 循环到 50,并更新约束条件。