Search code examples
iosswiftautolayoutnslayoutanchor

IOS SafeAreaLayoutGuide anchor for landscape screen


I've been following Paul Hudsons' Hacking with Swift tutorials and I'm up to project 6 where he uses layout constraint programmatically. I've been doing this kind of task solely using Interface Builder, but I'm keen to learn on how to do it programmatically.

From his tutorial, we have the following code that add 5 UILabels to the main controller's view.

        let label1 = UILabel()
        label1.translatesAutoresizingMaskIntoConstraints = false
        label1.text = "THESE"
        label1.backgroundColor = #colorLiteral(red: 0.5725490451, green: 0, blue: 0.2313725501, alpha: 1)
        label1.sizeToFit()

// do the same with label2, label3, label4, label5
        
        view.addSubview(label1)
        view.addSubview(label2)
        view.addSubview(label3)
        view.addSubview(label4)
        view.addSubview(label5)

and then I can add constraints manually:

        let dictionary = [
            "label1": label1,
            "label2": label2,
            "label3": label3,
            "label4": label4,
            "label5": label5
        ]

        let metrics=[ "labelHeight":80]

        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[label1(labelHeight@999)]-[label2(label1)]-[label3(label1)]-[label4(label1)]-[label5(label1)]-(>=10)-|", options: [], metrics: metrics, views: dictionary))

        for label in dictionary.keys {
            view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[\(label)]|", options: [], metrics:nil, views: dictionary))
        }

as you can see, I'm setting the first label's height to be 80. Then set label1 to have priority of 999, and make the remaining labels to follow label1's height constraint.

This is working fine, both in portrait and landscape mode.

portrait mode 1 landscape mode 1

Now i'm converting the code to use anchor.

   let heightConstraint = label1.heightAnchor.constraint(equalToConstant: 88)
        heightConstraint.priority = UILayoutPriority(rawValue: 999)
        heightConstraint.isActive = true

        for label in [label2, label3, label4, label5] {
            label.heightAnchor.constraint(equalTo: label1.heightAnchor, multiplier: 1).isActive = true
        }

        var previousLabel : UILabel?
        for label in [label1, label2, label3, label4, label5] {
            label.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
            label.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true

            if let previousLabel = previousLabel {
                label.topAnchor.constraint(equalTo: previousLabel.bottomAnchor, constant: 10).isActive = true
            } else {
                label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
            }
            previousLabel = label
        }
        label5.bottomAnchor.constraint(greaterThanOrEqualTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 10.0).isActive = true

I think I'm missing something here, because

  1. when the app is in portrait mode, it is trying to fill the entire screen
  2. when the app is in landscape mode, label5 is chopped off.

portrait mode 2 landscape mode 2

I think i'm missing something here when using anchor? I'm guessing it is this bit:

-(>=10)-

But i'm not sure how to do it with anchor mode. Any assistance would be greatly appreciated!


Solution

  • For label5 you should change a bottom constraint to:

    label5.bottomAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10.0).isActive = true
    

    It should be a negative number since you fix it to the anchor, which is below (in contrast to previous anchors where you fix it to the label above your current one). And for negative numbers you need to use lessThanOrEqualTo.

    For the vertical layout it works fine too since constraint is lessThanOrEqualTo: