1

I have set safeAreaLayoutGuide like this:

let guide = view.safeAreaLayoutGuide

NSLayoutConstraint.activate([
  topControls.topAnchor.constraint(equalTo: guide.topAnchor, constant: 0)
])

this works great. However, when I rotate device to landscape mode, there is no status bar (battery, signal etc) and the offset from top of the screen is zero. That is correct, based on anchor. However, it does not look good. How can I add offset 20 for landscape during anchors creation? I don't want to change the constant based on orientation manually.

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Martin Perry
  • 9,232
  • 8
  • 46
  • 114

1 Answers1

2

Assuming you are running on a iPhone then this is expected behaviour and can be changed by overriding the following:

override var prefersStatusBarHidden: Bool {
    return false
}

From the description of that UIViewController method:

By default, this method returns false with one exception. For apps linked against iOS 8 or later, this method returns true if the view controller is in a vertically compact environment.

If you override that then you will get a status bar in landscape orientation and the safe area guide will adapt accordingly.

EDIT

So a slight misunderstanding with the original requirement. It's not to show the status bar but to have a gap like if there had been a status bar.

This will need to be done manually as you can't setup constraints that only apply in certain orientations/size class manually.

This is a basic UIViewController that will do what you are looking for:

class ViewController: UIViewController {
    var testView: UIView!
    var landscapeConstraint: NSLayoutConstraint!
    var portraitConstraint: NSLayoutConstraint!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        self.testView = UIView()
        self.testView.backgroundColor = .green
        self.view.addSubview(self.testView)
        self.testView.translatesAutoresizingMaskIntoConstraints = false

        let guide = view.safeAreaLayoutGuide

        self.testView.leftAnchor.constraint(equalTo: guide.leftAnchor, constant: 0).isActive = true
        self.testView.rightAnchor.constraint(equalTo: guide.rightAnchor, constant: 0).isActive = true
        self.testView.bottomAnchor.constraint(equalTo: guide.bottomAnchor, constant: 0).isActive = true

        self.portraitConstraint = self.testView.topAnchor.constraint(equalTo: guide.topAnchor, constant: 0)
        self.landscapeConstraint = self.testView.topAnchor.constraint(equalTo: guide.topAnchor, constant: 20) // Hardcoded the size but this can be anything you want.

        switch self.traitCollection.verticalSizeClass {
        case .compact:
            self.landscapeConstraint.isActive = true
        case.regular:
            self.portraitConstraint.isActive = true
        default: // This shouldn't happen but let's assume regular (i.e. portrait)
            self.portraitConstraint.isActive = true
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
        super.willTransition(to: newCollection, with: coordinator)

        switch newCollection.verticalSizeClass {
        case .compact:
            self.portraitConstraint.isActive = false
            self.landscapeConstraint.isActive = true
        case.regular:
            self.landscapeConstraint.isActive = false
            self.portraitConstraint.isActive = true
        default: // This shouldn't happen but let's assume regular (i.e. portrait)
            self.landscapeConstraint.isActive = false
            self.portraitConstraint.isActive = true
        }
    }
}

Basically you set up the fixed constraints, i.e. left, right and bottom, and then setup constraints for portrait and landscape (regular and compact vertical size class) which are both disabled by default. Then you decide which one to activate based on the current orientation/size class. Then you override the:

willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)

method and swap active state of the two constraints based on the new orientation/size class.

One thing to note is to always deactivate constraints first and then activate ones to avoid anything complaining about constraints that can't be satisfied.

Upholder Of Truth
  • 4,643
  • 2
  • 13
  • 23
  • This is a "half-solution". Status bar in landscape mode is not very often. – Martin Perry Jan 01 '18 at 12:38
  • Sorry I don't understand. What do you mean 'status bar in landscape mode is not very often'? Do you not want the status bar but still have the gap from the top where it would be? – Upholder Of Truth Jan 01 '18 at 12:48
  • Yes... gap, but no status bar – Martin Perry Jan 01 '18 at 13:30
  • Ok got it. Well you are going to have to do it manually as you can't setup constraints which apply in only one orientation or the other and you should really be talking size classes rather than orientations. I will update my answer with one approach that you can take. – Upholder Of Truth Jan 01 '18 at 13:37
  • Thank you. Manuall changing of constraints helped. It is not as bad as I woried the code would be :-) – Martin Perry Jan 01 '18 at 14:02