[ios] UIView에 영향을 미치는 모든 제약 조건 제거

몇 가지 제약 조건을 통해 화면에 배치되는 UIView가 있습니다. 제약 중 일부는 수퍼 뷰가 소유하고 다른 제약은 다른 조상이 소유합니다 (예 : UIViewController의 뷰 속성).

이 모든 오래된 제약을 제거하고 새로운 제약을 사용하여 새로운 곳에 배치하고 싶습니다.

모든 단일 제약 조건에 대해 IBOutlet을 생성하지 않고 어떤 뷰가 해당 제약 조건을 소유하는지 기억하지 않고 어떻게이 작업을 수행 할 수 있습니까?

정교하게 설명하기 위해 순진한 접근 방식은 각 제약 조건에 대해 IBOutlet을 생성 한 다음 다음과 같은 코드를 호출하는 것입니다.

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

이 코드의 문제점은 가장 단순한 경우에도 2 개의 IBOutlet을 생성해야한다는 것입니다. 복잡한 레이아웃의 경우 필요한 IBOutlet 4 개 또는 8 개에 쉽게 도달 할 수 있습니다. 또한 제약 조건을 제거하라는 호출이 적절한 뷰에서 호출되는지 확인해야합니다. 예를 들어, 그는 상상 myViewsLeftConstraint에 의해 소유된다 viewA. 실수로을 호출 [self.view removeConstraint:self.myViewsLeftConstraint]하면 아무 일도 일어나지 않습니다.

참고 : constraintsAffectingLayoutForAxis 메소드 는 유망 해 보이지만 디버깅 목적으로 만 사용됩니다.


업데이트 : 내가 받고있는 대부분의 답변 self.constraintsself.superview.constraints, 또는 일부 변형을 처리합니다. 이러한 방법은 뷰에 영향을주는 제약 조건이 아닌 뷰가 소유 한 제약 조건 만 반환하므로 이러한 솔루션은 작동 하지 않습니다 .

이러한 솔루션의 문제를 명확히하려면 다음 뷰 계층 구조를 고려하십시오.

  • 할아버지
    • 아버지
      • 나를
        • 아들
      • 동료
    • 삼촌

이제 다음 제약 조건을 만들고 항상 가장 가까운 공통 조상에 연결한다고 상상해보십시오.

  • C0 : 나 : 아들과 같은 탑 (나 소유)
  • C1 : 나 : 너비 = 100 (내가 소유)
  • C2 : 나 : 형제와 같은 키 (아버지 소유)
  • C3 : 나 : 삼촌과 같은상의 (할아버지 소유)
  • C4 : 나 : 할아버지와 같은 왼쪽 (Grandfather 소유)
  • 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 is Me)를 사용하면 [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

실행이 완료되면 자동 크기 조정 제약 조건을 생성하기 때문에 뷰가 그대로 유지됩니다. 이 작업을 수행하지 않으면 일반적으로보기가 사라집니다. 또한 상위 뷰에서 제약 조건을 제거하는 것이 아니라 상위 뷰에 영향을 미치는 제약 조건이있을 수 있으므로 계속 위로 이동합니다.

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


답변

지금까지 찾은 유일한 해결책은 수퍼 뷰에서 뷰를 제거하는 것입니다.

[view removeFromSuperview]

이것은 레이아웃에 영향을 미치는 모든 제약을 제거하고 수퍼 뷰에 추가 할 준비가되어 있고 새로운 제약이 첨부 된 것처럼 보입니다. 그러나 계층 구조에서 모든 하위보기를 잘못 제거하고 잘못 제거합니다 [C7].


답변

다음을 수행하여 뷰의 모든 제약 조건을 제거 할 수 있습니다.

self.removeConstraints(self.constraints)

편집 : 모든 하위보기의 제약 조건을 제거하려면 Swift에서 다음 확장을 사용하십시오.

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


답변

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 이상용으로 개발하는 경우 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. UIView


답변

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


답변

세부

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


답변

신속한 솔루션 :

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

두 요소 사이의 제약은 공통 조상이 유지하므로 모든 부모를 살펴 보는 것이 중요하므로이 답변에 자세히 설명 된 수퍼 뷰를 지우는 것만으로 는 충분하지 않으며 나중에 심각한 놀라움을 겪을 수 있습니다.