【问题标题】:OS X Cocoa Auto Layout hidden elementsOS X Cocoa 自动布局隐藏元素
【发布时间】:2011-12-16 20:03:12
【问题描述】:

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

我有两个标签:

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

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

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

我必须添加哪些约束才能使其自动与自动布局一起使用?我知道我可以编写所有代码,但是我有大约 30 个这样的标签和图像以及不同样式和形状的按钮,这些都是可选的,我不想添加数百行代码,因为它可以自动运行也很好.

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

【问题讨论】:

  • 我不确定自动布局是否可行,但看起来你真正想要的是一张桌子。
  • 一个表格如果只是从上到下会有帮助,但有些东西太从左到右了,应该代替其他东西和东西。但我承认这个想法还不错。

标签: macos cocoa osx-lion


【解决方案1】:

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

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

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

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

【讨论】:

  • 第二部分是如何工作的?如果从 B 向 superview 添加另一个约束,它不总是不管优先级触发吗?
  • 没关系,我试图在我的场景中使用这个特定的逻辑,但最终没有成功:我有按钮 A、B、C。我正在删除 B,我希望 C B的地方。
  • 这是一个干净的解决方案。对于更多标签的情况,在代码中复制它可能是最容易的。 IB 在弹出的“添加新约束”编辑器中建议一个解决方案,当它引用“最近的邻居”时。如果它真的这样做了,它就会开箱即用。我认为问题在于它们绑定(“neigbhor”)太早了,您可以在故事板文件格式中看到这一点。
【解决方案2】:

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

每次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
}

【讨论】:

    【解决方案3】:

    我找到了另一种方法来做到这一点。这种方法可以在任何地方应用,没有扩展问题;并处理边距。而且你不需要 3rd 方的东西。

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

    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 约束。所以找出它们,而不是在它们上设置 ActiveNO。这将导致Margin-ContainerFrame Size 为(0,0);因为它确实有子视图;但没有任何(活动)布局约束将这些子视图锚定到它。

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

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

    public List<NSLayoutConstraint> SubConstraints { get; private set; }
    
    private void ReadSubContraints()
    {
        var constraints = View.Constraints; // View: the Margin-Container NSView
        if(constraints?.Any() ?? false)
        {
            SubConstraints = constraints.Where((NSLayoutConstraint c) => {
                var predicate = 
                    c.FirstAttribute == NSLayoutAttribute.Top ||
                    c.FirstAttribute == NSLayoutAttribute.Bottom ||
                    c.FirstAttribute == NSLayoutAttribute.Leading ||
                    c.FirstAttribute == NSLayoutAttribute.Trailing;
                predicate &= ViewAndSubviews.Contains(c.FirstItem); // ViewAndSubviews: The View and View.Subviews
                predicate &= ViewAndSubviews.Contains(c.SecondItem);
                return predicate;
            }).ToList();
        }
    }
    

    【讨论】:

      【解决方案4】:

      折叠 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
      

      【讨论】:

      • 这似乎是一个非常简单、优雅的方法。我很好奇如果它确实有效,为什么它没有被投票更高?
      • 嗯...好问题。一般来说,自动布局主题并没有很好地涵盖在 SO 上。我也认为很多人只是将一个出口连接到约束并重击高度。子分类也有点烦人。
      • 这种方法的问题是它没有考虑到您通常在视图周围的边距。
      • 边距如H:|-(8)-[view1]-(12)-[hidden1]-(8)-|?是的......你是对的,但是当你有框架时,这是同样的问题。您仍然必须确保处理视图之间的间距,但这是一个单独的问题!
      • 这是一个非常简洁的解决方案,它也适用于 Cocoa,只需将 UIViewNoIntrinsicMetric 切换到 NSViewNoInstrinsicMetricupdateConstraintsIfNeededupdateConstraintsForSubtreeIfNeededlayoutIfNeededlayoutSubtreeIfNeeded 就可以了一种魅力。谢谢!
      【解决方案5】:

      这个类别让折叠自动布局约束视图变得非常简单:

      https://github.com/depth42/AutolayoutExtensions

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

      【讨论】:

      • 我正在尝试在我的项目中使用它,但新的公共出口 PWHidingMasterView 没有出现在 Xcode 中以获取我项目的视图。当我打开示例项目时,插座就在那里。我无法弄清楚我的项目和示例项目有什么不同。有什么建议吗?
      【解决方案6】:

      这是我如何以编程方式而不是使用 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 实现的,并根据自己的需要进行了更改。

      【讨论】:

        【解决方案7】:

        我也找到了一个看起来不错的方法来做到这一点。它类似于David's。这是它在代码中的工作方式。我创建了超级视图及其所有子视图,甚至那些可能并不总是显示的子视图。我在超级视图中添加了许多约束,例如V:|-[_btn]。正如您在这些约束的末尾看到的那样,在超级视图的底部没有链接。然后,我为视图的两种状态创建了两个约束数组,对我来说,区别在于“更多选项”显示三角形。然后当根据它的状态单击三角形时,我相应地添加和删除约束和子视图。例如添加 I do:

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

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

        【讨论】:

          【解决方案8】:

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

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

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

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

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

          但是在完成所有这些之后,您现在已经编写了一个自动调整大小的 NSLabel 子类,手动而不是通过可视语言创建了一个约束对象,并且超出了它的意愿弯曲 NSLayoutConstraint。

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

          【讨论】:

          • 没有将常量设置为 0。界面生成器中是否有任何直接选项可用?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-10-14
          • 2023-04-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-12-21
          • 1970-01-01
          相关资源
          最近更新 更多