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

使用swift ios编程约束,如何指定默认为高度的50%,但如果需要可以缩小的视图?,ios,swift,autolayout,Ios,Swift,Autolayout,我正在使用NSLayoutConstraint约束视图。我希望它的高度在默认情况下占据屏幕的50%,但如果没有足够的空间容纳其他组件(如iphone在横向),视图可以缩小到高度的10% 我正在努力: let y1 = NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 0)

我正在使用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%

我被两件事弄糊涂了:

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


  • 我认为约束只能有一个解决方案。为什么我没有得到一个模糊性错误,因为任何从10%到50%的高度都是有效的解决方案

  • 最后,我如何得到我想要的,一个50%的视图,如果需要可以缩小


    非常感谢

    您可以通过更改50%高度约束的
    优先级来执行此操作

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

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

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

    所以

    或者,用更现代的语法

        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元素会减少可用空间,auto layout都会将按钮的高度设置为“尽可能接近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
        }
    }
    

    “为什么我不能得到一个歧义错误呢?”没有歧义错误。@matt啊,我在苹果开发者网站上读到了这个“使用自动布局时,目标是提供一系列具有且只有一个可能解的方程。不明确的约束有多个可能的解决方案。不可满足的约束没有有效的解决方案。“假设模棱两可意味着一个错误,但我想他们只是说它作为一个布局是不可取的,因为它是模棱两可的。好吧,这样想。如果一次添加一个约束,则每个语句后的约束都是不明确的。您不会因为尚未完成而希望出现错误。一只鞋穿上,不是错误;你只是没穿好衣服。这很糟糕,但可能是暂时的。两只鞋都穿错了是错误的。:)
    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
        }
    }