사용자 지정 제약 조건이있는 UIStackView-축 변경시 "제약 조건을 동시에 충족 할 수 없음"
.NET 관련 문제를 발견했습니다 UIStackView
. UIStackView와 배열 된 하위 뷰 사이에 사용자 지정 제약이있는 경우와 관련이 있습니다. 스택보기의 축과 사용자 지정 제약 조건을 변경할 때 [LayoutConstraints] Unable to simultaneously satisfy constraints.
콘솔에 오류 메시지 가 나타납니다.
다음은 문제를 관찰하기 위해 새 프로젝트에 붙여 넣을 수있는 코드 예제입니다.
import UIKit
public class ViewController: UIViewController {
private var stateToggle = false
private let viewA: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.accessibilityIdentifier = "viewA"
view.backgroundColor = .red
return view
}()
private let viewB: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.accessibilityIdentifier = "viewB"
view.backgroundColor = .green
return view
}()
private lazy var stackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [viewA, viewB])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.distribution = .fill
stackView.alignment = .fill
stackView.accessibilityIdentifier = "stackView"
return stackView
}()
private lazy var viewAOneFourthOfStackViewHightConstraint = viewA.heightAnchor.constraint(equalTo: stackView.heightAnchor, multiplier: 0.25)
private lazy var viewAOneFourthOfStackViewWidthConstraint = viewA.widthAnchor.constraint(equalTo: stackView.widthAnchor, multiplier: 0.25)
public override func viewDidLoad() {
super.viewDidLoad()
view.accessibilityIdentifier = "rootView"
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
let button = UIButton(type: .system)
button.frame = .init(x: 50, y: 50, width: 100, height: 44)
button.setTitle("toggle layout", for: .normal)
button.tintColor = .white
button.backgroundColor = .black
button.addTarget(self, action: #selector(buttonTap), for: .touchUpInside)
view.addSubview(button)
updateConstraintsBasedOnState()
}
@objc func buttonTap() {
stateToggle.toggle()
updateConstraintsBasedOnState()
}
private func updateConstraintsBasedOnState() {
switch (stateToggle) {
case true:
stackView.axis = .horizontal
viewAOneFourthOfStackViewHightConstraint.isActive = false
viewAOneFourthOfStackViewWidthConstraint.isActive = true
case false:
stackView.axis = .vertical
viewAOneFourthOfStackViewWidthConstraint.isActive = false
viewAOneFourthOfStackViewHightConstraint.isActive = true
}
}
}
이 예제에서는 두 개의 하위보기가있는 스택보기를 만들고 있습니다. 그중 하나가 면적의 25 %를 차지하기를 원하므로이를 달성하기위한 제약이 있지만 (각 방향에 대해 하나씩) 그에 따라 전환 할 수 있어야합니다. 전환이 발생하면 (이 경우 수직 스택에서 수평 스택으로) 오류 메시지가 나타납니다.
[LayoutConstraints] 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.
(
"<NSLayoutConstraint:0x60000089ca00 stackView.leading == UILayoutGuide:0x6000012b48c0'UIViewSafeAreaLayoutGuide'.leading (active, names: stackView:0x7fe269d07b50 )>",
"<NSLayoutConstraint:0x60000089c9b0 stackView.trailing == UILayoutGuide:0x6000012b48c0'UIViewSafeAreaLayoutGuide'.trailing (active, names: stackView:0x7fe269d07b50 )>",
"<NSLayoutConstraint:0x60000089c730 viewA.width == 0.25*stackView.width (active, names: viewA:0x7fe269e14fd0, stackView:0x7fe269d07b50 )>",
"<NSLayoutConstraint:0x6000008ba990 'UISV-canvas-connection' stackView.leading == viewA.leading (active, names: stackView:0x7fe269d07b50, viewA:0x7fe269e14fd0 )>",
"<NSLayoutConstraint:0x6000008ba9e0 'UISV-canvas-connection' H:[viewA]-(0)-| (active, names: stackView:0x7fe269d07b50, viewA:0x7fe269e14fd0, '|':stackView:0x7fe269d07b50 )>",
"<NSLayoutConstraint:0x6000008bab20 'UIView-Encapsulated-Layout-Width' rootView.width == 375 (active, names: rootView:0x7fe269c111a0 )>",
"<NSLayoutConstraint:0x60000089ce60 'UIViewSafeAreaLayoutGuide-left' H:|-(0)-[UILayoutGuide:0x6000012b48c0'UIViewSafeAreaLayoutGuide'](LTR) (active, names: rootView:0x7fe269c111a0, '|':rootView:0x7fe269c111a0 )>",
"<NSLayoutConstraint:0x60000089cdc0 'UIViewSafeAreaLayoutGuide-right' H:[UILayoutGuide:0x6000012b48c0'UIViewSafeAreaLayoutGuide']-(0)-|(LTR) (active, names: rootView:0x7fe269c111a0, '|':rootView:0x7fe269c111a0 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x6000008ba990 'UISV-canvas-connection' stackView.leading == viewA.leading (active, names: stackView:0x7fe269d07b50, viewA:0x7fe269e14fd0 )>
: 제약 주목 "<NSLayoutConstraint:0x6000008ba9e0 'UISV-canvas-connection' H:[viewA]-(0)-| (active, names: stackView:0x7fe269d07b50, viewA:0x7fe269e14fd0, '|':stackView:0x7fe269d07b50 )>"
말한다 "viewA의 후행 스택 뷰의 후행과 동일해야합니다"를 . 이것은 수평 축 레이아웃에 유효한 제약 조건이 아닙니다. 그러나 이것은 내가 추가 한 제약이 아니라 스택 뷰 ( UISV-canvas-connection
) 내부에있는 것 입니다.
사용자 지정 제약 조건을 활성화 / 비활성화하고 스택 뷰도 축을 전환 할 때 내부적으로 동일한 작업을 수행하기 때문에 일시적으로 충돌과 오류가 발생하는 것 같습니다.
잠재적 인 해결책 / 해결 방법 :
- 해결 방법 1 : 사용자 지정 제약 조건에
priority = 999
. 이것은 좋은 해결 방법이 아닙니다. 해키 때문일뿐만 아니라 어떤 경우에는 다른 레이아웃 문제가 발생하기 때문입니다 (예 : viewA에 필요한 포옹 우선 순위가있는 하위보기와 같이 충돌하는 내부 레이아웃 요구 사항이있는 경우). - 해결 방법 2 : 축이 변경되기 전에 정렬 된 뷰를 제거하고 축이 변경된 후 다시 추가합니다. 이것은 작동하지만 또한 해키이며 일부 복잡한 경우에는 실제로 어려울 수 있습니다.
- 해결 방법 3 : 전혀 사용하지 마십시오.
UIStackView
일반UIView
을 컨테이너로 사용하여 구현 하고 필요에 따라 필요한 제약 조건을 만듭니다.
이것이 버그로 간주되어야하고 (Apple에보고되어야 함) 다른 (더 나은?) 해결책이 있는지에 대한 아이디어가 있습니까?
예를 들어 AutoLayout에 "제약 조건과 스택 뷰의 축을 변경하려고합니다. 완료 될 때까지 기다린 다음 레이아웃 평가를 계속합니다"라고 알려주는 방법이 있습니다.
답변
내가보기에, 당신과 iOS 모두 비활성화하고 활성화해야하는 제약이 있습니다. 올바르게 수행하고 충돌을 피하려면 새 제약 조건을 활성화하기 전에 사용자와 iOS 모두 이전 제약 조건을 비활성화해야합니다.
작동하는 솔루션이 있습니다. 에게 stackView
로 layoutIfNeeded()
당신이 당신의 오래된 제약 조건을 비활성화 한 후에. 그런 다음 제약 조건을 순서대로 가져오고 새 제약 조건을 활성화 할 수 있습니다.
private func updateConstraintsBasedOnState() {
switch (stateToggle) {
case true:
stackView.axis = .horizontal
viewAOneFourthOfStackViewHightConstraint.isActive = false
stackView.layoutIfNeeded()
viewAOneFourthOfStackViewWidthConstraint.isActive = true
case false:
stackView.axis = .vertical
viewAOneFourthOfStackViewWidthConstraint.isActive = false
stackView.layoutIfNeeded()
viewAOneFourthOfStackViewHightConstraint.isActive = true
}
}
vacawama 의 대답이 효과가 있지만 다른 두 가지 옵션 ...
1- .required
자동 레이아웃이 불만없이 일시적으로 제약 조건을 깨는 것을 허용하는 것보다 제약 조건에 우선 순위를 부여합니다 .
이것을 추가하십시오 viewDidLoad()
:
viewAOneFourthOfStackViewHightConstraint.priority = .defaultHigh
viewAOneFourthOfStackViewWidthConstraint.priority = .defaultHigh
updateConstraintsBasedOnState()
원래 가지고 있던 그대로 떠나 거나
2-다른 우선 순위 수준을 사용하고 제약 조건을 비활성화 / 활성화하는 대신 교체합니다.
이것을 추가하십시오 viewDidLoad()
:
viewAOneFourthOfStackViewHightConstraint.priority = .defaultHigh
viewAOneFourthOfStackViewWidthConstraint.priority = .defaultLow
viewAOneFourthOfStackViewHightConstraint.isActive = true
viewAOneFourthOfStackViewWidthConstraint.isActive = true
다음으로 변경 updateConstraintsBasedOnState()
하십시오.
private func updateConstraintsBasedOnState() {
switch (stateToggle) {
case true:
stackView.axis = .horizontal
viewAOneFourthOfStackViewHightConstraint.priority = .defaultLow
viewAOneFourthOfStackViewWidthConstraint.priority = .defaultHigh
case false:
stackView.axis = .vertical
viewAOneFourthOfStackViewWidthConstraint.priority = .defaultLow
viewAOneFourthOfStackViewHightConstraint.priority = .defaultHigh
}
}
우선 순위를 변경하는 것이 활성화를 변경하는 것보다 덜 효율적인지 모르겠습니까? 그러나 고려해야 할 사항 일 수 있습니다.