[ios] “squished”숨겨진 뷰에 대한 UIStackView “동시에 제약 조건을 충족 할 수 없음”

내 UIStackView “행”이 찌그러지면 AutoLayout경고가 발생합니다. 그러나 그들은 잘 표시되며 다음과 같은 종류의 로깅 외에는 잘못된 것이 없습니다.

제약 조건을 동시에 충족 할 수 없습니다. 아마도 다음 목록의 제약 조건 중 적어도 하나는 원하지 않는 제약 조건 일 것입니다. 다음을 시도해보십시오 : (1) 각 제약 조건을 살펴보고 예상하지 못한 것이 무엇인지 파악하십시오. (2) 원치 않는 제약 또는 제약을 추가 한 코드를 찾아 수정합니다. (참고 : NSAutoresizingMaskLayoutConstraints이해가되지 않는 경우 UIView속성 에 대한 설명서를 참조하십시오. translatesAutoresizingMaskIntoConstraints) (

그래서 나는 이것을 고치는 방법을 아직 모르겠지만 단지 성가신 것 외에는 아무것도 깨뜨리지 않는 것 같습니다.

누구든지 그것을 해결하는 방법을 알고 있습니까? 흥미롭게도 레이아웃 제약 조건은 ‘UISV-hiding’ 태그가 자주 붙는데 , 이는 아마도이 인스턴스에서 서브 뷰 또는 무언가에 대한 높이 최소값을 무시해야한다는 것을 나타냅니다.



답변

이 문제는 하위 뷰를 내부 UIStackView에서 숨김으로 설정할 때 애니메이션을 적용하기 위해 먼저 높이를 0으로 제한하기 때문에 발생합니다.

다음과 같은 오류가 발생했습니다.

2015-10-01 11:45:13.732 <redacted>[64455:6368084] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
    "<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-|   (Names: '|':UIView:0x7f7f5be69d30 )>",
    "<NSLayoutConstraint:0x7f7f5be508d0 V:|-(8)-[UISegmentedControl:0x7f7f5bec4180]   (Names: '|':UIView:0x7f7f5be69d30 )>",
    "<NSLayoutConstraint:0x7f7f5bdfbda0 'UISV-hiding' V:[UIView:0x7f7f5be69d30(0)]>"
)

Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-|   (Names: '|':UIView:0x7f7f5be69d30 )>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

내가하려고했던 것은 각 가장자리에 8pt 씩 삽입 된 것을 UIView내 안에 배치하는 것이 었습니다 .UIStackViewUISegmentedControl

숨김으로 설정하면 컨테이너 뷰를 0 높이로 제한하려고하지만 위에서 아래로 제약 조건이 설정되어 있기 때문에 충돌이 발생했습니다.

이 문제를 해결하기 위해 UISV-hiding필요한 경우 제약 조건이 우선 순위를 가질 수 있도록 8pt 상단 및 하단 제약 우선 순위를 1000에서 999로 변경 했습니다.


답변

해결하기 쉽지 않은 비슷한 문제가 발생했습니다. 제 경우에는 스택 뷰에 스택 뷰가 포함되어 있습니다. 내부 UIStackView에는 두 개의 레이블과 0이 아닌 간격이 지정되었습니다.

addArrangedSubview ()를 호출하면 다음과 유사한 제약 조건이 자동으로 생성됩니다.

V:|[innerStackView]|              | = outerStackView

  V:|[label1]-(2)-[label2]|       | = innerStackView

이제 innerStackView를 숨기려고하면 모호한 제약 조건 경고가 표시됩니다.

이유를 이해하려면이 왜의 처음 보자 하지 않는 경우에 발생 innerStackView.spacing같다 0. 를 호출 할 때 innerStackView.hidden = true@liamnichols가 정확했습니다. outerStackView마법처럼이 호출을 가로 채고 우선 순위가 1000 인 0높이 UISV 숨김 제약 조건을 만듭니다 (필수). 아마도 이것은 숨김 코드가 UIView.animationWithDuration()블록 내에서 호출되는 경우 스택 뷰의 요소가 뷰 밖에서 애니메이션되도록 허용하는 것 입니다. 불행히도이 제약이 추가되는 것을 막을 수있는 방법은없는 것 같습니다. 그럼에도 불구하고 다음과 같은 상황이 발생하기 때문에 “제약 조건을 동시에 충족 할 수 없음”(USSC) 경고가 표시되지 않습니다.

  1. label1의 높이가 0으로 설정 됨
  2. 두 레이블 사이의 간격이 이미 0으로 정의되었습니다.
  3. label2의 높이가 0으로 설정 됨
  4. innerStackView의 높이는 0으로 설정됩니다.

이러한 4 가지 제약 조건을 충족 할 수 있다는 것은 분명합니다. 스택 뷰는 단순히 모든 것을 0 높이 픽셀로 부드럽게 만듭니다.

우리가 설정 한 경우 이제, 버그 예를 다시가는 spacing하기 위해 2, 우리는 지금 이러한 제약이있다 :

  1. label1의 높이가 0으로 설정 됨
  2. 두 레이블 사이의 간격은 1000 우선 순위에서 2 픽셀 높이로 스택보기에 의해 자동으로 생성되었습니다.
  3. label2의 높이가 0으로 설정 됨
  4. innerStackView의 높이는 0으로 설정됩니다.

스택보기는 높이가 0 픽셀 일 수없고 콘텐츠 높이가 2 픽셀 일 수 없습니다. 제약 조건을 충족 할 수 없습니다.

참고 : 더 간단한 예를 통해이 동작을 확인할 수 있습니다. 정렬 된 하위보기로 스택보기에 UIView를 추가하기 만하면됩니다. 그런 다음 해당 UIView에 1000 우선 순위로 높이 제약 조건을 설정하십시오. 이제 Hide를 호출 해보십시오.

참고 : 어떤 이유로 든 스택 뷰가 UICollectionViewCell 또는 UITableViewCell의 하위 뷰인 경우에만 발생했습니다. 그러나 innerStackView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)내부 스택보기를 숨긴 후 다음 실행 루프 를 호출하여 셀 외부에서이 동작을 재현 할 수 있습니다.

참고 : UIView.performWithoutAnimations에서 코드를 실행하려고해도 스택 뷰는 여전히 USSC 경고를 발생시키는 0 높이 제약 조건을 추가합니다.


이 문제에 대한 최소 3 가지 해결책이 있습니다.

  1. 스택보기에서 요소를 숨기기 전에 스택보기인지 확인하고, 그렇다면 spacing을 0으로 변경 하십시오. 콘텐츠를 다시 표시 할 때마다 프로세스를 반대로 (원래 간격을 기억해야 함)해야하기 때문에 이것은 성가신 일입니다.
  2. 스택보기에서 요소를 숨기는 대신 removeFromSuperview. 프로세스를 되돌릴 때 제거 된 항목을 삽입 할 위치 를 기억해야하기 때문에 훨씬 더 성가신 일 입니다. removeArrangedSubview를 호출 한 다음 숨기는 것만으로 최적화 할 수 있지만 여전히 수행해야하는 부기가 많이 있습니다.
  3. spacingUIView에서 중첩 된 스택 뷰 (0이 아닌 )를 래핑 합니다. 비 필수 우선 순위 (999 이하)로 하나 이상의 제한 조건을 지정하십시오. 부기를 할 필요가 없기 때문에 이것이 최상의 솔루션입니다. 필자의 예에서는 스택보기와 래퍼보기 사이에 1000에서 위쪽, 선행 및 후행 제약 조건을 만든 다음 스택보기의 아래쪽에서 래퍼보기까지 999 제약 조건을 만들었습니다. 이렇게하면 외부 스택 뷰가 0 높이 제약 조건을 생성 할 때 999 제약 조건이 깨지고 USSC 경고가 표시되지 않습니다. (참고 : 이는 UICollectionViewCell 하위 클래스의 contentView.translatesAutoResizingMaskToConstraints를로 설정해야하는 경우false 에 대한 솔루션과 유사합니다. )

요약하면이 동작이 발생하는 이유는 다음과 같습니다.

  1. 관리되는 하위보기를 스택보기에 추가하면 Apple은 자동으로 1000 개의 우선 순위 제약 조건을 생성합니다.
  2. Apple은 스택보기의 하위보기를 숨길 때 자동으로 0 높이 제한을 생성합니다.

Apple이 (1) 제약 조건 (특히 스페이서)의 우선 순위를 지정하도록 허용했거나 (2) 자동 UISV 숨김 제약 조건 을 옵트 아웃하도록 허용했다면 이 문제는 쉽게 해결 될 것입니다.


답변

대부분의 경우이 오류는 충돌을 제거하기 위해 제약 조건 우선 순위를 낮춤으로써 해결할 수 있습니다.


답변

보기를 숨김으로 설정하면 UIStackview에서 애니메이션을 적용하려고합니다. 이러한 효과를 원하면 제약 조건에 대해 올바른 우선 순위를 설정하여 충돌하지 않도록해야합니다 (위에서 많은 사람들이 제안한대로).

그러나 애니메이션을 신경 쓰지 않는다면 (아마도 ViewDidLoad에서 숨길 것입니다), removeFromSuperview보기와 함께 제거되므로 제약 조건에 문제가없는 동일한 효과를 가지도록 간단하게 할 수 있습니다.


답변

@Senseful의 답변에 따라 다음은 뷰에서 스택 뷰를 래핑하고 권장하는 제약 조건을 적용하는 UIStackView 확장입니다.

/// wraps in a `UIView` to prevent autolayout warnings when a stack view with spacing is placed inside another stack view whose height might be zero (usually due to `hidden` being `true`).
/// See http://stackoverflow.com/questions/32428210
func wrapped() -> UIView {
    let wrapper = UIView()
    translatesAutoresizingMaskIntoConstraints = false
    wrapper.addSubview(self)

    for attribute in [NSLayoutAttribute.Top, .Left, .Right, .Bottom] {
        let constraint = NSLayoutConstraint(item: self,
                                            attribute: attribute,
                                            relatedBy: .Equal,
                                            toItem: wrapper,
                                            attribute: attribute,
                                            multiplier: 1,
                                            constant: 0)
        if attribute == .Bottom { constraint.priority = 999 }
        wrapper.addConstraint(constraint)
    }
    return wrapper
}

대신 추가 stackView사용을stackView.wrapped() .


답변

첫째, 다른 사람들이 제안했듯이 제어 할 수있는 제약 조건, 즉 UIStackView에 내재 된 제약 조건이 우선 순위 999로 설정되어 뷰가 숨겨 질 때 재정의 될 수 있는지 확인합니다.

여전히 문제가 발생하는 경우 숨겨진 StackView의 간격으로 인한 문제 일 수 있습니다. 내 솔루션은 UIView를 스페이서로 추가하고 UIStackView 간격을 0으로 설정하는 것입니다. 그런 다음 View.height 또는 View.width 제약 조건 (수직 또는 수평 스택에 따라 다름)을 StackView의 간격으로 설정합니다.

그런 다음 새로 추가 된보기의 콘텐츠 포옹 및 콘텐츠 압축 저항 우선 순위를 조정합니다. 상위 StackView의 배포도 변경해야 할 수 있습니다.

위의 모든 작업은 Interface Builder에서 수행 할 수 있습니다. 추가로 새로 추가 된 뷰 중 일부를 프로그래밍 방식으로 숨기거나 숨김 해제해야 원치 않는 간격이 생기지 않을 수 있습니다.


답변

나는 최근에 UIStackView. 에서 많은 책을 보관하고 포장하는 대신 UIViews, 나는 parentStackView숨기거나 숨기고 싶은 아이들을위한 콘센트와 콘센트 를 만들기로 결정했습니다 .

@IBOutlet weak var parentStackView: UIStackView!
@IBOutlet var stackViewNumber1: UIStackView!
@IBOutlet var stackViewNumber2: UIStackView!

스토리 보드에서 내 parentStack은 다음과 같습니다.

여기에 이미지 설명 입력

여기에는 4 명의 자식이 있고 각 자식에는 내부에 스택 뷰가 있습니다. 스택 뷰를 숨길 때 스택 뷰인 UI 요소도 있으면 자동 레이아웃 오류 스트림이 표시됩니다. 숨기는 대신 제거하기로 결정했습니다.

내 예에서는 parentStackViewsTop Stack View, StackViewNumber1, Stack View Number 2 및 Stop Button의 4 개 요소 배열을 포함합니다. 인덱스는 arrangedSubviews각각 0, 1, 2, 3입니다. 하나를 숨기고 싶을 때 간단히 parentStackView's arrangedSubviews배열 에서 제거합니다 . 약하지 않기 때문에 메모리에 남아 있으며 나중에 원하는 인덱스에 다시 넣을 수 있습니다. 나는 그것을 다시 초기화하지 않기 때문에 필요할 때까지 놀고 있지만 메모리를 부 풀리지 않습니다.

따라서 기본적으로 …

1) 부모 스택과 숨기거나 숨기려는 자식에 대한 IBOutlets를 스토리 보드로 드래그합니다.

2) 숨기고 싶을 때, 숨기고 싶은 스택을 parentStackView's arrangedSubviews어레이 에서 제거합니다 .

3) 전화 self.view.layoutIfNeeded()UIView.animateWithDuration.

마지막 두 개의 stackView는 weak. 숨기기를 해제 할 때를 대비해 보관해야합니다.

stackViewNumber2를 숨기고 싶다고 가정 해 보겠습니다.

parentStackView.removeArrangedSubview(stackViewNumber2)
stackViewNumber2.removeFromSuperview()

그런 다음 애니메이션을 적용합니다.

UIView.animate(withDuration: 0.25,
               delay: 0,
               usingSpringWithDamping: 2.0,
               initialSpringVelocity: 10.0,
               options: [.curveEaseOut],
               animations: {
                self.view.layoutIfNeeded()
},
               completion: nil)

stackViewNumber2나중에 “숨기기를 해제” 하려면 원하는 parentStackView arrangedSubViews색인 에 삽입 하고 업데이트를 애니메이션하면됩니다.

parentStackView.removeArrangedSubview(stackViewNumber1)
stackViewNumber1.removeFromSuperview()
parentStackView.insertArrangedSubview(stackViewNumber2, at: 1)

// Then animate it
UIView.animate(withDuration: 0.25,
               delay: 0,
               usingSpringWithDamping: 2.0,
               initialSpringVelocity: 10.0,
               options: [.curveEaseOut],
               animations: {
                self.view.layoutIfNeeded()
},
               completion: nil)

나는 제약에 대한 부기, 우선 순위 등을 다루는 것보다 훨씬 쉽다는 것을 알았다.

기본적으로 숨기고 싶은 것이있는 경우 스토리 보드에 배치하고 제거 viewDidLoad하고을 사용하여 애니메이션없이 업데이트 할 수 view.layoutIfNeeded()있습니다.