Reputation: 376
I entered an example in a book for iOS programming, but the example doesn't work. The result of the examples is supposed to be like the following picture:
The code of the example is like the following:
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)
}
}
In the example, at first there was no such a line of code as
v.translatesAutoresizingMaskIntoConstraints = false
The result of the program is like the following picture:
And in the console, there is the following warning message:
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.
It seems that there are two kinds of conflicts going on here. One conflict is between the auto layout guides and the views' auto resizing masks and the other is between the auto layout guides and the views' auto layout constraints.
Therefore, to resolve the first conflict, I added the line code:
v.translatesAutoresizingMaskIntoConstraints = false
However, the result is that there's nothing shown on the simulator screen.
And I don't know how to resolve the second conflict, the one between the auto layout guides and the views' auto layout constraints.
Thank you for your help!
Upvotes: 0
Views: 211
Reputation: 77638
Here is the approach using UILayoutGuide
"spacers" for the equal vertical spacing, with the missing / corrected code:
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
}
}
Now, here is one way to do the same thing, but using a UIStackView
for the layout:
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),
])
}
As you can see... far fewer lines of code, and far fewer objects and constraints to deal with.
Upvotes: 1