0

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!

Michael May
  • 286
  • 2
  • 11
  • 1
    This seems to be a very odd approach, unless there is some other purpose that "book" you're following explains. The actual *"supposed to be like"* layout is very easily achieved with a `UIStackView` and would require much simpler constraints. – DonMag Jun 10 '21 at 17:45
  • You're right, @DonMag. Thank you for your comment. The book intends this example as sort of a "prelude" to the introduction of UIStackView to show how easily this effect can be achieved by using UIStackView instead of the spacer views. – Michael May Jun 10 '21 at 21:26

1 Answers1

1

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.

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Thanks again, @DonMag. These two approaches are both good with the latter more succinct. In fact, UIStackView has done all the weight-lifting jobs for us behind the scenes. The problem with the code snippet is that it doesn't pin the first view of the set in place in the first place, no puns here, so that the other three can follow suit. – Michael May Jun 10 '21 at 21:30