你可以通过自动布局得到你想要的。示范:
但是,如果绿色视图没有任何垂直重叠,则结果可能不是您想要的:
那么,我是怎么做到的呢?
首先,有五种观点。有窗口的内容视图、两个可拖动的绿色视图、文本字段(“中心”)和棕褐色间隔视图。可拖动视图、文本字段和分隔符都是窗口内容视图的直接子视图。特别是,文本字段不是间隔视图的子视图。
其次,我需要设置一些约束的优先级,所以我定义了一个辅助函数:
static NSLayoutConstraint *constraintWithPriority(NSLayoutConstraint *constraint,
NSLayoutPriority priority)
{
constraint.priority = priority;
return constraint;
}
接下来我创建左侧可拖动视图。我为其 X 位置和宽度、高度和 Y 位置设置了约束。
- (void)createLeftView {
leftView = [[DraggableView alloc] init];
leftView.translatesAutoresizingMaskIntoConstraints = NO;
[rootView addSubview:leftView];
NSDictionary *views = NSDictionaryOfVariableBindings(leftView);
[rootView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"H:|-(20)-[leftView(80)]"
options:0 metrics:nil views:views]];
[leftView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:[leftView(120)]"
options:0 metrics:nil views:views]];
leftView.yConstraint = [NSLayoutConstraint
constraintWithItem:leftView attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:rootView attribute:NSLayoutAttributeTop
multiplier:1 constant:20];
[rootView addConstraint:leftView.yConstraint];
}
请注意,我的DraggableView
类通过调整其yConstraint
. 由于我需要访问 Top 约束,因此我直接设置了该约束,而不是使用可视格式。
我非常相似地创建了正确的可拖动视图,除了它被锚定到根视图的后缘。
- (void)createRightView {
rightView = [[DraggableView alloc] init];
rightView.translatesAutoresizingMaskIntoConstraints = NO;
[rootView addSubview:rightView];
NSDictionary *views = NSDictionaryOfVariableBindings(rightView);
[rootView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"H:[rightView(80)]-(20)-|"
options:0 metrics:nil views:views]];
[rightView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:[rightView(120)]"
options:0 metrics:nil views:views]];
rightView.yConstraint = [NSLayoutConstraint
constraintWithItem:rightView attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:rootView attribute:NSLayoutAttributeTop
multiplier:1 constant:20];
[rootView addConstraint:rightView.yConstraint];
}
现在是棘手的部分。我创建了一个额外的视图,我称之为垫片。我已经让视图可见,以便更容易理解这个演示是如何工作的。通常你会把垫片隐藏起来;隐藏视图仍然参与布局。
- (void)createSpacerView {
spacerView = [[SpacerView alloc] init];
spacerView.translatesAutoresizingMaskIntoConstraints = NO;
[rootView addSubview:spacerView];
垫片的水平约束很简单。分隔符的前缘被固定到左侧可拖动视图的后缘,而分隔符的后缘被固定到右侧可拖动视图的前缘。因此,垫片总是准确地跨越可拖动(绿色)视图之间的水平间隙。
NSDictionary *views = NSDictionaryOfVariableBindings(leftView, rightView, spacerView);
[rootView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"H:[leftView][spacerView][rightView]"
options:0 metrics:nil views:views]];
接下来,我们将 spacer 的顶部和底部边缘约束为等于根视图的顶部和底部边缘,但优先级为 1(优先级极低):
[rootView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:|-(0@1)-[spacerView]-(0@1)-|"
options:0 metrics:nil views:views]];
然后我们进一步限制间隔的上边缘大于或等于两个可拖动视图的上边缘的上边缘,优先级为 2(几乎是极低):
[rootView addConstraint:constraintWithPriority([NSLayoutConstraint
constraintWithItem:spacerView attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:leftView attribute:NSLayoutAttributeTop
multiplier:1 constant:0], 2)];
[rootView addConstraint:constraintWithPriority([NSLayoutConstraint
constraintWithItem:spacerView attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:rightView attribute:NSLayoutAttributeTop multiplier:1 constant:0], 2)];
所以垫片的上边缘有三个约束:
- spacerView.top == rootView.top 优先级为 1
- spacerView.top >= leftView.top 优先级为 2
- spacerView.top >= rightView.top 优先级为 2
这些约束结合起来,使垫片的顶部边缘尽可能高,同时不高于两个可拖动视图的顶部边缘。因此,间隔顶部边缘将等于可拖动视图的顶部边缘中的较低者。
我们需要在这里使用低优先级,因为自动布局将拒绝使垫片的高度为负。如果我们使用优先级 1000(默认),自动布局将开始调整可拖动视图的大小以强制它们始终具有一些垂直重叠。
我们在垫片的底部边缘设置了类似的约束:
[rootView addConstraint:constraintWithPriority([NSLayoutConstraint
constraintWithItem:spacerView attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:leftView attribute:NSLayoutAttributeBottom
multiplier:1 constant:0], 2)];
[rootView addConstraint:constraintWithPriority([NSLayoutConstraint
constraintWithItem:spacerView attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:rightView attribute:NSLayoutAttributeBottom
multiplier:1 constant:0], 2)];
}
因此,如果存在重叠,则分隔符将始终跨越可拖动视图的垂直重叠。如果没有重叠,自动布局将打破一些约束,以避免给定间隔一个负高度。间隔将最终高度为 0,固定到可拖动视图的近边缘之一。
最后,我将文本字段设置为在分隔符上水平和垂直居中:
- (void)createMiddleView {
middleView = [[NSTextField alloc] init];
middleView.translatesAutoresizingMaskIntoConstraints = NO;
middleView.stringValue = @"Center";
[middleView setEditable:NO];
[middleView setSelectable:NO];
[rootView addSubview:middleView];
[rootView addConstraint:[NSLayoutConstraint
constraintWithItem:middleView attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:spacerView attribute:NSLayoutAttributeCenterX
multiplier:1 constant:0]];
[rootView addConstraint:[NSLayoutConstraint
constraintWithItem:middleView attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:spacerView attribute:NSLayoutAttributeCenterY
multiplier:1 constant:0]];
}
由于五个视图(包括根视图)之间的所有这些约束,自动布局使中间视图以您请求的方式居中。
我不确定从 Xcode 4.6.3 开始是否可以在 nib 中设置这样的约束。如果可能的话,我敢肯定这是非常痛苦的。
您可以在这个 github 存储库中找到我的完整演示项目源代码。我在 Xcode 5-DP2 中创建了项目。