为什么 UIButton 不返回正确的约束?
Why isn't UIButton returning correct constraints?
在我下面的代码中:
我在垂直滚动视图中添加了 5 个按钮。每个按钮都被限制在 scrollViews 的顶部 + 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 class 中。开箱即用。我的代码是
代码的副产品
这是因为视图和它的 superView 之间的约束被添加到 superView ,如果它们是静态添加到 UIButton
的,你只能看到 height/width 约束,看看 Vandad 的这张图IOS 书
看到这个Demo
以下是关于您的代码的几条评论:
- 您从未对任何视图添加任何约束,因此您不应该删除它们。 iOS (CocoaTouch) 将这些约束添加到这些视图中,所以请不要触摸它们。 (换句话说:当你没有调用
addConstraint
时不要调用 removeConstraint
。您对约束的控制是 activating 和 deactivating 它们。将 adding 和 removing 保留为 iOS.
- 当您激活一个约束时,约束被添加(通过 iOS)到约束中提到的两个项目的最常见的祖先。所以如果这两个视图是兄弟,它会被添加到parent。如果两个视图是parent和child,约束将被添加到parent。如果这两个视图是grandparent和grandchild,则将其添加到grandparent。如果两个视图是表亲,则约束将添加到它们的共同 grandparent.
这几行代码:
let btnHeight = theBtn.heightAnchor.constraint(equalTo: scrollView.heightAnchor)
if btn == b1{
b1HeightConstraint = btnHeight
}
正在创建新约束并将其分配给 b1HeightConstraint
,但您从未激活此约束,因此它根本没有添加到任何视图。因此,尝试删除它永远不会奏效,因为该约束仅存在于您的 b1HeightConstraint
属性 中。因为它从未被激活,所以它实际上并没有约束任何东西。
如果你想缩小一个按钮,你需要执行以下操作之一:a) 修改其高度约束的 constant
属性 OR b) 将其高度约束的 isActive
属性 设置为 false
然后给它一个新的高度约束 OR c) 修改活动约束的优先级 Auto Layout 选择使用不同的约束。
在您的视图调试层次结构中,显示的所有约束都是活动约束(意味着它们可供 Auto Layout 使用)。灰色的是自动布局选择不使用的那些,因为更高优先级的约束优先于它。这不会引起冲突。 self.height = 34 (content size)
约束由系统添加以解决 内容压缩 和 内容拥抱 。 UIButton
s 以 750
的优先级抵抗压缩,以 250
的优先级抵抗扩展。 self.height = 34 (content size)
约束变灰是因为内容拥抱的优先级为 250
并且使用了另一个更高优先级的约束(将按钮的高度设置为等于 scrollView 的高度的约束具有优先级 1000
).
更新代码:
这是您修改后的代码。我改变了两件事:
- 我确定
b1HeightConstraint
是一个激活的约束。
- 我更改了
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)
}
}
缩小按钮高度的选项
设置高度限制constant
属性
// shrink button's height by 200 points
b1HeightConstraint?.constant -= 200
停用旧约束并创建并激活新约束
// make button height 20 points
b1HeightConstraint?.isActive = false
b1HeightConstraint = sender.heightAnchor.constraint(equalToConstant: 20)
b1HeightConstraint?.isActive = true
更改高度约束的优先级
// 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
工作得很好)因为 Auto Layout 不会让您更改活动约束的优先级(如果需要)(优先级 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
在我下面的代码中:
我在垂直滚动视图中添加了 5 个按钮。每个按钮都被限制在 scrollViews 的顶部 + 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 class 中。开箱即用。我的代码是
这是因为视图和它的 superView 之间的约束被添加到 superView ,如果它们是静态添加到 UIButton
的,你只能看到 height/width 约束,看看 Vandad 的这张图IOS 书
看到这个Demo
以下是关于您的代码的几条评论:
- 您从未对任何视图添加任何约束,因此您不应该删除它们。 iOS (CocoaTouch) 将这些约束添加到这些视图中,所以请不要触摸它们。 (换句话说:当你没有调用
addConstraint
时不要调用removeConstraint
。您对约束的控制是 activating 和 deactivating 它们。将 adding 和 removing 保留为 iOS. - 当您激活一个约束时,约束被添加(通过 iOS)到约束中提到的两个项目的最常见的祖先。所以如果这两个视图是兄弟,它会被添加到parent。如果两个视图是parent和child,约束将被添加到parent。如果这两个视图是grandparent和grandchild,则将其添加到grandparent。如果两个视图是表亲,则约束将添加到它们的共同 grandparent.
这几行代码:
let btnHeight = theBtn.heightAnchor.constraint(equalTo: scrollView.heightAnchor) if btn == b1{ b1HeightConstraint = btnHeight }
正在创建新约束并将其分配给
b1HeightConstraint
,但您从未激活此约束,因此它根本没有添加到任何视图。因此,尝试删除它永远不会奏效,因为该约束仅存在于您的b1HeightConstraint
属性 中。因为它从未被激活,所以它实际上并没有约束任何东西。如果你想缩小一个按钮,你需要执行以下操作之一:a) 修改其高度约束的
constant
属性 OR b) 将其高度约束的isActive
属性 设置为false
然后给它一个新的高度约束 OR c) 修改活动约束的优先级 Auto Layout 选择使用不同的约束。在您的视图调试层次结构中,显示的所有约束都是活动约束(意味着它们可供 Auto Layout 使用)。灰色的是自动布局选择不使用的那些,因为更高优先级的约束优先于它。这不会引起冲突。
self.height = 34 (content size)
约束由系统添加以解决 内容压缩 和 内容拥抱 。UIButton
s 以750
的优先级抵抗压缩,以250
的优先级抵抗扩展。self.height = 34 (content size)
约束变灰是因为内容拥抱的优先级为250
并且使用了另一个更高优先级的约束(将按钮的高度设置为等于 scrollView 的高度的约束具有优先级1000
).
更新代码:
这是您修改后的代码。我改变了两件事:
- 我确定
b1HeightConstraint
是一个激活的约束。 - 我更改了
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)
}
}
缩小按钮高度的选项
设置高度限制
constant
属性// shrink button's height by 200 points b1HeightConstraint?.constant -= 200
停用旧约束并创建并激活新约束
// make button height 20 points b1HeightConstraint?.isActive = false b1HeightConstraint = sender.heightAnchor.constraint(equalToConstant: 20) b1HeightConstraint?.isActive = true
更改高度约束的优先级
// 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
工作得很好)因为 Auto Layout 不会让您更改活动约束的优先级(如果需要)(优先级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