web-dev-qa-db-ja.com

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

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

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

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

詳細に説明すると、単純なアプローチは、各制約に対して多数のIBOutletsを作成し、次のようなコードを呼び出すことです。

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

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

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


更新:私が受け取っている回答の多くは、self.constraintsself.superview.constraints、またはそれらの変形に対処しています。これらの方法は、これらのメソッドが制約所有のみを返し、制約ではなく影響を返すため、機能しません。

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

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

次の制約を作成し、それらを常に最も近い共通の祖先にアタッチすることを想像してください。

  • C0:私:息子と同じトップ(私が所有)
  • C1:Me:width = 100(所有者はMe)
  • 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(selfはMe)を使用すると、[C2,C5]を取得します。これらは、Fatherが所有する制約であるためです。 C5Meとは完全に無関係なので、これらすべてを削除することはできません。

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

ブルートフォースアプローチは、ビューの祖先(それ自体を含む)をループし、firstItemまたはsecondItemがビュー自体であるかどうかを確認することです。その場合、その制約を削除します。これは、正しいソリューションにつながり、[C0,C1,C2,C3,C4]、およびそれらの制約のみを返します。

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

68
Senseful

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

[view removeFromSuperview]

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

41
Senseful

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

@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

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


Swift 4バージョン

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

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

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

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

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

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

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

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を使用すると、シナリオで説明したように制約を構成するのが非常に面倒になる可能性があるため、削除する各制約に対してIBOutletを作成する必要があります。それでも、ほとんどの場合、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. IView
14
E-Riddie

詳細

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

次の方法を使用して、ビューからすべての制約を削除します。

.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
Darkseal

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

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つの要素間の制約は共通の祖先によって保持されているため、すべての親を確認することが重要です。したがって、 this answer で詳述されているようにスーパービューをクリアするだけでは十分ではありません。後で悪い驚き。

1
Guig

以前の回答に基づいて(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
}
}
1
eonist

(2017年7月31日現在)

Swift

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

目的C

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

これは、UIViewに存在するすべての制約をすばやく削除する最も簡単な方法です。 UIViewを新しい制約または新しいフレームで追加し直してください=)

0
BennyTheNerd

次のようなものを使用できます。

[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から制約を削除します。

0
Tristan