8

I have two UI layout constraints that are conflicting with each other by design. Only one of could be active at a time.

In UIViewController's method updateConstraintsIfNeeded, I have the following code which toggles between the two constraints, depending on the state of a data model.

override func updateConstraintsIfNeeded() {
    super.updateConstraintsIfNeeded()

    if question?.thumbURL != nil {
        showAttachmentConstraint.active = true
        hideAttachmentConstraint.active = false        
    } else {
        showAttachmentConstraint.active = false
        hideAttachmentConstraint.active = true
    }
}

This work as intended, but I got this familiar warning in the debug output:

Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. ...

Apparently when the statement showAttachmentConstraint.active = true is executed, it temporarily conflicts with hideAttachmentConstraint which is still active at that time.

Is it possible to make this toggle operation atomic? I'm hoping there is something like beginUpdate and endUpdate in UITableView.

shim
  • 9,289
  • 12
  • 69
  • 108
Andree
  • 3,033
  • 6
  • 36
  • 56
  • 1
    Are you sure two constraints is what you need? Can you not just have one and manipulate it in code instead? Anyway, try inverting the order of operations in the first if branch, that way you first disable the unnecessary constraint, and then enable the one you want. Between the two statements, you have no constraints active at the same time. – Morpheu5 Nov 23 '15 at 09:49
  • Try to put it inside UIView animation block. ;) – Omar Al-Shammary Nov 23 '15 at 09:52
  • Check out this [answer](http://stackoverflow.com/a/26022729/2177402) – Islam Nov 23 '15 at 10:30
  • 1
    @Morpheu5 Good question. So I ended up going with the one constraint approach and manipulate the constant as you suggested. And your suggestion to arrange the order of constraints activation/deactivation is clever, although I haven't yet to confirm if works. Thanks anyway! – Andree Nov 24 '15 at 00:49

2 Answers2

7

you could change the priority of one of the conflicting constraints to 999 instead of 1000. so you do not even have any problems at design time.

André Slotta
  • 13,774
  • 2
  • 22
  • 34
  • Changing priorities should really be a last resort kind of thing. It's best to have the fewest constraints possible, and manipulate them instead. This makes it easier to reason about them. – Morpheu5 Nov 24 '15 at 08:15
  • you should not misuse it. but i don't see a problem in using it in this case. as the official documentation states: `[...]This combination of inequalities, equalities, and priorities gives you a great amount of flexibility and power. By combining multiple constraints, you can define layouts that dynamically adapt as the size and location of the elements in your user interface change.[...]` – André Slotta Nov 24 '15 at 14:11
  • This is indeed a simple enough case that will likely cause no headaches. I'm just saying that, in more complex cases, you might want to first look at other, clearer alternatives before tinkering with priorities. They are less apparent, and thus, if not properly documented, they can easily go unnoticed and forgotten. – Morpheu5 Nov 24 '15 at 18:23
4

Always first deactivate some constraints and then activate the other constraints:

override func updateConstraintsIfNeeded() {
    super.updateConstraintsIfNeeded()

    if question?.thumbURL != nil {
        hideAttachmentConstraint.active = false
        showAttachmentConstraint.active = true
    } else {
        showAttachmentConstraint.active = false
        hideAttachmentConstraint.active = true
    }
}
Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
  • what if these constraints are stored inside `UIViewController`? – Vyachaslav Gerchicov Aug 15 '19 at 13:13
  • @VyachaslavGerchicov, What about it? It should be the same. – Iulian Onofrei Sep 26 '19 at 08:41
  • Now I have understood. It seems the app tries to check constraints after each constraint change but anyways the final result will be ok. – Vyachaslav Gerchicov Sep 26 '19 at 09:25
  • This can be reduced from 7 to 2 lines of code (without explicit if-statements). `hideAttachment...active = question.thumbURL == nil` and `showAttachment...active = question.thumbURL != nil`. – Sti Apr 06 '21 at 16:44
  • @Sti, If you do this, if `question.thumbURL` is `nil`, the `hideAttachment` constraint will be activated first, which is exactly the problem described in the question. – Iulian Onofrei Apr 06 '21 at 18:41
  • You're right, I'm dumb. Didn't read the actual question or answer (came here by googling something else), but definitely didn't know the order of activation/deactivation was important. Glad to have learned something! – Sti Apr 06 '21 at 21:26
  • @Sti, No, you're not, we all make mistakes :) Really glad I could help you. – Iulian Onofrei Apr 07 '21 at 09:24