删除所有影响UIView的约束

时间:2014-06-25 21:27:21

标签: ios objective-c uiview constraints autolayout

我有一个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。此外,我需要确保在正确的视图上调用我的删除约束的调用。例如,假设myViewsLeftConstraintviewA所有。如果我不小心打电话给[self.view removeConstraint:self.myViewsLeftConstraint],就不会发生任何事情。

注意:方法constraintsAffectingLayoutForAxis看起来很有希望,但仅用于调试目的。


更新:我收到的许多答案都涉及self.constraintsself.superview.constraints或其中的一些变体。这些解决方案不起作用,因为这些方法仅返回视图的拥有约束,而不是影响视图的约束。

要澄清这些解决方案的问题,请考虑以下视图层次结构:

  • 祖父

现在假设我们创建了以下约束,并始终将它们附加到最近的共同祖先:

  • C0:我:与Son(由我拥有)相同的顶部
  • C1:我:宽度= 100(由我拥有)
  • C2:我:与兄弟(父亲拥有)相同的高度
  • C3:我:与叔叔(由祖父拥有)相同的顶部
  • C4:我:和祖父一样(由祖父拥有)
  • C5:兄弟:和父亲一样(由父亲拥有)
  • C6:叔叔:和祖父一样(由祖父拥有)
  • C7:儿子:和女儿一样(由我拥有)

现在假设我们要删除所有影响Me的约束。任何正确的解决方案都应该删除[C0,C1,C2,C3,C4]而不是其他任何内容。

如果我使用self.constraints(自我就是我),我将获得[C0,C1,C7],因为这些是我拥有的唯一约束。显然,删除它是不够的,因为它缺少[C2,C3,C4]。此外,它正在不必要地删除C7

如果我使用self.superview.constraints(自我就是我),我将获得[C2,C5],因为这些是父拥有的约束。显然,由于C5Me完全无关,我们无法删除所有这些内容。

如果我使用grandfather.constraints,我会得到[C3,C4,C6]。同样,我们无法删除所有这些,因为C6应保持完整。

蛮力方法是遍历每个视图的祖先(包括其自身),并查看firstItemsecondItem是否为视图本身;如果是这样,删除该约束。这将导致一个正确的解决方案,返回[C0,C1,C2,C3,C4],只返回那些约束。

但是,我希望有一个更优雅的解决方案,而不是遍历整个祖先列表。

16 个答案:

答案 0 :(得分:48)

这种方法对我有用:

#!/bin/bash
exec scala -J-mx4g "$0" "$@"
!#

import scala.collection.JavaConversions._
import edu.stanford.nlp.pipeline._
import java.util._

val props = new Properties()
props.put("annotators", Seq("tokenize", "ssplit", "pos","parse","sentiment").mkString(", "))
props.put("parse.model", "edu/stanford/nlp/models/srparser/englishSR.ser.gz");
val pipeline = new StanfordCoreNLP(props)

完成后,您的视图仍然保持原样,因为它会创建自动调整约束。当我不这样做时,视图通常会消失。此外,它不仅从superview中删除约束,而且一直遍历,因为在祖先视图中可能存在影响它的约束。


Swift 4版本

@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

答案 1 :(得分:39)

到目前为止,我找到的唯一解决方案是从超级视图中删除视图:

[view removeFromSuperview]

这看起来会删除影响其布局的所有约束,并且可以添加到superview并附加新的约束。但是,它也会错误地从层次结构中删除任何子视图,并错误地删除[C7]

答案 2 :(得分:39)

您可以通过执行以下操作删除视图中的所有约束:

[_cell.contentView removeConstraints:_cell.contentView.constraints];

编辑:要删除所有子视图的约束,请在Swift中使用以下扩展名:

extension UIView {
    func clearConstraints() {
        for subview in self.subviews {
            subview.clearConstraints()
        }
        self.removeConstraints(self.constraints)
    }
}

答案 3 :(得分:13)

在斯威夫特:

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)
    }
}

答案 4 :(得分:12)

根据 Apple开发人员文档

,有两种方法可以实现这一目标

1。 NSLayoutConstraint.deactivateConstraints

  

这是一种方便的方法,提供了一种简单的方法来停用a   一次调用的约束集。这种方法的效果是   与将每个约束的isActive属性设置为false相同。   通常,使用此方法比停用每个方法更有效   单独约束。

// Declaration
class func deactivate(_ constraints: [NSLayoutConstraint])

// Usage
NSLayoutConstraint.deactivate(yourView.constraints)

2。 UIView.removeConstraints(已弃用> = iOS 8.0)

  

在为iOS 8.0或更高版本开发时,请使用NSLayoutConstraint   class的deactivateConstraints:方法而不是调用   removeConstraints:方法直接。 deactivateConstraints:方法   自动从正确的视图中删除约束。

// Declaration
func removeConstraints(_ constraints: [NSLayoutConstraint])`

// Usage
yourView.removeConstraints(yourView.constraints)

提示

使用StoryboardXIB在配置场景中提到的约束时会非常困难,您必须为要删除的每个约束创建IBOutlet。即便如此,大部分时间Interface Builder都会产生比解决方案更多的麻烦。

因此,当具有非常动态的内容和不同的视图状态时,我建议:

  1. 以编程方式创建视图
  2. 布局并使用NSLayoutAnchor
  3. 将稍后可能删除的每个约束附加到数组
  4. 每次应用新状态前清除它们
  5. 简单代码

    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()
    }
    

    参考

    1. NSLayoutConstraint
    2. UIView

答案 5 :(得分:3)

详细

  • Xcode 10.2.1(10E1001),Swift 5

解决方案

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()

答案 6 :(得分:2)

我使用以下方法从视图中删除所有约束:

.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,这是一个功能强大的框架类,您可以在需要以编程方式正确处理约束时使用它。

答案 7 :(得分:1)

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中详述的超级视图是不够好的,你可能最终会变坏后来感到惊讶。

答案 8 :(得分:1)

基于之前的答案(swift 4)

如果您不想抓取整个层次结构,可以使用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
}
}

答案 9 :(得分:0)

您可以使用以下内容:

[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];

基本上,这是对viewA超级视图的所有约束的枚举,并删除了与viewA相关的所有约束。

然后,第二部分使用viewA的约束数组从viewA中删除约束。

答案 10 :(得分:0)

(截至2017年7月31日)

SWIFT 3

self.yourCustomView.removeFromSuperview()
self.yourCustomViewParentView.addSubview(self.yourCustomView)

目标C

[self.yourCustomView removeFromSuperview];
[self.yourCustomViewParentView addSubview:self.yourCustomView];

这是快速删除UIView上存在的所有约束的最简单方法。请确保将UIView添加回新的约束条件或之后的新框架=)

答案 11 :(得分:0)

使用ObjectiveC

[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];
}

答案 12 :(得分:0)

更简单有效的方法是从superView中删除视图,然后再次重新添加为子视图。 这会导致所有子视图约束都被自动删除。?

答案 13 :(得分:0)

使用可重复使用的序列

我决定以一种更“可重用”的方式来解决这个问题。由于找到影响视图的所有约束是以上所有方面的基础,因此我决定实现一个自定义序列,将所有约束以及拥有的视图全部返回给我。

首先要做的是在Arrays的{​​{1}}上定义一个扩展名,以返回影响特定视图的所有元素。

NSLayoutConstraint

然后,我们以自定义序列使用该扩展名,该扩展名将返回影响该视图的所有约束以及实际拥有它们的视图(可以在视图层次结构中的任何位置)

public extension Array where Element == NSLayoutConstraint {

    func affectingView(_ targetView:UIView) -> [NSLayoutConstraint] {

        return self.filter{

            if let firstView = $0.firstItem as? UIView,
                firstView == targetView {
                return true
            }

            if let secondView = $0.secondItem as? UIView,
                secondView == targetView {
                return true
            }

            return false
        }
    }
}

最后,我们在public struct AllConstraintsSequence : Sequence { public init(view:UIView){ self.view = view } public let view:UIView public func makeIterator() -> Iterator { return Iterator(view:view) } public struct Iterator : IteratorProtocol { public typealias Element = (constraint:NSLayoutConstraint, owningView:UIView) init(view:UIView){ targetView = view currentView = view currentViewConstraintsAffectingTargetView = currentView.constraints.affectingView(targetView) } private let targetView : UIView private var currentView : UIView private var currentViewConstraintsAffectingTargetView:[NSLayoutConstraint] = [] private var nextConstraintIndex = 0 mutating public func next() -> Element? { while(true){ if nextConstraintIndex < currentViewConstraintsAffectingTargetView.count { defer{nextConstraintIndex += 1} return (currentViewConstraintsAffectingTargetView[nextConstraintIndex], currentView) } nextConstraintIndex = 0 guard let superview = currentView.superview else { return nil } self.currentView = superview self.currentViewConstraintsAffectingTargetView = currentView.constraints.affectingView(targetView) } } } } 上声明一个扩展名,以在一个简单的属性中公开影响它的所有约束,您可以使用简单的for-each语法对其进行访问。

UIView

现在,我们可以迭代影响视图的所有约束,并使用它们进行操作...

列出他们的标识符...

extension UIView {

    var constraintsAffectingView:AllConstraintsSequence {
        return AllConstraintsSequence(view:self)
    }
}

停用它们...

for (constraint, _) in someView.constraintsAffectingView{
    print(constraint.identifier ?? "No identifier")
}

或将其完全删除...

for (constraint, _) in someView.constraintsAffectingView{
    constraint.isActive = false
}

享受!

答案 14 :(得分:0)

快速

以下UIView扩展将删除视图的所有Edge约束:

extension UIView {
    func removeAllConstraints() {
        if let _superview = self.superview {
            self.removeFromSuperview()
            _superview.addSubview(self)
        }
    }
}

答案 15 :(得分:-1)

这是从特定视图中禁用所有约束的方法

 NSLayoutConstraint.deactivate(myView.constraints)