I have a UIView in IB that has another UIView within it, which I am using as a container view. In code, I create three different views and then animate the appropriate one
In the problem you state "my new subviews are not scaled to match the container view" - but from the description I think they are - the problem is that your container view has no fixed size.
I think that if you set your container view to have a fixed width and height that should do it, you may also need to call
[self.containerView layoutSubviews]
to force a resize after updating the constraints.
I'd also suggest you swap to using the text based formatting, you could swap out your lines above for something like "H:|[newSubview]|" and "V:|[newSubview]|"
A couple of thoughts:
This basically looks fine, other than some variable name weirdness: Your local variable, containerView
is equal to self.subView
, but all of your other lines refer to a different variable, a class property, self.containerView
. Did you omit a line where you set that containerView
class property? But when I fixed that, your code worked fine for me.
Make sure you're not trying to look at the frame
immediately after setting the constraints, as the change will not yet be reflected in the frame
settings. You can do a [containerView layoutIfNeeded];
if you want to force it to relayout everything based upon the constraints. Also, if you want to confirm frame
settings, it's better to defer looking at those values until after viewDidAppear
(i.e. viewDidLoad
is too early in the view construction process).
A minor tweak on your code (and unrelated to your problem), but when I'm setting the constraints within a container view, I often will set not only NSLayoutAttributeTop
and NSLayoutAttributeLeading
, like you did, but also NSLayoutAttributeBottom
and NSLayoutAttributeTrailing
(rather than NSLayoutAttributeWidth
and NSLayoutAttributeHeight
). It accomplishes the same thing as your code, but when using non-zero values, it is a fraction more intuitive.
Anyway, I just did the following code, permutation of yours, and it works fine:
- (IBAction)didTouchUpInsideAddView:(id)sender
{
UIView *containerView = self.containerView;
UIView *newSubview = [[UIView alloc] initWithFrame:CGRectZero]; // initializing with CGRectZero so we can see it change
newSubview.translatesAutoresizingMaskIntoConstraints = NO;
newSubview.backgroundColor = [UIColor lightGrayColor];
[containerView addSubview:newSubview];
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0]];
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:0.0]];
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0]];
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:0.0]];
// the frame is still `CGRectZero` at this point
NSLog(@"newSubview.frame before = %@", NSStringFromCGRect(newSubview.frame));
// if not doing this in `viewDidLoad` (e.g. you're doing this in
// `viewDidAppear` or later), you can force `layoutIfNeeded` if you want
// to look at `frame` values. Generally you don't need to do this unless
// manually inspecting `frame` values or when changing constraints in a
// `animations` block of `animateWithDuration`.
[containerView layoutIfNeeded];
// everything is ok here
NSLog(@"containerView.bounds after = %@", NSStringFromCGRect(containerView.bounds));
NSLog(@"newSubview.frame after = %@", NSStringFromCGRect(newSubview.frame));
}
You can simplify that code a little using visual format language, e.g.:
NSDictionary *views = NSDictionaryOfVariableBindings(newSubview);
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[newSubview]|"
options:0
metrics:nil
views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[newSubview]|"
options:0
metrics:nil
views:views]];
I find it easier to get the constraints right using visual format language. A little less error-prone (for me, at least). There are, though, some constraints that cannot be represented in visual format language, in which case I fall back to the syntax you outline.
In your revised question, you show us an init
method for your subview, which does another addSubview
. You have to set constraints there, too. Bottom line, wherever you do addSubview
, you have to set your constraints, e.g.
- (id)init
{
if(self = [super init])
{
NSArray *nibArray = [[NSBundle mainBundle]loadNibNamed:@"MySubview" owner:self options:nil];
UIView *subview = [nibArray objectAtIndex:0];
subview.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:subview];
NSDictionary *views = NSDictionaryOfVariableBindings(subview);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[subview]|"
options:0
metrics:nil
views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[subview]|"
options:0
metrics:nil
views:views]];
}
return self;
}