首页 文章

删除影响UIView的所有约束

提问于
浏览
54

我有一个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] ,什么都不会发生 .

Note: 方法constraintsAffectingLayoutForAxis看起来很有前景,但仅用于调试目的 .


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

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

  • 祖父

  • 父亲

  • 儿子

  • 女儿

  • 兄弟

  • 叔叔

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

  • C0:我:和儿子一样(由我拥有)

  • C1:我:宽度= 100(由我拥有)

  • C2:我:和兄弟一样高(父亲拥有)

  • C3:我:和叔叔一样(由祖父拥有)

  • C4:我:和祖父一样(由祖父拥有)

  • C5:兄弟:和父亲一样(由父亲拥有)

  • C6:叔叔:和祖父一样(由祖父拥有)

  • C7:儿子:和女儿一样(由我拥有)

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

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

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

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

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

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

12 回答

  • 1

    这种方法对我有用:

    @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
    

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

  • 2

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

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

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

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

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

    [view removeFromSuperview]
    

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

  • 9

    在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)
        }
    }
    
  • 39

    根据 Apple Developer Documentation ,有两种方法可以实现这一目标

    1. NSLayoutConstraint.deactivateConstraints

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

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

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

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

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

    小贴士

    使用 Storyboard s或者 XIB 在配置场景中提到的约束时会非常痛苦,您必须为要删除的每个约束创建IBOutlet . 即便如此,大部分时间 Interface Builder 都会产生比它解决的更多麻烦 .

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

    • 以编程方式创建视图

    • 布局并使用NSLayoutAnchor

    • 将稍后可能删除的每个约束附加到数组

    • 每次应用新状态前清除它们

    Simple Code

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

    参考文献

  • 45

    详情

    Xcode 9.1,Swift 4

    解决方案

    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 { (view) -> [NSLayoutConstraint] in
                return view.constraints
            }
    
            if let superview = self.superview {
                subviewsConstraints += superview.constraints.flatMap{ (constraint) -> NSLayoutConstraint? in
                    if let view = constraint.firstItem as? UIView {
                        if view == self {
                            return constraint
                        }
                    }
                    return nil
                }
            }
    
            return subviewsConstraints + constraints
        }
    
        class func getAllSubviews(view: UIView) -> [UIView] {
            return view.subviews.flatMap { subView -> [UIView] in
                return [subView] + getAllSubviews(view: subView)
            }
        }
    }
    

    用法

    view.deactivateAllConstraints()
    

    完整样本

    ViewController

    import UIKit
    
    class ViewController: UIViewController {
    
        @IBOutlet weak var simpleView: UIView!
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
            sampleWithViewFromStoryBoard()
            sampleWithViewGeneratedProgrammaticallyted()
        }
    
        private func removeConstraints(view: UIView) {
            print("constraints: \(view.getAllConstraints().count), subviews: \(view.getAllSubviews().count)")
            view.deactivateAllConstraints()
            print("constraints: \(view.getAllConstraints().count), subviews: \(view.getAllSubviews().count)")
        }
    
        func sampleWithViewFromStoryBoard() {
            print("\nStoryboard:")
            removeConstraints(view: simpleView)
        }
    
        func sampleWithViewGeneratedProgrammaticallyted() {
            print("\nProgrammatically:")
            let view = UIView(frame: .zero)
            view.translatesAutoresizingMaskIntoConstraints = false
            view.backgroundColor = .blue
            self.view.addSubview(view)
    
            view.widthAnchor.constraint(equalToConstant: 200).isActive = true
            view.heightAnchor.constraint(equalToConstant: 200).isActive = true
            view.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 100).isActive = true
            view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 100).isActive = true
    
            removeConstraints(view: view)
        }
    
    }
    

    StoryBoard

    <?xml version="1.0" encoding="UTF-8"?>
    <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
        <device id="retina4_7" orientation="portrait">
            <adaptation id="fullscreen"/>
        </device>
        <dependencies>
            <deployment identifier="iOS"/>
            <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13527"/>
            <capability name="Safe area layout guides" minToolsVersion="9.0"/>
            <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
        </dependencies>
        <scenes>
            <!--View Controller-->
            <scene sceneID="tne-QT-ifu">
                <objects>
                    <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="stackoverflow_24418884" customModuleProvider="target" sceneMemberID="viewController">
                        <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                            <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                            <subviews>
                                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5Ni-DW-acW">
                                    <rect key="frame" x="16" y="20" width="240" height="128"/>
                                    <subviews>
                                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="phZ-9f-16a">
                                            <rect key="frame" x="188" y="8" width="42" height="21"/>
                                            <constraints>
                                                <constraint firstAttribute="width" constant="42" id="QHb-On-hfY"/>
                                                <constraint firstAttribute="height" constant="21" id="Sfy-Pn-YGi"/>
                                            </constraints>
                                            <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                            <nil key="textColor"/>
                                            <nil key="highlightedColor"/>
                                        </label>
                                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6gs-OS-OBa">
                                            <rect key="frame" x="97" y="98" width="46" height="30"/>
                                            <constraints>
                                                <constraint firstAttribute="height" constant="30" id="L0A-My-PAS"/>
                                            </constraints>
                                            <state key="normal" title="Button"/>
                                        </button>
                                        <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="TvD-24-wfT">
                                            <rect key="frame" x="20" y="20" width="60" height="80"/>
                                            <subviews>
                                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hIi-fL-pqH">
                                                    <rect key="frame" x="0.0" y="0.0" width="60" height="26.5"/>
                                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                                    <nil key="textColor"/>
                                                    <nil key="highlightedColor"/>
                                                </label>
                                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ahb-L9-itj">
                                                    <rect key="frame" x="0.0" y="26.5" width="60" height="27"/>
                                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                                    <nil key="textColor"/>
                                                    <nil key="highlightedColor"/>
                                                </label>
                                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="A5a-DJ-Mem">
                                                    <rect key="frame" x="0.0" y="53.5" width="60" height="26.5"/>
                                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                                    <nil key="textColor"/>
                                                    <nil key="highlightedColor"/>
                                                </label>
                                            </subviews>
                                            <constraints>
                                                <constraint firstAttribute="height" constant="80" id="uwc-lA-VQH"/>
                                                <constraint firstAttribute="width" constant="60" id="xzx-yt-Svs"/>
                                            </constraints>
                                        </stackView>
                                    </subviews>
                                    <color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
                                    <constraints>
                                        <constraint firstItem="phZ-9f-16a" firstAttribute="top" secondItem="5Ni-DW-acW" secondAttribute="top" constant="8" id="1y5-vl-Tar"/>
                                        <constraint firstAttribute="trailing" secondItem="phZ-9f-16a" secondAttribute="trailing" constant="10" id="8DX-L0-aFJ"/>
                                        <constraint firstItem="TvD-24-wfT" firstAttribute="leading" secondItem="5Ni-DW-acW" secondAttribute="leading" constant="20" id="azT-VR-ipJ"/>
                                        <constraint firstAttribute="height" constant="128" id="dDD-5k-oXP"/>
                                        <constraint firstAttribute="bottom" secondItem="6gs-OS-OBa" secondAttribute="bottom" id="jjx-wV-0pX"/>
                                        <constraint firstAttribute="width" constant="240" id="ojW-Rq-z0h"/>
                                        <constraint firstItem="TvD-24-wfT" firstAttribute="top" secondItem="5Ni-DW-acW" secondAttribute="top" constant="20" id="vgM-Do-HvO"/>
                                        <constraint firstItem="6gs-OS-OBa" firstAttribute="centerX" secondItem="5Ni-DW-acW" secondAttribute="centerX" id="wjV-CZ-xYY"/>
                                    </constraints>
                                </view>
                            </subviews>
                            <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                            <constraints>
                                <constraint firstItem="5Ni-DW-acW" firstAttribute="top" secondItem="6Tk-OE-BBY" secondAttribute="top" id="546-OU-Up3"/>
                                <constraint firstItem="5Ni-DW-acW" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="16" id="Okf-rU-y08"/>
                            </constraints>
                            <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                        </view>
                        <connections>
                            <outlet property="simpleView" destination="5Ni-DW-acW" id="a56-pn-GXE"/>
                        </connections>
                    </viewController>
                    <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
                </objects>
                <point key="canvasLocation" x="32.799999999999997" y="70.614692653673174"/>
            </scene>
        </scenes>
    </document>
    

    结果

    enter image description here

    enter image description here

  • 0

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

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

  • 34

    一个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中详述的超级视图是不够好的,并且您可能最终会在以后遇到不好的意外 .

  • 1

    (截至2017年7月31日)

    SWIFT 3

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

    Objective C

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

    这是快速删除UIView上存在的所有约束的最简单方法 . 只需确保将UIView添加回新的约束或之后的新帧=)

  • 3

    基于之前的答案(快速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
    }
    }
    
  • 1

    你可以使用这样的东西:

    [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的superview上的所有约束进行枚举,并删除与viewA相关的所有约束 .

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

  • 12

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

    NSLayoutConstraint.deactivate(myView.constraints)
    

相关问题