【问题标题】:Why isn't UIButton returning correct constraints?为什么 UIButton 不返回正确的约束?
【发布时间】:2018-08-10 20:06:22
【问题描述】:

在我下面的代码中: 我在垂直滚动视图中添加了 5 个按钮。每个按钮都被限制在滚动视图的 top + 20 、前缘、后缘及其高度。我创建了一个b1HeightConstraint 变量。它用于保存 b1 按钮的 heightConstraint。

在单击按钮时,我正在尝试删除此约束。然而我面临一个奇怪的问题:

当我记录约束时,我只看到 2 个约束,即使我已经添加了 4 个约束。我的视图调试层次结构如下:

import UIKit
import Foundation

class ViewController: UIViewController {
    var filterView: UIView!
    var scrollView: UIScrollView!
    var containerView: UIView!

    override func loadView() {
        filterView = UIView()
        view = filterView
        view.backgroundColor = #colorLiteral(red: 0.909803926944733, green: 0.47843137383461, blue: 0.643137276172638, alpha: 1.0)

        scrollView = UIScrollView()
        scrollView.backgroundColor = #colorLiteral(red: 0.474509805440903, green: 0.839215695858002, blue: 0.976470589637756, alpha: 1.0)
        view.addSubview(scrollView)
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
        scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
        scrollView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 1).isActive = true
        scrollView.isScrollEnabled = true

        containerView = UIView()
        containerView.backgroundColor = #colorLiteral(red: 0.176470592617989, green: 0.498039215803146, blue: 0.756862759590149, alpha: 1.0)
        scrollView.addSubview(containerView)
        containerView.translatesAutoresizingMaskIntoConstraints = false

        // This is key:  connect all four edges of the containerView to
        // to the edges of the scrollView
        containerView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
        containerView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
        containerView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
        containerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true

        // Making containerView and scrollView the same height means the
        // content will not scroll vertically
        containerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
    }



    let b1 = Buttons(titleText: "one")
    let b2 = Buttons(titleText: "two")
    let b3 = Buttons(titleText: "three")
    let b4 = Buttons(titleText: "four")
    let b5 = Buttons(titleText: "five")
    var b1HeightConstraint : NSLayoutConstraint?

    override func viewDidLoad() {
        super.viewDidLoad()


        let buttonArray = [b1, b2, b3, b4, b5]

        b1.button.addTarget(self, action: #selector(ViewController.shrink(_:)), for: .touchUpInside)

        var startPoint = containerView.topAnchor

        for btn in buttonArray {
            let theBtn = btn.button
            containerView.addSubview(theBtn)
            theBtn.translatesAutoresizingMaskIntoConstraints = false
            theBtn.topAnchor.constraint(equalTo: startPoint, constant: 20).isActive = true
            theBtn.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
            theBtn.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
            theBtn.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true

            startPoint = theBtn.bottomAnchor
            let btnHeight = theBtn.heightAnchor.constraint(equalTo: scrollView.heightAnchor)
            if btn == b1{
                b1HeightConstraint = btnHeight
            }
        }

        containerView.bottomAnchor.constraint(equalTo: startPoint, constant: 20).isActive = true

    }

    @objc func shrink(_ sender: Any){
        guard let btn = sender as? UIButton else{
            return
        }
        print("count is: \(btn.constraints.count)")

        btn.removeConstraint(b1HeightConstraint!)
        containerView.removeConstraint(b1HeightConstraint!)
        print("count is: \(btn.constraints.count)")
        containerView.updateConstraintsIfNeeded()
        containerView.updateConstraints()
        scrollView.updateConstraintsIfNeeded()
        scrollView.updateConstraints()
    }
}

class Buttons : NSObject {
    let button = UIButton()
    init(titleText: String) {
        button.backgroundColor = #colorLiteral(red: 0.976470589637756, green: 0.850980401039124, blue: 0.549019634723663, alpha: 1.0)
        button.setTitle(titleText, for: .normal)
    }
}

代码已准备好转储到 ViewController 类中。开箱即用。我的代码是 here

编写的代码的衍生代码

【问题讨论】:

  • 总结答案:1)灰色的约束是活动的但由于具有较低的优先级而“无影响” 2)创建了约束因为iOS添加了约束来匹配buttons/labels/texfield/textviews的intrinsicContentSize(如果它是一个普通的UIView那么就不会添加约束,因为它没有intrinsicContentSize 3)当你激活一个约束时,一个约束被添加( iOS)到约束中提到的两个项目的最常见的祖先。因此,您看不到添加到按钮本身的约束。它们被添加到其父级 (scrollView)
  • 4) 此外,在 Xcode 8.3.3 上,灰色约束显示 incorrect 优先级。它不是 1000。它是一个自适应优先级:Hug:250 CompressionResistance:750,即拉伸时为 250,压缩时为 750。对于较新的版本,@1000 不再出现。
  • FWIW 如果你有 UIViewAlertForUnsatisfiableConstraints 那么任何被破坏的约束都将变为非活动状态,即灰显

标签: ios swift autolayout intrinsic-content-size view-debugging


【解决方案1】:

这里有几个关于你的代码的 cmets:

  1. 您从未向任何视图添加任何约束,因此您不应该删除它们。 iOS (CocoaTouch) 将这些约束添加到这些视图中,所以请不要触摸它们。 (换句话说:当你没有打电话给addConstraint时不要打电话给removeConstraint)。您对约束的控制是激活停用它们。将添加删除留给iOS。
  2. 当您激活一个约束时,一个约束(由 iOS)添加到约束中提到的两个项目的最常见的祖先。所以如果两个视图是兄弟,它会被添加到父视图中。如果两个视图是父视图和子视图,则将约束添加到父视图。如果这两个视图是祖父母和孙子,它将被添加到祖父母。如果这两个视图是表亲,则约束将被添加到它们的共同祖父母。
  3. 这些代码行:

    let btnHeight = theBtn.heightAnchor.constraint(equalTo: scrollView.heightAnchor)
    if btn == b1{
        b1HeightConstraint = btnHeight
    }
    

    正在创建一个新约束并将其分配给b1HeightConstraint,但您从未激活此约束,因此它根本没有添加到任何视图中。因此,试图删除它永远不会起作用,因为该约束仅存在于您的 b1HeightConstraint 属性中。由于它从未被激活,它实际上并没有限制任何东西。

  4. 如果要缩小按钮,需要执行以下操作之一:a) 修改其高度约束的constant 属性OR b) 设置其高度约束的@987654327 @property to false 然后给它一个新的高度约束 c) 修改活动约束的优先级以让 Auto Layout 选择使用不同的约束。

  5. 在您的视图调试层次结构中,所有显示的约束都是活动约束(意味着它们可供自动布局使用)。灰色的那些是 Auto Layout 选择不使用的那些,因为更高优先级的约束优先于它。这不会导致冲突。系统添加了self.height = 34 (content size) 约束以说明内容压缩内容拥抱UIButtons 抗压缩优先750,抗扩展优先250self.height = 34 (content size) 约束显示为灰色,因为内容拥抱的优先级为 250,而是使用了另一个更高优先级的约束(将按钮高度设置为等于滚动视图高度的约束具有优先级 1000)。


更新代码:

这是您修改后的代码。我改变了两件事:

  1. 我确定b1HeightConstraint 是一个激活的约束。
  2. 我更改了shrink 方法以停用旧的高度约束,然后创建并激活一个新的。

更新代码

import UIKit
import Foundation

class ViewController: UIViewController {
    var filterView: UIView!
    var scrollView: UIScrollView!
    var containerView: UIView!

    override func loadView() {
        filterView = UIView()
        view = filterView
        view.backgroundColor = #colorLiteral(red: 0.909803926944733, green: 0.47843137383461, blue: 0.643137276172638, alpha: 1.0)

        scrollView = UIScrollView()
        scrollView.backgroundColor = #colorLiteral(red: 0.474509805440903, green: 0.839215695858002, blue: 0.976470589637756, alpha: 1.0)
        view.addSubview(scrollView)
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
        scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
        scrollView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 1).isActive = true
        scrollView.isScrollEnabled = true

        containerView = UIView()
        containerView.backgroundColor = #colorLiteral(red: 0.176470592617989, green: 0.498039215803146, blue: 0.756862759590149, alpha: 1.0)
        scrollView.addSubview(containerView)
        containerView.translatesAutoresizingMaskIntoConstraints = false

        // This is key:  connect all four edges of the containerView to
        // to the edges of the scrollView
        containerView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
        containerView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
        containerView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
        containerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true

        // Making containerView and scrollView the same height means the
        // content will not scroll vertically
        containerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
    }

    let b1 = Buttons(titleText: "one")
    let b2 = Buttons(titleText: "two")
    let b3 = Buttons(titleText: "three")
    let b4 = Buttons(titleText: "four")
    let b5 = Buttons(titleText: "five")
    var b1HeightConstraint : NSLayoutConstraint?

    override func viewDidLoad() {
        super.viewDidLoad()

        let buttonArray = [b1, b2, b3, b4, b5]

        b1.button.addTarget(self, action: #selector(ViewController.shrink(_:)), for: .touchUpInside)

        var startPoint = containerView.topAnchor

        for btn in buttonArray {
            let theBtn = btn.button
            containerView.addSubview(theBtn)
            theBtn.translatesAutoresizingMaskIntoConstraints = false
            theBtn.topAnchor.constraint(equalTo: startPoint, constant: 20).isActive = true
            theBtn.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
            theBtn.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
            //theBtn.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true

            startPoint = theBtn.bottomAnchor
            let btnHeight = theBtn.heightAnchor.constraint(equalTo: scrollView.heightAnchor)
            btnHeight.isActive = true
            if btn == b1{
                b1HeightConstraint = btnHeight
            }
        }

        containerView.bottomAnchor.constraint(equalTo: startPoint, constant: 20).isActive = true

    }

    @objc func shrink(_ sender: UIButton) {
        b1HeightConstraint?.isActive = false
        b1HeightConstraint = sender.heightAnchor.constraint(equalToConstant: 20)
        b1HeightConstraint?.isActive = true
    }
}

class Buttons : NSObject {
    let button = UIButton()
    init(titleText: String) {
        button.backgroundColor = #colorLiteral(red: 0.976470589637756, green: 0.850980401039124, blue: 0.549019634723663, alpha: 1.0)
        button.setTitle(titleText, for: .normal)
    }
}

缩小按钮高度的选项

  1. 设置高度约束的constant属性

    // shrink button's height by 200 points
    b1HeightConstraint?.constant -= 200
    
  2. 停用旧约束并创建并激活新约束

    // make button height 20 points
    b1HeightConstraint?.isActive = false
    b1HeightConstraint = sender.heightAnchor.constraint(equalToConstant: 20)
    b1HeightConstraint?.isActive = true
    
  3. 改变高度约束的优先级

    // Set b1HeightConstraint's priority to less than 250, and the
    // *content hugging* with priority 250 will take over and resize
    // the button to its intrinsic height
    b1HeightConstraint?.priority = UILayoutPriority(rawValue: 100)
    

    注意:为此,您必须为高度约束赋予低于 1000 的初始优先级(999 效果很好)因为自动布局不会让您更改如果需要,则为活动约束(优先级1000)。

    // Just deactivate the buttonHeight constraint and the *content
    // hugging* will take over and it will set the button's height
    // to its intrinsic height
    b1HeightConstraint?.isActive = false
    

【讨论】:

  • 您从未向任何视图添加任何约束,因此您不应该删除它们那么为什么在我的屏幕截图中该视图有 4 + 3 个约束?
  • 下一句:iOS (CocoaTouch) 将这些约束添加到这些视图中。我的意思是,如果你没有打电话给addConstraint,就不要打电话给removeConstraint
  • 对对对。 iOS 添加了 3。我最初添加的 4 被添加到父级。在我的屏幕截图中,灰色的约束是非活动的约束吗?我仍在考虑这个问题的原因是为什么这 7 个约束之间没有任何冲突。一个约束将宽度限制为 31,另一个将其限制为视图的宽度...
  • 添加额外的显式高度约束后我记录了按钮上的约束。 po b3.button.constraints ▿ 3 elements - 0 : <NSContentSizeLayoutConstraint:0x6080000b0c20 UIButton:0x7f8c3270a370'three'.width == 43 Hug:250 CompressionResistance:750 (active)> - 1 : <NSContentSizeLayoutConstraint:0x6080000b0c80 UIButton:0x7f8c3270a370'three'.height == 34 Hug:250 CompressionResistance:750 (active)> - 2 : <NSLayoutConstraint:0x600000096a80 UIButton:0x7f8c3270a370'three'.height == 200 (active)> 因此为 1:灰显的约束活动的!
  • 2.注意不是NSLayoutConstraint,而是NSContentSizeLayoutConstraint。我现在正在查找他们的差异。这就像必须用它的内在大小做一些事情......
【解决方案2】:

这是因为视图和它的 superView 之间的约束被添加到 superView ,如果它们是静态添加到 UIButton ,您只会看到高度/宽度约束,请查看 Vandad IOS Book 中的此图

看到这个Demo

【讨论】:

  • A) 如果我在 button3 和 button4 之间添加了一个约束,它会被添加到 subview2 或视图中吗?我的猜测是 subview2 B) 另外按钮的宽度和高度不应该等于滚动视图吗?如果按钮的宽度是 31,那么它不应该被拉伸到整个屏幕的宽度
  • 对于 subview2 ,scrollView 从它的 subviews 获取它的 contentSize ,所以你应该钩住前导和尾随,并从外部附加内部视图的宽度说等于 self.view这就是scrollView知道它的宽度的方式,关于从scrollview顶部到底部正确钩住元素的高度
  • (这个项目只是为了测试一些东西)。我的层次结构是:View -> ScrollView -> ContainerView -> Button。我希望按钮与屏幕的左侧和右侧对齐。你是说这样做我需要限制视图的前导/尾随?
  • 我无法打开文件。尽管如此,当我将按钮限制在滚动视图/视图的前导/尾随时,当我打印按钮的约束时,我仍然只看到问题中提到的宽度和高度。然而,这些都没有被应用,因为按钮正确地接触了屏幕的前/后边缘,这意味着按钮的宽度不是 31,它的高度也不是 34。所以我仍然对 1. 约束来自哪里感到困惑?我问这一切的原因是因为我想控制按钮的高度。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-06
  • 2013-03-08
  • 2014-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-11
相关资源
最近更新 更多