2

I have separate arrays of NSLayoutConstraints built using visual formatting strings that I want to use for portrait and landscape orientations. I have tried two methods for switching between the two: activating/deactivating them, and adding/removing them.

Activating/Deactivating:

portaitConstraints.forEach {
    $0.active = false
}
landscapeConstraints.forEach {
    $0.active = true
}

Adding/Removing:

self.view.removeConstraints(portraitConstraints)
self.view.addConstraints(landscapeConstraints)

Both methods seem to work fine and behave as expected, but I get the following runtime error with the activate/deactivate method:

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) 

Is there a common pattern for this? Can I safely ignore the error if it behaves as expected? Is there more overhead for one method versus the other? I thought it made more sense to just flip the "active" property on the ones I wanted active versus repeatedly adding and removing constraints to/from the view.

Or is this just a dumb way to do this, and I need to do something like change the NSLayoutAttributes directly?

Silviu St
  • 1,810
  • 3
  • 33
  • 42
alexp
  • 3,587
  • 3
  • 29
  • 35

2 Answers2

1

You probably have a layout ambiguity, some of the constraints may be conflict with each other. Usually this happens if you haven't set translatesAutoresizingMaskIntoConstraints = NO for the view you setting constraints to. The console gives you the list of conflicting constraints and you should resolve the layout ambiguity, because it may lead to wrong layout. It's not fatal, but if you want a good looking app - you need to look at it.

WWDC 2015 has a great videos, Mysteries of Auto Layout, Part 1 and Mysteries of Auto Layout, Part 2 that describes how you can find conflicting constraints fast and solve it. These are also a great videos to understand auto layout as a whole. There are some more videos at WWDC, like WWDC2012, Auto Layout by Example, Best Practices for Mastering Auto Layout or Taking Control of Auto Layout in Xcode 5 that covers a lot of information on auto layout.

Sega-Zero
  • 3,034
  • 2
  • 22
  • 46
  • Thanks. I should clarify everything has translatesAutoresizingMaskIntoConstraints = false. According to the "Best Practices for Mastering Auto Layout,"the pattern is this: Set the constraints in updateConstraints. When the constraints need to change, remove them all, and then call setNeedsUpdateConstraints(). – alexp Sep 06 '15 at 19:35
  • 1
    Well, this is true, if you're targeting iOS versions prior to iOS8. In "Mysteries of Auto Layout" session, apple recommends moving to activate/deactivate constraints instead of add/remove. And you can add those constraints once right in viewDidLoad. Then, activate the constraints you need when rotation is changed. Also, a size classes can help you to build a different layout in lanscape right in IB without any constraint creating in code - you can set a different constraints in Any|Compact mode which means iphones in lanscape. But it will work in iOS8+ of course. – Sega-Zero Sep 06 '15 at 20:31
0

I had a similar problem with horizontal-display constraints that couldn't be satisfied on a vertical display, and vertical-display constraints that couldn't be satisfied on a horizontal display. The key appears to be deactivating and activating the correct constraints at the right times. Here's how I did it, and ended up avoiding errors:

override func viewWillLayoutSubviews() {
  if view.bounds.size.height >= view.bounds.size.width {
    horizontalOrientationConstraints.forEach { $0.isActive = false }
    verticalOrientationConstraints.forEach { $0.isActive = true }
  } else {
    verticalOrientationConstraints.forEach { $0.isActive = false }
    horizontalOrientationConstraints.forEach { $0.isActive = true }
  }
  self.updateDoneButton()
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
  for constraint in horizontalOrientationConstraints + verticalOrientationConstraints {
    constraint.isActive = false
  }
}