UIStackView พร้อมข้อ จำกัด แบบกำหนดเอง -“ ไม่สามารถตอบสนองข้อ จำกัด พร้อมกันได้” เมื่อเปลี่ยนแกน
ฉันพบปัญหาเกี่ยวกับ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
}
}
ฉันไม่รู้ว่าการเปลี่ยนลำดับความสำคัญจะมีประสิทธิภาพมากกว่าหรือน้อยกว่าการเปลี่ยนการเปิดใช้งานหรือไม่? แต่อาจเป็นสิ่งที่ต้องพิจารณา