Layout guide has conflicts with auto layout constraints

198 Views Asked by At

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:

enter image description here

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:

enter image description here

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!

1

There are 1 best solutions below

1
On BEST ANSWER

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.