繁体   English   中英

使用 swift ios 编程约束,如何指定默认为高度的 50% 但可以在需要时缩小的视图?

[英]With swift ios programming constraints, how to specify a view that defaults to 50% of the height but can shrink if needed?

我正在使用 NSLayoutConstraint 来约束视图。 我希望它的高度在默认情况下占据屏幕的 50%,但如果没有足够的空间容纳其他组件(例如横向的 iphone),则视图可以缩小到高度的 10%。

我想:

       let y1 = NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal,
toItem: self.view, attribute: .top, multiplier: 1, constant: 0)

        let y2 = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .lessThanOrEqual, 
toItem: self.view, attribute: .height, multiplier: 0.5, constant: 0)
        
        let y3 = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .greaterThanOrEqual, 
toItem: self.view, attribute: .height, multiplier: 0.1, constant: 0)

不幸的是,这仅呈现屏幕高度的 10%。

我对两件事感到困惑:

  1. 当我像这样设置模糊的约束时,基本上说“在 10% 到 50% 之间”,它如何决定给它多少高度? 它是否默认为最小空间量?

  2. 我认为约束必须只有一种解决方案。 为什么我不会得到歧义错误,因为从 10% 到 50% 的任何高度在这里都是有效的解决方案?

最后,我如何获得我想要的,如果需要可以缩小 50% 的视图?

非常感谢!

您可以通过更改 50% 高度约束的Priority来做到这一点。

我们会告诉自动布局按钮必须至少是视图高度的 10%。

并且,我们会告诉 auto-layout 我们希望按钮是视图高度的 50%,但是:

.priority = .defaultHigh

上面写着“如果需要,你可以打破这个限制。”

所以...

    // constrain button Top to view Top
    let btnTop = NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal,
                                toItem: self.view, attribute: .top, multiplier: 1, constant: 0)

    // button Height Greater Than Or Equal To 10%
    let percent10 = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .greaterThanOrEqual,
                                toItem: self.view, attribute: .height, multiplier: 0.1, constant: 0)

    // button Height Equal To 50%
    let percent50 = NSLayoutConstraint(item: button, attribute: .height, relatedBy: .equal,
                                toItem: self.view, attribute: .height, multiplier: 0.5, constant: 0)

    // let auto-layout break the 50% height constraint if necessary
    percent50.priority = .defaultHigh

    [btnTop, percent10, percent50].forEach {
        $0.isActive = true
    }
    

或者,使用更现代的语法......

    let btnTop = button.topAnchor.constraint(equalTo: view.topAnchor)
    let percent10 = button.heightAnchor.constraint(greaterThanOrEqualTo: view.heightAnchor, multiplier: 0.10)
    let percent50 = button.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.50)
    percent50.priority = .defaultHigh
    
    NSLayoutConstraint.activate([btnTop, percent10, percent50])

现在,无论您拥有的其他 UI 元素会减少可用空间,自动布局都会将按钮的高度设置为“尽可能接近 50%,但始终至少为 10%”

这里有一个完整的例子来演示。 我使用了两个标签(顶部蓝色为“按钮”,底部为红色)。 点击将增加红色标签的高度,直到它开始“向上推底部”或“压缩”蓝色标签:

class ExampleViewController: UIViewController {
    
    let blueLabel = UILabel()
    let redLabel = UILabel()
    
    var viewSafeAreaHeight: CGFloat = 0
    
    var adjustableLabelHeightConstraint: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        [blueLabel, redLabel].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(v)
            v.textAlignment = .center
            v.textColor = .white
            v.numberOfLines = 0
        }
        blueLabel.backgroundColor = .blue
        redLabel.backgroundColor = .red
        
        view.addSubview(blueLabel)
        view.addSubview(redLabel)
        
        // blueLabel should be 50% of the height if possible
        //  otherwise, let it shrink to minimum of 10%
        
        // so, we'll constrain redLabel to the bottom of the view
        //  and give it a Height constraint that we can change
        //  so it can "compress" blueLabel
        
        // we'll constrain the bottom of blueLabel to stay above the top of redLabel
        
        // let's respect the safe-area
        let safeArea = view.safeAreaLayoutGuide
        
        // start by horizontally centering both elements,
        //  and 75% of the width of the view
        
        blueLabel.centerXAnchor.constraint(equalTo: safeArea.centerXAnchor).isActive = true
        redLabel.centerXAnchor.constraint(equalTo: safeArea.centerXAnchor).isActive = true
        
        blueLabel.widthAnchor.constraint(equalTo: safeArea.widthAnchor, multiplier: 0.75).isActive = true
        redLabel.widthAnchor.constraint(equalTo: safeArea.widthAnchor, multiplier: 0.75).isActive = true
        
        // now, let's constrain redLabel to the bottom
        redLabel.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
        
        // tell the Bottom of blueLabel to stay Above the top of redLabel
        blueLabel.bottomAnchor.constraint(lessThanOrEqualTo: redLabel.topAnchor, constant: 0.0).isActive = true
        
        // next, constrain the top of blueLabel to the top
        let blueLabelTop = blueLabel.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 0.0)
        
        // blueLabel height must be At Least 10% of the view
        let blue10 = blueLabel.heightAnchor.constraint(greaterThanOrEqualTo: safeArea.heightAnchor, multiplier: 0.10)
        
        // blueLabel should be 50% if possible -- so we'll set the
        //  Priority on that constraint to less than Required
        let blue50 = blueLabel.heightAnchor.constraint(equalTo: safeArea.heightAnchor, multiplier: 0.50)
        blue50.priority = .defaultHigh

        // start redLabel Height at 100-pts
        adjustableLabelHeightConstraint = redLabel.heightAnchor.constraint(equalToConstant: 100.0)
        // we'll be increasing the Height constant past the available area,
        //  so we also need to change its Priority so we don't get
        //  auto-layout conflict errors
        // and, we need to set it GREATER THAN blueLabel's height priority
        adjustableLabelHeightConstraint.priority = UILayoutPriority(rawValue: blue50.priority.rawValue + 1)
        
        // activate those constraints
        NSLayoutConstraint.activate([blueLabelTop, blue10, blue50, adjustableLabelHeightConstraint])

        // add a tap gesture recognizer so we can increas the height of the label
        let t = UITapGestureRecognizer(target: self, action: #selector(self.gotTap(_:)))
        view.addGestureRecognizer(t)
        
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        viewSafeAreaHeight = view.frame.height - (view.safeAreaInsets.top + view.safeAreaInsets.bottom)
        updateLabelText()
    }
    
    @objc func gotTap(_ g: UITapGestureRecognizer) -> Void {
        adjustableLabelHeightConstraint.constant += 50
        updateLabelText()
    }
    
    func updateLabelText() -> Void {
        let blueHeight = blueLabel.frame.height
        let redHeight = redLabel.frame.height
        let redConstant = adjustableLabelHeightConstraint.constant
        
        let percentFormatter            = NumberFormatter()
        percentFormatter.numberStyle    = .percent
        percentFormatter.minimumFractionDigits = 2
        percentFormatter.maximumFractionDigits = 2
        
        guard let bluePct = percentFormatter.string(for: blueHeight / viewSafeAreaHeight) else { return }
        
        var s = "SafeArea Height: \(viewSafeAreaHeight)"
        s += "\n"
        s += "Blue Height: \(blueHeight)"
        s += "\n"
        s += "\(blueHeight) / \(viewSafeAreaHeight) = \(bluePct)"
        blueLabel.text = s
        
        s = "Tap to increase..."
        s += "\n"
        s += "Red Height Constant: \(redConstant)"
        s += "\n"
        s += "Red Actual Height: \(redHeight)"
        redLabel.text = s
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM