UIViewに影響を与えるすべての制約を削除します


90

いくつかの制約を介して画面に配置されるUIViewがあります。一部の制約はスーパービューによって所有され、その他の制約は他の祖先によって所有されます(たとえば、UIViewControllerのviewプロパティ)。

これらの古い制約をすべて削除し、新しい制約を使用して新しい場所に配置したいと思います。

すべての制約に対してIBOutletを作成せず、どのビューがその制約を所有しているかを覚えておく必要なしに、これを行うにはどうすればよいですか?

詳述すると、単純なアプローチは、制約ごとにIBOutletsの束を作成し、次のようなコードを呼び出すことです。

[viewA removeConstraint:self.myViewsLeftConstraint];
[viewB removeConstraint:self.myViewsTopConstraint];
[viewB removeConstraint:self.myViewsBottomConstraint];
[self.view removeConstraint:self.myViewsRightConstraint];

このコードの問題は、最も単純な場合でも、2つのIBOutletを作成する必要があることです。複雑なレイアウトの場合、これは4つまたは8つの必要なIBOutletsに簡単に到達する可能性があります。さらに、制約を削除するための呼び出しが適切なビューで呼び出されていることを確認する必要があります。たとえば、myViewsLeftConstraintがによって所有されていると想像してくださいviewA。誤って電話をかけても[self.view removeConstraint:self.myViewsLeftConstraint]、何も起こりません。

注:メソッドconstraintsAffectingLayoutForAxisは有望に見えますが、デバッグのみを目的としています。


アップデート:私は契約を受けています回答の多くはself.constraintsself.superview.constraintsこれらの、またはいくつかの変種。これらのメソッドは、ビューに影響を与える制約ではなく、ビューが所有する制約のみを返すため、これらのソリューションは機能しません。

これらのソリューションの問題を明確にするために、次のビュー階層を検討してください。

  • 祖父
    • お父さん
        • 息子
    • おじさん

ここで、次の制約を作成し、それらを常に最も近い共通の祖先にアタッチするとします。

  • C0:私:息子と同じトップ(私が所有)
  • C1:私:幅= 100(私が所有)
  • C2:私:ブラザーと同じ高さ(父が所有)
  • C3:私:おじさんと同じトップ(祖父が所有)
  • C4:私:祖父と同じ左(祖父が所有)
  • C5:兄弟:父と同じ左(父が所有)
  • C6:おじさん:おじいさんと同じ左(おじいさんが所有)
  • C7:息子:娘と同じ左(私が所有)

ここで、に影響を与えるすべての制約を削除したいとしますMe。適切な解決策は削除する必要が[C0,C1,C2,C3,C4]あり、他には何もありません。

self.constraints(selfがMeである場合)を使用[C0,C1,C7]すると、Meが所有する唯一の制約であるため、が取得されます。明らかに、これが欠落しているため、これを削除するだけでは十分ではありません[C2,C3,C4]。さらに、C7不必要に取り外しています。

私がself.superview.constraints(自己が私である場合)を使用する場合[C2,C5]、それらは父が所有する制約であるため、私は取得します。とC5はまったく関係がないため、明らかにこれらすべてを削除することはできませんMe

を使用するとgrandfather.constraints、取得し[C3,C4,C6]ます。繰り返しますが、そのC6ままにしておく必要があるため、これらすべてを削除することはできません。

強引なアプローチは、ビューの祖先(それ自体を含む)のそれぞれをループし、ビュー自体であるかどうfirstItemsecondItemを確認することです。その場合は、その制約を削除します。これにより、正しい解が返され[C0,C1,C2,C3,C4]、が返され、それらの制約のみが返されます。

ただし、祖先のリスト全体をループするよりも、より洗練されたソリューションがあることを望んでいます。


削除したいすべての制約に識別子を付けるのはどうですか?このようにあなたはそれらのための出口を保つ必要はありません。
nsuinteger 2016

回答:


71

このアプローチは私のために働いた:

@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

実行が完了すると、自動サイズ変更の制約が作成されるため、ビューは元の場所に残ります。これを行わないと、通常、ビューが消えます。さらに、スーパービューから制約を削除するだけでなく、祖先ビューで制約に影響を与える可能性があるため、ずっと上に移動します。


Swift4バージョン

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

3
これが公式の答えになるはずです。
ジェイソンクロッカー2015

なぜself.translatesAutoresizingMaskIntoConstraints = YESがあるのですか。?制約付きで設定しているビューに対して、文字通りこれを望んでいませんか?
lensovet 2015

4
制約を削除しているので、他の制約を削除した後で自動サイズ変更制約を使用して、コード内にその行がないとビューが表示されないようにします
marchinram 2015

デバッグの目的でビューのすべての制約をログに記録する必要があり、そのためにこの回答を少し変更することができました。+1
chiliNUT 2016年

これにより、が誤って削除されC7ます。あなたが削除する場合しかし、それは修正が容易であるべき[self removeConstraints:self.constraints];との変化UIView *superview = self.superview;UIView *superview = self;
意味のある2018

50

私がこれまでに見つけた唯一の解決策は、そのスーパービューからビューを削除することです。

[view removeFromSuperview]

これは、レイアウトに影響を与えるすべての制約を削除し、スーパービューに追加して新しい制約を追加する準備ができているように見えます。ただし、階層からサブビューも誤って削除され、誤って削除されます[C7]


制約のfirstItemプロパティとsecondItemプロパティを使用して、それらがビューに適用されるかどうかを確認し、ビュー階層をループしてすべてを見つけることができます。ただし、削除して再追加する方が良い解決策だと思います。これは非常に悪いことのように思われるので、あなたのユースケースを見てみたいと思います。他のビューが適切にレイアウトするためにこれらの制約に依存している可能性があるため、制約は簡単に完全に無効になる可能性があります。
bjtitus 2014年

@bjtitus:良い点です。ただし、この特定のケースでは、ビューに依存しないビューを再配置しています。配置場所を知るために他のビューを使用し、配置場所を知るためにそれを使用するビューはありません。
意味のある2014年

唯一の解決策ではありませんが、非常に簡単です。私のユースケースでは、ビューをスーパービューから削除し、後で再度追加しました。
マリアンチェルニー

49

これを行うことにより、ビュー内のすべての制約を削除できます。

self.removeConstraints(self.constraints)

編集:すべてのサブビューの制約を削除するには、Swiftで次の拡張機能を使用します。

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

22
このソリューションの問題は、このビューが所有する制約(幅や高さなど)のみが削除されることです。先行制約や上位制約などは削除されません。
意味のある2014年

2
上記のコメントを確認していません。ただし、これは上記と同じかそれ以上に機能すると思います。[NSLayoutConstraint deactivateConstraints:self.constraints];
emdog4 2014年

2
それは元の解決策と同じ問題に苦しむでしょう。self.constraintsself影響するすべての制約ではなく、所有する制約のみを返しますself
意味のある2014年

1
私はあなたが今言っていることを理解しています。私の答えでは、UITableViewCellのcontentViewから制約を削除しています。次に、メソッドupdateConstraintsは、すべてのサブビューに制約を追加し、レイアウトをリセットします。上記の最初のコメントでは、self.view.constraintsと入力する必要がありました。self.viewとself.contentViewはどちらも、ビュー階層の最上位にあります。self.viewまたはself.contentViewでtranslatesAutoresizingMasksToConstraints = YESを設定することはお勧めできません。;-)。addSubviewの呼び出しが好きではありません。updateConstraintsメソッドでは、階層からビューを削除する必要がないようです。
emdog4 2014年

質問を更新して、このようなソリューションが私が探しているものに対して機能しない理由を示しました。UITableViewCell内にいるとすると、contentViewに相当するのはGrandfatherです。したがって、ソリューションはを誤って削除し[C6]、を誤って削除しません[C0,C1,C2]
意味のある2015

20

Apple Developer Documentationによると、これを達成する方法は2つあります。

1.1。 NSLayoutConstraint.deactivateConstraints

これは、1回の呼び出しで一連の制約を非アクティブ化する簡単な方法を提供する便利なメソッドです。このメソッドの効果は、各制約のisActiveプロパティをfalseに設定するのと同じです。通常、この方法を使用すると、各制約を個別に非アクティブ化するよりも効率的です。

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

// Usage
NSLayoutConstraint.deactivate(yourView.constraints)

2. UIView.removeConstraints(iOS 8.0以上では非推奨)

iOS 8.0以降用に開発する場合は、removeConstraints:メソッドを直接呼び出すのではなく、NSLayoutConstraintクラスのdeactivateConstraints:メソッドを使用してください。deactivateConstraints:メソッドは、正しいビューから制約を自動的に削除します。

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

// Usage
yourView.removeConstraints(yourView.constraints)

チップ

StoryboardsまたはXIBsを使用すると、シナリオで説明したように制約を構成するのが非常に面倒になる可能性があります。削除する制約ごとにIBOutletsを作成する必要があります。それでも、ほとんどの場合Interface Builder場合、解決するよりも多くの問題が発生します。

したがって、非常に動的なコンテンツとさまざまなビューの状態がある場合は、次のことをお勧めします。

  1. プログラムでビューを作成する
  2. それらをレイアウトし、NSLayoutAnchorを使用します
  3. 後で削除される可能性のある各制約を配列に追加します
  4. 新しい状態を適用する前に、毎回それらをクリアしてください

シンプルなコード

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

2
注意してください。固有のコンテンツサイズを指定するビューには、iOSによってNSContentSizeLayoutConstraintが自動的に追加されます。通常のyourView.constraintsプロパティにアクセスしてこれを削除すると、これらのビューの自動レイアウトが壊れます。
TigerCoding 2017

このソリューションの問題は、このビューが所有する制約(たとえば、幅と高さ)のみが削除されることです。先行制約や上位制約などは削除されません。
意味のある2018年

ええ、Sensefulが言ったこと。これらの方法は存在しますが、質問が実際に求めている問題をそれ自体で解決するわけではありません。
GSnyder 2018

18

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

ビューの「superview!」にスーパービューがない場合、これはクラッシュします。パーツを「
ifletsuperview

1
これはすべての制約を取り除くわけではありません。制約は、影響を受ける2つのビューの最も近い親を取得するため、このビューのスーパービューを共有しない制約を作成しても、その制約は削除されません...
vrwim 2016

2
おそらくもっと正しいのはif c.firstItem === self、キャストする代わりにIDを比較しas? UIView、同等性をチェックすることです==
user1244109 2016年

おそらくremoveConstraintsを使用しないでください:from apple doc:// removeConstraintsメソッドは将来のリリースで非推奨になるため、回避する必要があります。代わりに、+ [NSLayoutConstraint deactivateConstraints:]を使用してください。
eonist 2018

@eonist答えは3年前です。批判の代わりにあなたはそれを編集するべきです。
アレキサンダーヴォルコフ2018年

5

詳細

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

1
なかなかいいです。
eonist 2018

2

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

2つの要素間の制約は共通の祖先によって保持されているため、すべての親を確認することが重要です。したがって、この回答で詳しく説明されているようにスーパービューをクリアするだけでは不十分であり、後で悪い驚きを感じる可能性があります。


2

以前の回答に基づく(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
}
}

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

また、私のブログでこの投稿読んで、内部でどのように機能するかをよりよく理解することもできます。

よりきめ細かい制御が必要な場合は、プログラムで制約を適切に処理する必要があるときにいつでも使用できる強力なフレームワーククラスであるMasonryに切り替えることを強くお勧めします。


2

より簡単で効率的なアプローチは、superViewからビューを削除し、サブビューとして再度追加することです。これにより、すべてのサブビュー制約が自動的に削除されます。😉


1

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

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に関連するすべての制約を削除します。

次に、2番目の部分では、viewAの制約の配列を使用して、viewAから制約を削除します。


1
このソリューションの問題は、スーパービューが所有する制約のみが削除されることです。ビューに影響を与えるすべての制約が削除されるわけではありません。(たとえば、スーパービューのスーパービューに制約を簡単に設定できますが、このソリューションを使用しても削除されません。)
Senseful

このソリューションが機能しない理由を示すために、質問を更新しました。これは、前述のブルートフォースソリューションに最も近いため、正しいことに最も近いものです。ただし、このソリューションは誤って削除し[C7]、誤って削除しません[C3,C4]
意味のある2015

階層をクロールするメソッドを設計できます。ただし、とにかく即時監視の上に制約を追加する必要があるため、このソリューションで十分です。IMO
eonist 2018

0

(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を新しい制約または新しいフレームで追加し直してください=)


0

再利用可能なシーケンスの使用

私はこれにもっと「再利用可能な」方法でアプローチすることにしました。ビューに影響を与えるすべての制約を見つけることが上記のすべての基礎であるため、所有するビューとともに、それらすべてを返すカスタムシーケンスを実装することにしました。

最初に行うべきことは、上の拡張定義でArraysNSLayoutConstraint特定のビューに影響を与えるすべての要素そのリターンを。

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

最後に、拡張機能を宣言して、UIViewそれに影響を与えるすべての制約を、単純なfor-each構文でアクセスできる単純なプロパティで公開します。

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
}

またはそれらを完全に削除します...

for (constraint, owningView) in someView.constraintsAffectingView{
    owningView.removeConstraints([constraint])
}

楽しい!


0

迅速

UIView Extensionを実行すると、ビューのすべてのEdge制約が削除されます。

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

-1

これは、特定のビューからすべての制約を無効にする方法です

 NSLayoutConstraint.deactivate(myView.constraints)

このソリューションの問題は、このビューが所有する制約(たとえば、幅と高さ)のみが削除されることです。先行制約や上位制約などは削除されません。
意味のある2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.