몇 가지 제약 조건을 통해 화면에 배치되는 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.constraints
은 self.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)
팁
Storyboard
s 또는 XIB
s를 사용 하는 것은 시나리오에서 언급 한대로 제약 조건을 구성하는 데 매우 고통 스러울 수 있으므로 제거하려는 각 항목에 대해 IBOutlet을 만들어야합니다. 그럼에도 불구하고 대부분의 Interface Builder
경우 해결하는 것보다 더 많은 문제가 발생합니다.
따라서 매우 동적 인 콘텐츠와 뷰의 다른 상태를 가질 때 다음을 제안합니다.
- 프로그래밍 방식으로보기 만들기
- 레이아웃 및 NSLayoutAnchor 사용
- 나중에 제거 될 수있는 각 제약 조건을 배열에 추가합니다.
- 새 상태를 적용하기 전에 매번 지우십시오.
간단한 코드
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()
}
참고 문헌
답변
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
}
}
}
두 요소 사이의 제약은 공통 조상이 유지하므로 모든 부모를 살펴 보는 것이 중요하므로이 답변에 자세히 설명 된 수퍼 뷰를 지우는 것만으로 는 충분하지 않으며 나중에 심각한 놀라움을 겪을 수 있습니다.