iOS中NSLayoutConstraint优先级能否在required与非required间修改
正如NSLayoutConstraint优先级相关的官方文档中所述:
优先级不得在非required与required之间相互切换。
但我实际尝试该操作时却成功生效,示例代码如下:
var centerConstraint: UILayoutConstraint! var topConstraint: UILayoutConstraint! signInButton = UIButton(type: .system) // Some setting code ... signUpView = UIView() // Some setting code ... signInView.addSubview(signInButton) centerConstraint = signInButton.centerXAnchor.constraint(equalTo: signInView.centerXAnchor) centerConstraint.isActive = true topConstraint = signInButton.centerXAnchor.constraint(equalTo: signInView.leadingAnchor) topConstraint.priority = .defaultHigh topConstraint.isActive = true
我创建了signInButton与signInView,其中signInButton有两个约束:一个使用默认的.required优先级,另一个优先级为.defaultHigh。
后续当用户点击按钮时,我会修改centerConstraint的优先级为:
centerConstraint.priority = .defaultLow
该操作在iOS 13与iOS 14模拟器中均可正常运行,与文档描述不符:
若在约束被添加到视图后,将iOS的
UILayoutPriorityRequired(macOS中为required优先级)修改为更低优先级,或将低优先级修改为required优先级,系统应抛出异常。
我想知道是否是官方文档内容已过期?目前是否允许在required与非required之间修改NSLayoutConstraint的优先级?
问题解答
文档描述的规则在iOS 12及更早版本是强制生效的——只要对已激活的约束跨required/非required档位修改优先级,系统会直接触发断言崩溃。
从iOS 13开始,Apple 悄悄移除了这个硬校验,不会再对这类操作抛异常,也不拦截修改,这就是你在iOS 13、14模拟器上运行不报错的根本原因。
文档没有过期,只是没同步系统行为的变更,而且至今仍然不推荐这种操作,原因很实际:
- Auto Layout的约束求解器的设计逻辑里,
required(优先级值1000)的约束属于“必须无条件满足”的不可变集合,一旦在约束激活后修改这部分的优先级,会直接打破求解器的预期状态。 - 你现在没出问题,只是因为当前的约束组合刚好没有硬冲突:把原来required的居中约束改成
.defaultLow,和已有的.defaultHigh的贴边对齐约束共存时,高优先级约束自然生效,低优先级约束被忽略,整个求解过程是自洽的。 - 如果反过来操作——把一个已激活的非required约束直接改成required,而当前视图层级已经存在同方向的required约束,求解器不会崩溃,但会随机打破其中一个约束的布局,还不会输出清晰的冲突日志,这类问题排查成本极高。
另外你代码里有个笔误:UIKit里根本没有UILayoutConstraint这个类型,正确类型是NSLayoutConstraint,只是因为你用了隐式解包可选,编译器没在声明阶段报错而已。
工程上的通用正确做法是:提前声明好两套需要切换的约束,通过修改isActive属性来切换布局,不要在运行时跨required档位修改已激活约束的优先级。
内容的提问来源于stack exchange,提问作者migrant

