Centering subview's X in autolayout throws "not prepared for the constraint"
Asked Answered
B

8

83

I have a custom UIView subclass which is being initialized via a nib.

In -awakeFromNib, I'm creating a subview and attempting to center it in its superview.

[self setInteralView: [[UIView alloc] init]];
[[self internalView] addConstraint: [NSLayoutConstraint constraintWithItem: [self internalView]
                                                                 attribute: NSLayoutAttributeCenterX
                                                                 relatedBy: NSLayoutRelationEqual
                                                                    toItem: self
                                                                 attribute: NSLayoutAttributeCenterX
                                                                multiplier: 1
                                                                  constant: 0]];

This breaks, and causes the following output:

2013-08-11 17:58:29.628 MyApp[32414:a0b] The view hierarchy is not prepared for the constraint: <NSLayoutConstraint:0xc1dcc80 UIView:0xc132a40.centerX == MyView:0xc1315a0.centerX>
    When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView _viewHierarchyUnpreparedForConstraint:] to debug.
2013-08-11 17:58:29.630 MyApp[32414:a0b] View hierarchy unprepared for constraint.
    Constraint: <NSLayoutConstraint:0xc1dcc80 UIView:0xc132a40.centerX == MyView:0xc1315a0.centerX>
    Container hierarchy: 
<UIView: 0xc132a40; frame = (0 0; 0 0); clipsToBounds = YES; layer = <CALayer: 0xc132bc0>>
    View not found in container hierarchy: <MyView: 0xc1315a0; frame = (-128 -118; 576 804); layer = <CALayer: 0xc131710>>
    That view's superview: <UIView: 0xc131a70; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0xc131b50>>
Berghoff answered 11/8, 2013 at 22:18 Comment(0)
T
96

As the error states, you must add the constraint to a view such that the views involved in the constraint are the view you're adding it to or a subview of that view. In the case of your code above, you should be adding that constraint to self, rather than [self internalView]. The reason it doesn't work as written is one of the views involved in the constraint (self) is not in the view hierarchy if you consider only [self internalView] and below).

For more details, see the discussion section of the documentation on the addConstraint: method.

Here is your line of code, fixed as I suggest:

UIView* internalView = [[UIView alloc] initWithFrame:CGRectZero];
internalView.translatesAutoresizingMaskIntoConstraints = NO;
[self setInternalView:internalView];

[self addConstraint: [NSLayoutConstraint constraintWithItem: [self internalMapView]
                                         attribute: NSLayoutAttributeCenterX
                                         relatedBy: NSLayoutRelationEqual
                                         toItem: self
                                         attribute: NSLayoutAttributeCenterX
                                         multiplier: 1
                                         constant: 0]];
Troublesome answered 11/8, 2013 at 22:23 Comment(12)
That gives me an error claiming Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. And then it breaks <NSLayoutConstraint:0x126ad4c0 UIView:0xadd2e20.centerX == MyView:0xadd19b0.centerX>Berghoff
Most likely because you didn't disable translation of auto resizing mask into constraints. I'll update my code above to do so. You'll probably also need to add width/height constraints for it to be visible, and I would recommend adding a constraint on center Y or some sort of top/bottom constraint to self to make the layout well defined.Troublesome
so you cant have layout like: @"V:|[v]|" ? as this would refer to a superview?Athal
@Athal That's a valid constraint, but it won't center the view represented by [v], it will give you top and bottom constraints of the standard size between [v]'s top and bottom edges and that of its superview. You would also have to add it to the superview, I believe.Troublesome
Thanks for internalView.translatesAutoresizingMaskIntoConstraints = NO;Whitton
A simple thumb rule : Any constraints that relates to the size of a view should be added to the same view and any constraints that relates to its position should be added to its parent.Semblance
@DeepakGM It is certainly possible to add a constraint related to something above the level of the parent, in which case that rule of thumb wouldn't work. The simple rule of thumb is to add the constraint to the common ancestor of the two views you're dealing with, or in the case of one view that view itself.Troublesome
it gives no any error but it does not change the position. why any one explain.Rataplan
@CharlesA.: What is difference between your code and OP's code apart from translatesAutoresizingMaskIntoConstraints = NO? And what is [self internalMapView]? Where does it come from? Your answer seems confusing to me. :(. Please elaborate more.Summersault
@NiteshBorad The only difference is in fact the translatesAutoresizingMaskIntoConstraints = NO statement, which disables the extra constraints that were causing the OP issues. The other code was from the OPs question - I do not know specifically what it does.Troublesome
@CharlesA.: Ok that's clear. But in "constraintWithItem: [self internalMapView]", shouldn't it be "internalView" in place of "internalMapView"? If not, what is "internalMapView"? None of you mentioned about it?Summersault
@NiteshBorad Assuming that the [self setInternalMapView:internalView] ensures that [self internalMapView] returns what was last set, it would be the same reference regardless of if you provide [self internalMapView] or internalView as the parameter. Technically it would be slightly more efficient to just pass internalView as you wouldn't be invoking a method, but it is otherwise the same.Troublesome
P
117

For others who come here: I got this error, because I added the constraints before I added the subviews to the hierarchy.

Parapodium answered 29/7, 2014 at 17:21 Comment(2)
This is the most common reason for this failure I have noticed so far.Riesling
exactly and when you do that constraintWithItem: youView the item is not exist in it!Gudgeon
T
96

As the error states, you must add the constraint to a view such that the views involved in the constraint are the view you're adding it to or a subview of that view. In the case of your code above, you should be adding that constraint to self, rather than [self internalView]. The reason it doesn't work as written is one of the views involved in the constraint (self) is not in the view hierarchy if you consider only [self internalView] and below).

For more details, see the discussion section of the documentation on the addConstraint: method.

Here is your line of code, fixed as I suggest:

UIView* internalView = [[UIView alloc] initWithFrame:CGRectZero];
internalView.translatesAutoresizingMaskIntoConstraints = NO;
[self setInternalView:internalView];

[self addConstraint: [NSLayoutConstraint constraintWithItem: [self internalMapView]
                                         attribute: NSLayoutAttributeCenterX
                                         relatedBy: NSLayoutRelationEqual
                                         toItem: self
                                         attribute: NSLayoutAttributeCenterX
                                         multiplier: 1
                                         constant: 0]];
Troublesome answered 11/8, 2013 at 22:23 Comment(12)
That gives me an error claiming Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. And then it breaks <NSLayoutConstraint:0x126ad4c0 UIView:0xadd2e20.centerX == MyView:0xadd19b0.centerX>Berghoff
Most likely because you didn't disable translation of auto resizing mask into constraints. I'll update my code above to do so. You'll probably also need to add width/height constraints for it to be visible, and I would recommend adding a constraint on center Y or some sort of top/bottom constraint to self to make the layout well defined.Troublesome
so you cant have layout like: @"V:|[v]|" ? as this would refer to a superview?Athal
@Athal That's a valid constraint, but it won't center the view represented by [v], it will give you top and bottom constraints of the standard size between [v]'s top and bottom edges and that of its superview. You would also have to add it to the superview, I believe.Troublesome
Thanks for internalView.translatesAutoresizingMaskIntoConstraints = NO;Whitton
A simple thumb rule : Any constraints that relates to the size of a view should be added to the same view and any constraints that relates to its position should be added to its parent.Semblance
@DeepakGM It is certainly possible to add a constraint related to something above the level of the parent, in which case that rule of thumb wouldn't work. The simple rule of thumb is to add the constraint to the common ancestor of the two views you're dealing with, or in the case of one view that view itself.Troublesome
it gives no any error but it does not change the position. why any one explain.Rataplan
@CharlesA.: What is difference between your code and OP's code apart from translatesAutoresizingMaskIntoConstraints = NO? And what is [self internalMapView]? Where does it come from? Your answer seems confusing to me. :(. Please elaborate more.Summersault
@NiteshBorad The only difference is in fact the translatesAutoresizingMaskIntoConstraints = NO statement, which disables the extra constraints that were causing the OP issues. The other code was from the OPs question - I do not know specifically what it does.Troublesome
@CharlesA.: Ok that's clear. But in "constraintWithItem: [self internalMapView]", shouldn't it be "internalView" in place of "internalMapView"? If not, what is "internalMapView"? None of you mentioned about it?Summersault
@NiteshBorad Assuming that the [self setInternalMapView:internalView] ensures that [self internalMapView] returns what was last set, it would be the same reference regardless of if you provide [self internalMapView] or internalView as the parameter. Technically it would be slightly more efficient to just pass internalView as you wouldn't be invoking a method, but it is otherwise the same.Troublesome
P
36

Short answer: swap parent and child view.
Assuming self is the parent view:

  • bad: [subview addConstraint [NSLayoutConstraint constraintWithItem:self...

  • good: [self addConstraint [NSLayoutConstraint constraintWithItem:subview...


Swift

subview.translatesAutoresizingMaskIntoConstraints = false
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
    "H:|-0-[subview]-0-|",
    options: .DirectionLeadingToTrailing,
    metrics: nil,
    views: ["subview":subview]))

Obj-C

[subview setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addConstraints:[NSLayoutConstraint
                      constraintsWithVisualFormat:@"H:|-0-[subview]-0-|"
                      options:NSLayoutFormatDirectionLeadingToTrailing
                      metrics:nil
                      views:NSDictionaryOfVariableBindings(subview)]];
Philis answered 23/7, 2015 at 5:39 Comment(0)
M
10

Additional Tips:

I have a newly developed App which running fine on iOS7 but get error on iOS8 "The view hierarchy is not prepared for the constraint".

Read some other post said that the (sub)views need to be added before create constraints. I did that but still got the error.

Then I figured out that I should create constraints under -(void)viewDidAppear instead of viewDidLoad

Myelitis answered 19/5, 2015 at 16:54 Comment(1)
When viewDidAppear() is called, the view is already visible to the user. If modifying constraints it should be before that, in viewWillLayoutSubviews(). More of the lifecycle here: medium.com/@SergiGracia/…Creepy
M
6

This is an old question but I want to point out this line from the docs:

When developing for iOS 8.0 or later, set the constraint’s isActive property to true instead of calling the addConstraint(_:) method directly. The isActive property automatically adds and removes the constraint from the correct view.

At the very least, this will remove one point of failure since you no longer need to worry about calling addConstraint on the wrong view

Mansard answered 25/5, 2017 at 21:23 Comment(0)
F
2

In my case, it was solved by using root view as a container of constraints instead of a currently created view.

I mean, use inside viewController

view.addConstraints([leftConstraintImage, topConstraintImage]) instead of imageView.addConstraints...

Fara answered 10/5, 2018 at 21:45 Comment(0)
C
1

As @CharlesA points out in his answer the problem is that the view you are adding is not in the proper view hierarchy. His answer is a good one, but I'm going to throw in some detail for a particular use-case that caused this problem for me:

I had this happen when attempting to add constraints to a view whose view controller I had instantiated using instantiateViewControllerWithIdentifier. To solve the problem, I used [childViewController didMoveToParentViewController:self]. This added the view into the view hierarchy that was referenced by my constraint.

Canzonet answered 29/6, 2014 at 15:23 Comment(0)
B
0

First add your subView and then add your constraints like this

addSubview(yourSubViewHere)
yourSubViewHere.translatesAutoresizingMaskIntoConstraints = false

addConstraint(NSLayoutConstraint(....
Bungle answered 27/9, 2019 at 10:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.