WWDC 2012 Best Practices for Mastering Auto Layoutを見ると、この動的なサブビューや制約の構築について説明layoutSubviews
されています (ビデオの約 45 分)。これは、適合するビューを追加することに重点を置いていますが、シナリオでも使用できます。
UIView
これらすべてのラベルを含むコンテナー ビューのサブクラスを作成し、オーバーライドlayoutSubviews
して制約を適切に構成するという考え方です。少し毛むくじゃらですが、機能します:
- (void)layoutSubviews
{
[super layoutSubviews];
// add any labels for my `toRecipients` (and add to my dictionary that keeps
// track of which `UILabel` is for which `toRecipient`)
for (NSString *toRecipient in self.toRecipients)
{
UILabel *label = self.toLabels[toRecipient];
if (!label)
{
label = [[UILabel alloc] init];
label.text = toRecipient;
label.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:label];
[self.toLabels setObject:label forKey:toRecipient];
}
}
// remove any existing constraints on subviews (you could keep track of them and
// modify them, but I find it just as easy as to start from scratch every time)
NSMutableArray *constraintsToRemove = [NSMutableArray array];
for (NSLayoutConstraint *constraint in self.constraints)
{
if ([constraint.firstItem superview] == self || [constraint.secondItem superview] == self) {
[constraintsToRemove addObject:constraint];
}
}
[self removeConstraints:constraintsToRemove];
// add initial constraints for that leading "To:" label, putting it in the upper left corner
NSDictionary *views = @{@"to" : self.toLabel};
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[to]" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[to]" options:0 metrics:nil views:views]];
// now let's iterate through the `toRecipients`, and for each one, try adding
// the label, see where constraints put it (using the label's intrinsic size, too),
// and if it was too wide, then remove those constraints and add new constraints
// to put it on the next line
UIView *previousView = self.toLabel;
for (NSString *toRecipient in self.toRecipients)
{
UIView *nextView = self.toLabels[toRecipient];
views = NSDictionaryOfVariableBindings(previousView, nextView);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:[previousView]-[nextView]" options:0 metrics:nil views:views];
[self addConstraints:constraints];
[self updateConstraintsIfNeeded];
[super layoutSubviews];
if (CGRectGetMaxX(nextView.frame) < CGRectGetMaxX(self.bounds))
{
// if there was room, let's also set the baseline to be the same as the previous item
[self addConstraint:[NSLayoutConstraint constraintWithItem:nextView attribute:NSLayoutAttributeBaseline relatedBy:NSLayoutRelationEqual toItem:previousView attribute:NSLayoutAttributeBaseline multiplier:1.0 constant:0.0]];
}
else
{
// otherwise, let's get rid of the constraints I just added and move this next item down to the next line
[self removeConstraints:constraints];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[nextView]" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[previousView]-[nextView]" options:0 metrics:nil views:views]];
}
previousView = nextView;
}
// set the bottom constraint for the last recipient
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[nextView]-|" options:0 metrics:nil views:views]];
[self updateConstraintsIfNeeded];
[super layoutSubviews];
}
動的な高さの鍵は、最後の制約です。このUIView
サブクラスを内部のラベルの制約と固有のサイズに基づいてサイズ変更すると、このビューの高さは内部のラベルによって定義されます。このサブクラス化されたビューをメイン ビューに追加するとき、メイン ビューによって定義される上、左、右を定義しますが、下の制約は未定義のままにして、上の最後の制約で高さを定義します。したがって、このサブクラスをメイン ビューに追加すると、次のようになります。
RecipientsView *recipientsView = [[RecipientsView alloc] init];
recipientsView.toRecipients = @[@"rob@frankfurt.de", @"r@berlin.de", @"frank@dusseldorf.de", @"ernest@munich.de", @"mo@cologne.de", @"curly@stuttgart.de"];
recipientsView.backgroundColor = [UIColor lightGrayColor];
recipientsView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:recipientsView];
self.recipientsView = recipientsView;
// set the left, right, and top constraints to the main view, but I'll let the
// intrinsic size of the labels dictate the height of this UIView subclass, RecipientsView
NSDictionary *views = NSDictionaryOfVariableBindings(recipientsView);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[recipientsView]|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[recipientsView]" options:0 metrics:nil views:views]];
[self.view layoutIfNeeded];
背景色を明るい灰色に設定しているので、すべてのラベルとその制約に基づいて高さが動的に設定されていることがわかります。