OS X Cocoa Auto Layout隐藏元素

时间:2011-10-27 20:19:05

标签: macos cocoa osx-lion

我正在尝试使用Lion中的新Auto Layout,因为它看起来很不错。但我找不到关于如何做事的好信息。例如:

我有两个标签:

+----------------+
| +------------+ |
| + label 1    | |
| +------------+ |
|                |
| +------------+ |
| | label 2    | |
| +------------+ |
+----------------+

但是第一个标签并不总是填充内容,有时候根本没有内容。我想要做的是自动显示标签2的标签2,当标签1有内容时。

+----------------+
| +------------+ |
| + label 2    | |
| +------------+ |
|                |
|                |
|                |
|                |
+----------------+

我必须添加什么约束才能自动使用autolayout?我知道我可以编写所有代码,但我有大约30个这样的标签和图像以及不同风格和形状的按钮都是可选的,我不想添加代码行的代码,因为它可以自动工作也很好

如果它不起作用,那么我将只使用WebView并使用HTML和CSS。

8 个答案:

答案 0 :(得分:24)

这可以通过自动布局实现,但不能很好地扩展。

所以,举个例子,假设你有标签A,标签B(或按钮或其他任何东西)。首先为A的超视图添加一个顶部约束。然后在A和B之间建立一个垂直间距约束。到目前为止,这是正常的。如果此时要删除A,则B将具有不明确的布局。如果你要隐藏它,它仍会占据它的空间,包括标签之间的空间。

接下来,您需要将B中的另一个约束添加到超级视图的顶部。将此优先级更改为低于其他优先级(比如900),然后将其设置为标准(或其他较小值)。现在,当A从它的超视图中移除时,较低优先级的约束将启动并将B拉向顶部。约束看起来像这样:

Interface Builder screenshot

当您尝试使用一长串标签执行此操作时会出现此问题。

答案 1 :(得分:10)

折叠UILabel子类

一个简单的解决方案就是将UILabel子类化并更改内在内容大小。

@implementation WBSCollapsingLabel

- (CGSize)intrinsicContentSize
{
    if (self.isHidden) {
        return CGSizeMake(UIViewNoIntrinsicMetric, 0.0f);
    } else {
        return [super intrinsicContentSize];
    }
}

- (void)setHidden:(BOOL)hidden
{
    [super setHidden:hidden];

    [self updateConstraintsIfNeeded];
    [self layoutIfNeeded];
}

@end

答案 2 :(得分:4)

此类别使自动布局约束视图的折叠变得非常简单:

https://github.com/depth42/AutolayoutExtensions

我刚将它添加到项目中,效果很好。

答案 3 :(得分:2)

我认为你不能这样做。如果您使标签2的布局基于标签1的距离约束,即使您在没有内容时将标签1自动折叠为零高度,标签2仍将是该距离,即:

+----------------+
| +------------+ |
| + label 1    | |
| +------------+ |
|        ^       |
|        ^       !
| +------------+ |
| | label 2    | |
| +------------+ |
+----------------+

其中^是自动布局距离约束 - 如果标签1知道当字符串为空时如何变为零高度,那么你仍然会得到:

+----------------+
| +------------+ |
|        ^       |
|        ^       !
| +------------+ |
| | label 2    | |
| +------------+ |
+----------------+

也许可以通过手动创建NSLayoutConstraint来实现。您可以将第二个属性设置为标签1的高度,使常量为零,然后仔细计算乘数将根据非零标签高度的倍数使距离成为您想要的距离。

但是,完成所有这些后,您现在编写了一个自动调整大小的NSLabel子类,手动创建了一个约束对象,而不是通过可视语言,并将NSLayoutConstraint置于其意愿之外。

如果标签1的字符串为空白,我认为你最好只更改标签2的框架!

答案 4 :(得分:1)

以下是我如何以编程方式处理此方法而非使用Interface Builder的示例。综上所述;我只添加视图,如果它已启用,然后迭代子视图,添加垂直约束。

请注意,相关视图在此之前已初始化。

/*
  Begin Auto Layout
*/
NSMutableArray *constraints = [NSMutableArray array];
NSMutableDictionary *views = [[NSMutableDictionary alloc] init];


/*
  Label One
*/
if (enableLabelOne) {
    [contentView addSubview:self.labelOne];

    self.labelOne.translatesAutoresizingMaskIntoConstraints = NO;

    [views setObject:self.labelOne
              forKey:@"_labelOne"];

    [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_labelOne(44)]"
                                                                             options:0
                                                                             metrics:nil
                                                                               views:views]];

    [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_labelOne]-|"
                                                                             options:0
                                                                             metrics:nil
                                                                               views:views]];
}

/*
    Label Two
*/
if (enableLabelTwo) {
    [contentView addSubview:self.labelTwo];

    self.labelTwo.translatesAutoresizingMaskIntoConstraints = NO;

    [views setObject:self.labelTwo
              forKey:@"_labelTwo"];

    [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_labelTwo(44)]"
                                                                             options:0
                                                                             metrics:nil
                                                                               views:views]];
}

[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_labelTwo]-|"
                                                                         options:0
                                                                         metrics:nil
                                                                           views:views]];

/*
  Dynamically add vertical spacing constraints to subviews
*/
NSArray *subviews = [contentView subviews];

if ([subviews count] > 0) {
    UIView *firstView = [subviews objectAtIndex:0];
    UIView *secondView = nil;
    UIView *lastView = [subviews lastObject];

    [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[firstView]"
                                                                             options:0
                                                                             metrics:nil
                                                                               views:NSDictionaryOfVariableBindings(firstView)]];

    for (int i = 1; i < [subviews count]; i++) {
        secondView = [subviews objectAtIndex:i];
        [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[firstView]-10-[secondView]"
                                                                                 options:0
                                                                                 metrics:nil
                                                                                   views:NSDictionaryOfVariableBindings(firstView, secondView)]];
        firstView = secondView;
    }

    [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[lastView]-|"
                                                                             options:0
                                                                             metrics:nil
                                                                               views:NSDictionaryOfVariableBindings(lastView)]];
}


[self addConstraints:constraints];

我只设置lastView约束,因为此代码是根据UIScrollView中的内容改编的。

我最初是根据this Stack Overflow answer实现的,并根据自己的需要进行了改动。

答案 5 :(得分:0)

我发现了一个非常好的方式来做到这一点。它与David's类似。以下是它在代码中的工作原理。我创建了superview及其所有子视图,甚至是那些可能并不总是显示的子视图。我在superview中添加了许多约束,例如V:|-[_btn]。正如您在这些约束的末尾所看到的那样,在superview上没有到底部的链接。然后我为视图的两个状态创建了两个约束数组,对我来说差异是“更多选项”的显示三角形。然后,当根据它的状态单击三角形时,我相应地添加和删除约束和子视图。例如,添加我做:

[self.backgroundView removeConstraints:self.lessOptionsConstraints];
[self.backgroundView addSubview:self.nameField];
[self.backgroundView addConstraints:self.moreOptionsConstraints];

我删除的约束将按钮绑定到超级视图的底部,如V:[_btn]-|。我添加的约束看起来像V:[_btn]-[_nameField]-|,因为你可以看到这个约束将新视图放在它上面的原始视图和超视图底部之间,它延伸了超视图的高度。

答案 6 :(得分:0)

我找到了另一种方法。这种方法可以应用于任何地方,没有扩展问题;并处理边距。你不需要第三方的东西。

首先,不要使用这种布局:

V:|-?-[Label1]-10-[Label2]-10-|
H:|-?-[Label1]-?-|
H:|-20-[Label2]-20-|

请改用:

("|" is the real (outer) container)
V:|-?-[Label1]-0-[Label2HideableMarginContainer]-0-|
H:|-?-[Label1]-?-|
H:|-0-[Label2HideableMarginContainer]-0-|

("|" is Label2HideableMarginContainer)
V:|-10-[Label2]-10-|
H:|-20-[Label2]-20-|

那我们现在做了什么? Label2不直接用于布局;它被放入Margin-Container。该容器用作Label2代理布局中有0个边距。真实的边距放在Margin-Container里面。

现在我们可以隐藏Label2

  • Hidden设置为YES

  • 禁用TopBottomLeadingTrailing限制。所以要找出它们,而不是将Active设置为NO。这将导致Margin-Container的{​​{1}}为(0,0);因为它确实有子视图;但是没有任何(活动的)布局约束将这些子视图锚定到它。

也许有点复杂,但你只需要开发一次。所有逻辑都可以放在一个单独的地方,每次你需要隐藏smg时都可以重复使用。

这是C#Xamarin代码如何寻找那些将子视图锚定到Frame Size视图的内边缘的约束:

Margin-Container

答案 7 :(得分:0)

以编程方式解决此问题。我连续几个按钮,我可能决定随时隐藏一个按钮。

enter image description here

每次hidden更改时,都会使用Cartography替换它们。

let buttons = self.buttons!.filter { button in
    return !button.hidden
}

constrain(buttons, replace: self.constraintGroup) { buttons in
    let superview = buttons.first!.superview!

    buttons.first!.left == superview.left

    for var i = 1; i < buttons.count; i++ {
        buttons[i].left == buttons[i-1].right + 10
    }

    buttons.last!.right == superview.right
}