我有一个UIView,通过几个约束放在屏幕上。一些约束由superview拥有,其他约束由其他祖先拥有(例如,可能是UIViewController的view属性)。
我想删除所有这些旧约束,并使用新约束将其置于新的位置。
如果不为每个约束创建一个IBOutlet并且必须记住哪个视图拥有所述约束,我该怎么做呢?
详细说明,天真的方法是为每个约束创建一堆IBOutlet,然后涉及调用代码,例如:
[viewA removeConstraint:self.myViewsLeftConstraint];
[viewB removeConstraint:self.myViewsTopConstraint];
[viewB removeConstraint:self.myViewsBottomConstraint];
[self.view removeConstraint:self.myViewsRightConstraint];
这段代码的问题在于,即使在最简单的情况下,我也需要创建2个IBOutlet。对于复杂的布局,这可以轻松达到4或8个所需的IBOutlet。此外,我需要确保在正确的视图上调用我的删除约束的调用。例如,假设myViewsLeftConstraint
归viewA
所有。如果我不小心打电话给[self.view removeConstraint:self.myViewsLeftConstraint]
,什么都不会发生。
注意:方法constraintsAffectingLayoutForAxis看起来很有前景,但仅用于调试目的。
更新:我收到的许多答案都涉及self.constraints
,self.superview.constraints
或其中的一些变体。这些解决方案不起作用,因为这些方法只返回视图所拥有的约束,而不是影响视图的约束。
要澄清这些解决方案的问题,请考虑以下视图层次结构:
现在假设我们创建了以下约束,并始终将它们附加到最近的共同祖先:
现在假设我们要删除所有影响Me
的约束。任何适当的解决方案应该删除[C0,C1,C2,C3,C4]
,没有别的。
如果我使用self.constraints
(自我就是我),我会得到[C0,C1,C7]
,因为那些是我拥有的唯一约束。显然,这是不够的,因为它缺少[C2,C3,C4]
删除它。此外,它不必要地删除C7
。
如果我使用self.superview.constraints
(self是我),我会得到[C2,C5]
,因为那些是父拥有的约束。显然,我们无法删除所有这些,因为C5
与Me
完全无关。
如果我使用grandfather.constraints
,我会得到[C3,C4,C6]
。同样,我们无法删除所有这些,因为C6
应保持完整。
蛮力方法是遍历视图的每个祖先(包括它自己),并查看firstItem
或secondItem
是否是视图本身;如果是这样,删除该约束。这将导致一个正确的解决方案,返回[C0,C1,C2,C3,C4]
,只有那些约束。
但是,我希望有一个更优雅的解决方案,而不是遍历整个祖先列表。
这种方法对我有用:
@interface UIView (RemoveConstraints)
- (void)removeAllConstraints;
@end
@implementation UIView (RemoveConstraints)
- (void)removeAllConstraints
{
UIView *superview = self.superview;
while (superview != nil) {
for (NSLayoutConstraint *c in superview.constraints) {
if (c.firstItem == self || c.secondItem == self) {
[superview removeConstraint:c];
}
}
superview = superview.superview;
}
[self removeConstraints:self.constraints];
self.translatesAutoresizingMaskIntoConstraints = YES;
}
@end
完成后,您的视图将保持原样,因为它会创建自动调整约束。当我不这样做时,视图通常会消失。此外,它不仅从超级视图中删除约束,而且一直向上遍历,因为在祖先视图中可能存在影响它的约束。
extension UIView {
public func removeAllConstraints() {
var _superview = self.superview
while let superview = _superview {
for constraint in superview.constraints {
if let first = constraint.firstItem as? UIView, first == self {
superview.removeConstraint(constraint)
}
if let second = constraint.secondItem as? UIView, second == self {
superview.removeConstraint(constraint)
}
}
_superview = superview.superview
}
self.removeConstraints(self.constraints)
self.translatesAutoresizingMaskIntoConstraints = true
}
}
用目标C
[self.superview.constraints enumerateObjectsUsingBlock:^(__kindof NSLayoutConstraint * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLayoutConstraint *constraint = (NSLayoutConstraint *)obj;
if (constraint.firstItem == self || constraint.secondItem == self) {
[self.superview removeConstraint:constraint];
}
}];
[self removeConstraints:self.constraints];
}
你可以使用这样的东西:
[viewA.superview.constraints enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSLayoutConstraint *constraint = (NSLayoutConstraint *)obj;
if (constraint.firstItem == viewA || constraint.secondItem == viewA) {
[viewA.superview removeConstraint:constraint];
}
}];
[viewA removeConstraints:viewA.constraints];
基本上,这是对viewS超级视图的所有约束进行枚举,并删除与viewS相关的所有约束。
然后,第二部分使用viewA的约束数组从viewA中删除约束。
(截至2017年7月31日)
SWIFT 3
self.yourCustomView.removeFromSuperview()
self.yourCustomViewParentView.addSubview(self.yourCustomView)
目标C.
[self.yourCustomView removeFromSuperview];
[self.yourCustomViewParentView addSubview:self.yourCustomView];
这是快速删除UIView上存在的所有约束的最简单方法。只需确保将UIView添加回新的约束或之后的新帧=)
这是从特定视图禁用所有约束的方法
NSLayoutConstraint.deactivate(myView.constraints)
到目前为止我找到的唯一解决方案是从超视图中删除视图:
[view removeFromSuperview]
这看起来像删除了影响其布局的所有约束,并准备好添加到superview并附加新的约束。但是,它也会错误地从层次结构中删除任何子视图,并错误地摆脱[C7]
。
您可以通过执行以下操作删除视图中的所有约束:
[_cell.contentView removeConstraints:_cell.contentView.constraints];
编辑:要删除所有子视图的约束,请在Swift中使用以下扩展名:
extension UIView {
func clearConstraints() {
for subview in self.subviews {
subview.clearConstraints()
}
self.removeConstraints(self.constraints)
}
}
在Swift中:
import UIKit
extension UIView {
/**
Removes all constrains for this view
*/
func removeConstraints() {
let constraints = self.superview?.constraints.filter{
$0.firstItem as? UIView == self || $0.secondItem as? UIView == self
} ?? []
self.superview?.removeConstraints(constraints)
self.removeConstraints(self.constraints)
}
}
根据Apple Developer Documentation,有两种方法可以实现这一目标
NSLayoutConstraint.deactivateConstraints
这是一种便捷方法,通过一次调用提供了一种简单的方法来停用一组约束。此方法的效果与将每个约束的isActive属性设置为false相同。通常,使用此方法比单独停用每个约束更有效。
// Declaration
class func deactivate(_ constraints: [NSLayoutConstraint])
// Usage
NSLayoutConstraint.deactivate(yourView.constraints)
UIView.removeConstraints
(Deprecated for >= iOS 8.0)在为iOS 8.0或更高版本开发时,请使用NSLayoutConstraint类的deactivateConstraints:方法,而不是直接调用removeConstraints:方法。 deactivateConstraints:方法自动从正确的视图中删除约束。
// Declaration
func removeConstraints(_ constraints: [NSLayoutConstraint])`
// Usage
yourView.removeConstraints(yourView.constraints)
使用Storyboard
s或XIB
s在配置场景中提到的约束时会非常困难,您必须为要删除的每个约束创建IBOutlet。即便如此,大多数时候Interface Builder
造成的麻烦比它解决的还多。
因此,当具有非常动态的内容和不同的视图状态时,我建议:
简单代码
private var customConstraints = [NSLayoutConstraint]()
private func activate(constraints: [NSLayoutConstraint]) {
customConstraints.append(contentsOf: constraints)
customConstraints.forEach { $0.isActive = true }
}
private func clearConstraints() {
customConstraints.forEach { $0.isActive = false }
customConstraints.removeAll()
}
private func updateViewState() {
clearConstraints()
let constraints = [
view.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
view.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
view.topAnchor.constraint(equalTo: parentView.topAnchor),
view.bottomAnchor.constraint(equalTo: parentView.bottomAnchor)
]
activate(constraints: constraints)
view.layoutIfNeeded()
}
import UIKit
extension UIView {
func removeConstraints() { removeConstraints(constraints) }
func deactivateAllConstraints() { NSLayoutConstraint.deactivate(getAllConstraints()) }
func getAllSubviews() -> [UIView] { return UIView.getAllSubviews(view: self) }
func getAllConstraints() -> [NSLayoutConstraint] {
var subviewsConstraints = getAllSubviews().flatMap { $0.constraints }
if let superview = self.superview {
subviewsConstraints += superview.constraints.compactMap { (constraint) -> NSLayoutConstraint? in
if let view = constraint.firstItem as? UIView, view == self { return constraint }
return nil
}
}
return subviewsConstraints + constraints
}
class func getAllSubviews(view: UIView) -> [UIView] {
return view.subviews.flatMap { [$0] + getAllSubviews(view: $0) }
}
}
print("constraints: \(view.getAllConstraints().count), subviews: \(view.getAllSubviews().count)")
view.deactivateAllConstraints()
我使用以下方法从视图中删除所有约束:
.h文件:
+ (void)RemoveContraintsFromView:(UIView*)view
removeParentConstraints:(bool)parent
removeChildConstraints:(bool)child;
.m文件:
+ (void)RemoveContraintsFromView:(UIView *)view
removeParentConstraints:(bool)parent
removeChildConstraints:(bool)child
{
if (parent) {
// Remove constraints between view and its parent.
UIView *superview = view.superview;
[view removeFromSuperview];
[superview addSubview:view];
}
if (child) {
// Remove constraints between view and its children.
[view removeConstraints:[view constraints]];
}
}
您也可以在我的博客上使用read this post来更好地了解它的工作原理。
如果你需要更精细的控制,我强烈建议切换到Masonry,这是一个强大的框架类,你可以在需要以编程方式正确处理约束时使用它。
一个Swift解决方案:
extension UIView {
func removeAllConstraints() {
var view: UIView? = self
while let currentView = view {
currentView.removeConstraints(currentView.constraints.filter {
return $0.firstItem as? UIView == self || $0.secondItem as? UIView == self
})
view = view?.superview
}
}
}
重要的是要经历所有的父母,因为两个元素之间的约束是由共同的祖先所持有的,所以只需清除this answer中详述的超级视图就不够好了,你可能最终会在以后遇到不好的意外。
当您不想抓取整个层次结构时,可以使用immediateConstraints。
extension UIView {
/**
* Deactivates immediate constraints that target this view (self + superview)
*/
func deactivateImmediateConstraints(){
NSLayoutConstraint.deactivate(self.immediateConstraints)
}
/**
* Deactivates all constrains that target this view
*/
func deactiveAllConstraints(){
NSLayoutConstraint.deactivate(self.allConstraints)
}
/**
* Gets self.constraints + superview?.constraints for this particular view
*/
var immediateConstraints:[NSLayoutConstraint]{
let constraints = self.superview?.constraints.filter{
$0.firstItem as? UIView === self || $0.secondItem as? UIView === self
} ?? []
return self.constraints + constraints
}
/**
* Crawls up superview hierarchy and gets all constraints that affect this view
*/
var allConstraints:[NSLayoutConstraint] {
var view: UIView? = self
var constraints:[NSLayoutConstraint] = []
while let currentView = view {
constraints += currentView.constraints.filter {
return $0.firstItem as? UIView === self || $0.secondItem as? UIView === self
}
view = view?.superview
}
return constraints
}
}