在两个其他视图之间自动布局和居中视图

时间:2013-06-27 03:23:54

标签: cocoa nsview autolayout

所以我有两个矩形视图和一个将它们连接在一起的视图。见图http://cl.ly/image/340Q1l381b3L

是否可以约束红色视图,使其保持在绿色视图的垂直边界内?我想把它放在重叠区域的中心位置。

虽然这不会使它居中,但我认为我只能将以下两个约束应用于绿色视图和红色视图,以至少将红色视图绑定到绿色视图。

[NSLayoutConstraint
 constraintWithItem:redView
 attribute:NSLayoutAttributeCenterY
 relatedBy:NSLayoutRelationGreaterThanOrEqual
 toItem:greenView
 attribute:NSLayoutAttributeBottom
 multiplier:1.0f
 constant:0.0f];

[NSLayoutConstraint
 constraintWithItem:redView
 attribute:NSLayoutAttributeCenterY
 relatedBy:NSLayoutRelationLessThanOrEqual
 toItem:greenView
 attribute:NSLayoutAttributeTop
 multiplier:1.0f
 constant:0.0f];

但这会导致

  

2013-06-26 22:13:27.493 MiniMeasure [25896:303]无法同时满足约束条件:   (       “NSLayoutConstraint:0x1002cebf0 RedView:0x1002cdf90.centerY> = GreenView:0x1018a34c0.bottom”,       “NSLayoutConstraint:0x1002cf720 RedView:0x1002cdf90.centerY< = GreenView:0x1018a34c0.top”,       “NSLayoutConstraint:0x1002bb1e0 V:[GreenView:0x1018a34c0(157)]”   )将尝试通过破坏约束来恢复NSLayoutConstraint:0x1002bb1e0 V:[GreenView:0x1018a34c0(157)]

很明显,它不喜欢绿色视图中的一个有高度约束并试图打破它。但我需要绿色视图来保持它们的大小。两个绿色视图都有宽度和高度限制。

有任何想法/建议吗?

谢谢!

2 个答案:

答案 0 :(得分:11)

您可以通过自动布局获得所需内容。示范:

demo

但是,如果绿色视图没有任何垂直重叠,则结果可能不是您想要的结果:

enter image description here

那么,我是怎么做到的?

首先,有五个观点。有窗口的内容视图,两个可拖动的绿色视图,文本字段(“中心”)和棕褐色间隔视图。可拖动视图,文本字段和间隔符都是窗口内容视图的直接子视图。特别是,文本字段不是间隔视图的子视图。

其次,我需要设置一些约束的优先级,所以我定义了一个辅助函数:

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];
}

现在是棘手的部分。我创建了一个额外的视图,我称之为spacer。我已经将视图保持可见,以便更容易理解此演示的工作原理。通常你会隐藏间隔物;隐藏的视图仍然参与布局。

- (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中设置这样的约束。如果有可能的话,我确定这很痛苦。

您可以找到我的完整演示项目源代码in this github repository。我在Xcode 5-DP2中创建了这个项目。

答案 1 :(得分:0)

根据评论,一个解决方案是不使用红色矩形的自动布局,而是简单地在超级视图或与超级视图相同的框架矩形的子视图中使用drawRect。

首先使用KVO通知我们的绿色范围框架的变化(适合您的实施)

改变?

如果在两个绿色rects上使用NSUnionRect,一些if语句可以告诉您是否显示红色rect,并提供绘制或定位rect的位置的逻辑。

如果联合矩形大于绿色在一个方向上的总和而在另一个方向上的较小,则绘制一个红色矩形。

通过比较边缘可以绘制的位置。

伪代码 如果unionRect.size.x> (greenRect1.size.x + greenRect2.size.x)AND unionRect.size.y< (greenRect1.size.y + greenRect2.size.y) 然后在它们之间水平绘制一个红色矩形。 现在计算红色矩形的矩形。

您可以使用相同的方法找出相对于绿色rects使用红色rect的约束,但这实际上会增加复杂性。

我不相信一切都属于自动布局,因为工作量和复杂性以及潜在的意义混淆。