Руководство по макету конфликтует с ограничениями автоматического макета
Я ввел пример в книгу по программированию для iOS, но этот пример не работает. Результат примеров должен быть таким, как на следующей картинке:
Код примера выглядит следующим образом:
import UIKit
class ViewsController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
layoutViews()
}
func layoutViews () {
let v1 = UIView(frame: CGRect(0, 0, 200, 30))
let v2 = UIView(frame: CGRect(0, 50, 200, 30))
let v3 = UIView(frame: CGRect(0, 80, 200, 30))
let v4 = UIView(frame: CGRect(0, 150, 200, 30))
let views = [v1, v2, v3, v4]
let colors = [UIColor.red, UIColor.blue, UIColor.yellow, UIColor.green]
var guides = [UILayoutGuide]()
for (v, c) in zip(views, colors) {
v.backgroundColor = c
}
for v in views {
v.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(v)
}
// one fewer guides than views
for _ in views.dropLast() {
let g = UILayoutGuide()
self.view.addLayoutGuide(g)
guides.append(g)
}
// guides leading and width are arbitrary
let anc = self.view.leadingAnchor
for g in guides {
g.leadingAnchor.constraint(equalTo: anc).isActive = true
g.widthAnchor.constraint(equalToConstant: 10).isActive = true
}
// guides top to previous view
for (v,g) in zip(views.dropLast(), guides) {
v.bottomAnchor.constraint(equalTo: g.topAnchor).isActive = true
}
// guides bottom to next view
for (v,g) in zip(views.dropFirst(), guides) {
v.topAnchor.constraint(equalTo: g.bottomAnchor).isActive = true
}
let h = guides[0].heightAnchor
for g in guides.dropFirst() {
g.heightAnchor.constraint(equalTo: h).isActive = true
}
}
}
extension CGRect {
init(_ x:CGFloat, _ y:CGFloat, _ w:CGFloat, _ h:CGFloat) {
self.init(x:x, y:y, width:w, height:h)
}
}
extension CGRect {
var center : CGPoint {
return CGPoint(self.midX, self.midY)
}
}
extension CGRect {
func centeredRectOfSize(_ sz:CGSize) -> CGRect {
let c = self.center
let x = c.x - sz.width/2.0
let y = c.y - sz.height/2.0
return CGRect(x, y, sz.width, sz.height)
}
}
extension CGSize {
init(_ width:CGFloat, _ height:CGFloat) {
self.init(width:width, height:height)
}
}
extension CGPoint {
init(_ x:CGFloat, _ y:CGFloat) {
self.init(x:x, y:y)
}
}
extension CGVector {
init (_ dx:CGFloat, _ dy:CGFloat) {
self.init(dx:dx, dy:dy)
}
}
В примере сначала не было такой строчки кода, как
Результат работы программы подобен следующей картинке:
А в консоли появляется следующее предупреждающее сообщение:
2021-06-10 10:47:03.267054+1000 Chapter1Views[19879:4731640] [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.
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSAutoresizingMaskLayoutConstraint:0x600003b745f0 h=--& v=--& UIView:0x12ad170a0.minY == 0 (active, names: '|':UIView:0x12ad16a00 )>",
"<NSAutoresizingMaskLayoutConstraint:0x600003b74550 h=--& v=--& UIView:0x12ad170a0.height == 30 (active)>",
"<NSAutoresizingMaskLayoutConstraint:0x600003b74000 h=--& v=--& UIView:0x12ad16120.minY == 50 (active, names: '|':UIView:0x12ad16a00 )>",
"<NSAutoresizingMaskLayoutConstraint:0x600003b770c0 h=--& v=--& UIView:0x12ad16120.height == 30 (active)>",
"<NSAutoresizingMaskLayoutConstraint:0x600003b771b0 h=--& v=--& UIView:0x12ad16290.minY == 80 (active, names: '|':UIView:0x12ad16a00 )>",
"<NSLayoutConstraint:0x600003b51e00 UIView:0x12ad170a0.bottom == UILayoutGuide:0x60000215d5e0''.top (active)>",
"<NSLayoutConstraint:0x600003b51ea0 UIView:0x12ad16120.bottom == UILayoutGuide:0x60000215d6c0''.top (active)>",
"<NSLayoutConstraint:0x600003b51f40 V:[UILayoutGuide:0x60000215d5e0'']-(0)-[UIView:0x12ad16120] (active)>",
"<NSLayoutConstraint:0x600003b51f90 V:[UILayoutGuide:0x60000215d6c0'']-(0)-[UIView:0x12ad16290] (active)>",
"<NSLayoutConstraint:0x600003b51fe0 UILayoutGuide:0x60000215d6c0''.height == UILayoutGuide:0x60000215d5e0''.height (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600003b51f90 V:[UILayoutGuide:0x60000215d6c0'']-(0)-[UIView:0x12ad16290] (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
2021-06-10 10:47:03.269574+1000 Chapter1Views[19879:4731640] [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.
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSAutoresizingMaskLayoutConstraint:0x600003b745f0 h=--& v=--& UIView:0x12ad170a0.minY == 0 (active, names: '|':UIView:0x12ad16a00 )>",
"<NSAutoresizingMaskLayoutConstraint:0x600003b74550 h=--& v=--& UIView:0x12ad170a0.height == 30 (active)>",
"<NSAutoresizingMaskLayoutConstraint:0x600003b74000 h=--& v=--& UIView:0x12ad16120.minY == 50 (active, names: '|':UIView:0x12ad16a00 )>",
"<NSAutoresizingMaskLayoutConstraint:0x600003b771b0 h=--& v=--& UIView:0x12ad16290.minY == 80 (active, names: '|':UIView:0x12ad16a00 )>",
"<NSAutoresizingMaskLayoutConstraint:0x600003b77200 h=--& v=--& UIView:0x12ad16290.height == 30 (active)>",
"<NSAutoresizingMaskLayoutConstraint:0x600003b45b80 h=--& v=--& UIView:0x12ad16670.minY == 150 (active, names: '|':UIView:0x12ad16a00 )>",
"<NSLayoutConstraint:0x600003b51e00 UIView:0x12ad170a0.bottom == UILayoutGuide:0x60000215d5e0''.top (active)>",
"<NSLayoutConstraint:0x600003b51ef0 UIView:0x12ad16290.bottom == UILayoutGuide:0x60000215e220''.top (active)>",
"<NSLayoutConstraint:0x600003b51f40 V:[UILayoutGuide:0x60000215d5e0'']-(0)-[UIView:0x12ad16120] (active)>",
"<NSLayoutConstraint:0x600003b51e50 V:[UILayoutGuide:0x60000215e220'']-(0)-[UIView:0x12ad16670] (active)>",
"<NSLayoutConstraint:0x600003b52030 UILayoutGuide:0x60000215e220''.height == UILayoutGuide:0x60000215d5e0''.height (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600003b51e50 V:[UILayoutGuide:0x60000215e220'']-(0)-[UIView:0x12ad16670] (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
Похоже, что здесь происходят конфликты двух видов. Один конфликт возникает между направляющими автоматической компоновки и масками автоматического изменения размера представлений, а другой - между направляющими автоматической компоновки и ограничениями автоматического макета представлений.
Поэтому для разрешения первого конфликта я добавил код строки:
v.translatesAutoresizingMaskIntoConstraints = false
Однако в результате на экране симулятора ничего не отображается.
И я не знаю, как разрешить второй конфликт, конфликт между направляющими автоматической компоновки и ограничениями автоматической компоновки представлений.
Спасибо за помощь!
1 ответ
Вот подход с использованием
UILayoutGuide
"распорки" для равного вертикального интервала, с пропущенным / исправленным кодом:
func layoutViews () {
let v1 = UIView()
let v2 = UIView()
let v3 = UIView()
let v4 = UIView()
let views = [v1, v2, v3, v4]
let colors = [UIColor.red, UIColor.blue, UIColor.yellow, UIColor.green]
var guides = [UILayoutGuide]()
for (v, c) in zip(views, colors) {
v.backgroundColor = c
}
for v in views {
v.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(v)
}
// one fewer guides than views
for _ in views.dropLast() {
let g = UILayoutGuide()
self.view.addLayoutGuide(g)
guides.append(g)
}
// respect safe-area
let safeG = view.safeAreaLayoutGuide
// guides leading and width are arbitrary
let anc = safeG.leadingAnchor
for g in guides {
g.leadingAnchor.constraint(equalTo: anc).isActive = true
g.widthAnchor.constraint(equalToConstant: 10).isActive = true
}
// all 4 views constrain leading and trailing
for v in views {
v.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 20.0).isActive = true
v.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -20.0).isActive = true
}
// references to first and last views
guard let firstView = views.first,
let lastView = views.last
else {
fatalError("Incorrect setup!")
}
// first view, constrain to top
firstView.topAnchor.constraint(equalTo: safeG.topAnchor).isActive = true
// last view, constrain to bottom
lastView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor).isActive = true
// guides top to previous view
for (v,g) in zip(views.dropLast(), guides) {
g.topAnchor.constraint(equalTo: v.bottomAnchor).isActive = true
}
// guides bottom to next view
for (v,g) in zip(views.dropFirst(), guides) {
g.bottomAnchor.constraint(equalTo: v.topAnchor).isActive = true
}
// first view, constrain height to 30
views[0].heightAnchor.constraint(equalToConstant: 30.0).isActive = true
// remaining views heightAnchor equal to first view heightAnchor
for v in views.dropFirst() {
v.heightAnchor.constraint(equalTo: firstView.heightAnchor).isActive = true
}
let h = guides[0].heightAnchor
for g in guides.dropFirst() {
g.heightAnchor.constraint(equalTo: h).isActive = true
}
}
Вот один из способов сделать то же самое, но с помощью
UIStackView
для макета:
func layoutViews () {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .equalSpacing
let colors = [UIColor.red, UIColor.blue, UIColor.yellow, UIColor.green]
colors.forEach { c in
let v = UIView()
v.backgroundColor = c
v.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
stackView.addArrangedSubview(v)
}
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
// respect safe-area
let safeG = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: safeG.topAnchor),
stackView.leadingAnchor.constraint(equalTo: safeG.leadingAnchor, constant: 20.0),
stackView.trailingAnchor.constraint(equalTo: safeG.trailingAnchor, constant: -20.0),
stackView.bottomAnchor.constraint(equalTo: safeG.bottomAnchor),
])
}
Как видите ... гораздо меньше строк кода и гораздо меньше объектов и ограничений, с которыми приходится иметь дело.