使用AutoLayout在不同高度的两列中进行堆叠

时间:2015-07-27 16:32:19

标签: ios uitableview xamarin.ios autolayout mvvmcross

定位iOS 8.1

我正在使用AutoLayout在TableCell中布置一些标签。其中一些标签是可选的,有些可以包装它们的文本。它们分为两个" Columns",这些列只是TableCell的ContentView中的两个UIView。我的约束以编程方式应用。

第二次更新

如果没有SwiftArchitect's answer,我就不会解决这个问题并接受了他的回答。但是因为我的所有代码都在自定义的tablecell中,所以我还添加了separate answer below

更新

为了阻止标签拉伸到比我们需要更大的尺寸,我之前将SetContentHuggingPrioritySetContentCompressionResistancePriority设置为1000,因为我相信这相当于说&# 34; 我希望Label将其内容拥抱到其确切的高度,我不希望它被垂直压缩" AutoLayout显然没有遵守此请求,您可以在下面的红色和粉红色示例中看到。

this.notesLabel.SetContentHuggingPriority(1000, UILayoutConstraintAxis.Vertical);
this.notesLabel.SetContentCompressionResistancePriority(1000, UILayoutConstraintAxis.Vertical);

我删除了这些优先级的设置,标签不再被压扁,这是我原来的问题。当然,现在某些标签的拉伸超出了它们所需的高度。

  1. 为什么删除拥抱和压缩优先级会修复我的问题 问题?
  2. 如何在不回复我之前的问题的情况下将红色框(红色框不是后面添加的单元格的一部分)中的文本展开?
  3. enter image description here

    以下是关于压缩和拥抱优先级设置时的效果的几个屏幕截图。背景颜色用于调试

    enter image description here

    一般的问题是,包含视图(紫色和红色)将自己调整为两者中较小的一个。正如您在前面的第一个"优先级3"正在削减,因为左列容器不需要更高。

    在下一个示例中,没有Priority标签,但EventDate正在被压缩。

    enter image description here

3 个答案:

答案 0 :(得分:6)

以下答案已经编写并经过测试。它适用于iPhone和iPad。 iPad,肖像&景观。最高的列获胜,而另一列只占用它所需的空间。如果需要,甚至可以将其修改为垂直居中的对象。它解决了垂直裁剪的标签问题以及动态缩放。

enter image description here

初步建议

  • 如果可以,请使用Storyboard。您可以使用最先进的GUI直观地测试所有约束。
  • 修改拥抱,压缩甚至UILabel高度:让每个标签占据垂直所需的空间,只添加顶部和侧面锚点
  • 使用额外视图作为容器来定义每列的宽度。使用multiplier来获得,比如说三分之二和三分之一。
  • 让这些视图通过在最低标签的底部添加单个高度约束来计算其理想高度(leftColumn.bottom Equal minimumLeftLabel.bottom)
  • 不要动态添加或删除视图;相反,隐藏它们以便它们保留相关的约束。

解决方案说明

为简单起见,我创建了2个子视图,每列1个,并将它们并排放置。它们锚定在顶部/左侧和顶部/右侧,计算它们的宽度,并且它们的高度来自它们各自的内容(*)。

左右子视图有1/2 multiplier,我为其添加了constant 2像素的边距。这两列中的标签左右锚定(leading space到容器,trailing space到容器),边距为8像素。这确保没有标签在其色谱柱之外渗出。

  1. 考虑UITableViewCell的高度是2个内部列中最大的。换句话说,containerView.height> = left.height containerView.height> = right.height。
  2. 确保您没有删除任何不可见的标签。 view.hidden不会破坏您的约束,这就是您想要的。
  3. 将每个UILabel左右锚定到容器,最上面的容器也是容器,但是每个后续的label.top应该锚定到它上面的.bottom。这样,您的内容将流动。如果需要,可以添加边距。
  4. (*)最后一个关键是将每列的高度与列上的约束绑定,使其等于该列的最低标签的.bottom。在上面的示例中,您可以清楚地看到右侧列(蓝色背景)比左侧列短。

    enter image description here

    虽然我看到你想要一个代码解决方案,但我在不到15分钟的时间内使用Storyboard创建了我的示例。它不仅仅是一个概念,而是一个实际的实现。它具有 0行代码,适用于所有iOS设备。顺便提一下,它还有 0个错误

    所有约束列表

    注意> =洒在这里和那里。它们是使您的列独立高的关键。

    L R NSLayoutContraint几乎完全相同。

    enter image description here

    enter image description here

    获取故事板here和详细文章there

答案 1 :(得分:3)

我已经接受了SwiftArchitect's answer,但是在看到基于代码的TableCell方法之后,我会添加一个单独的答案。没有他的帮助我 不可能做到这一点。

我正在使用MVVMCross和Xamarin iOS而我的TableCell继承自MvxTableViewCell

创建子视图

从Cell的ctor我创建所有必需的UILabel并通过设置view.TranslatesAutoresizingMaskIntoConstraints = false

关闭AutoResizingMasks

同时我创建了两个UIViews leftColumnContainer和rightColumnContainer。这些TranslatesAutoresizingMaskIntoConstraints再次设置为false。

相关标签会作为子视图添加到leftColumnContainerrightColumnContainer UIViews。然后将这两个容器作为SubView添加到TableCell的ContentView

this.ContentView.AddSubviews(this.leftColumnContainer, this.rightColumnContainer);
this.ContentView.TranslatesAutoresizingMaskIntoConstraints = true;

UILabels是通过MVVMCross DelayBind调用绑定的所有数据

设置布局约束(UpdateConstraints())

TableCell的布局取决于单元格的数据,8个标签中的5个是可选的,8个中的4个需要支持文本的包装

我要做的第一件事是将leftColumnContainer的顶部和左侧固定到TableCell.ContentView。然后是右边的容器'的顶部和右边。到TableCell.ContentView。设计要求右列小于左侧,因此可以使用缩放来完成。我正在使用FluentLayout来应用这些约束

this.ContentView.AddConstraints(
                this.leftColumnContainer.AtTopOf(this.ContentView),
                this.leftColumnContainer.AtLeftOf(this.ContentView, 3.0f),
                this.leftColumnContainer.ToLeftOf(this.rightColumnContainer),
                this.rightColumnContainer.AtTopOf(this.ContentView),
                this.rightColumnContainer.ToRightOf(this.leftColumnContainer),
                this.rightColumnContainer.AtRightOf(this.ContentView),
                this.rightColumnContainer.WithRelativeWidth(this.ContentView, 0.35f));

对ToLeftOf和ToRight的调用正在放置左列的右边缘和右列的左边缘彼此相邻

来自SwiftArchitect的解决方案的一个关键部分是将TableCell的ContentView的高度设置为> =到leftColumnContainerrightColumnContainer的高度。使用FluentLayout如何做到这一点并不是那么明显,所以他们是" longhand"

this.ContentView.AddConstraint(
              NSLayoutConstraint.Create(this.ContentView, NSLayoutAttribute.Height, NSLayoutRelation.GreaterThanOrEqual, this.leftColumnContainer, NSLayoutAttribute.Height, 1.0f, 5.0f));

            this.ContentView.AddConstraint(
              NSLayoutConstraint.Create(this.ContentView, NSLayoutAttribute.Height, NSLayoutRelation.GreaterThanOrEqual, this.rightColumnContainer, NSLayoutAttribute.Height, 1.0f, 5.0f));

然后我将每列中第一个标签的顶部,左侧和右侧约束到列容器。以下是左栏第一个标签的示例

this.leftColumnContainer.AddConstraints(
                this.categoryLabel.AtTopOf(this.leftColumnContainer, CellPadding),
                this.categoryLabel.AtRightOf(this.leftColumnContainer, CellPadding),
                this.categoryLabel.AtLeftOf(this.leftColumnContainer, CellPadding));

对于每个可选标签,我首先检查MVVMCross DataContext以查看它们是否可见。如果它们是可见的,则应用左,上和右的类似约束,其中Top被约束到上面标签的底部。如果它们不可见,则会从视图中删除

this.bodyText.RemoveFromSuperview();

如果您想知道这些单元格如何与iOS Cell Reuse一起使用,我将介绍下一步。

如果标签将成为列中的最后一个标签(这取决于数据),我将从SwiftArcthiect的答案中应用其他关键字

  

让[列]通过添加单个来计算它们的理想高度   高度约束到最低标签的底部(leftColumn.bottom   等于minimumLeftLabel.bottom)

处理细胞重复使用

使用这样一组复杂的约束和许多可选单元格,我不希望每次使用可能不同的可选标签重复使用单元格时重新应用约束。为此,我在运行时构建并设置重用标识符。

TableSource继承自MvxTableViewSource。在重写的GetOrCreateCellFor中,我检查特定的reuseIdentifier(正常使用),如果是的话 调用DequeueReusableCell但是在这个实例中我遵循一个封装在自定义Cell类中的例程,该类知道如何构建一个特定于数据的id

protected override UITableViewCell GetOrCreateCellFor(UITableView tableView, NSIndexPath indexPath, object item)
        {
            MvxTableViewCell cell;

            if (this.reuseIdentifier != null)
            {
                cell = (MvxTableViewCell)tableView.DequeueReusableCell(this.reuseIdentifier);    
            }
            else
            {
                // No single reuse identifier, defer to the cell for the identifer
                string identifier = this.itemCell.GetCellIdentifier(item);

                if (this.reuseIdentifiers.Contains(identifier) == false)
                {
                    tableView.RegisterClassForCellReuse(this.tableCellType, identifier);
                    this.reuseIdentifiers.Add(identifier);
                }

                cell = (MvxTableViewCell)tableView.DequeueReusableCell(identifier);    
            }

            return cell;
        }

以及构建id的调用

public string GetCellIdentifier(object item)
        {
            StringBuilder cellIdentifier = new StringBuilder();

            var entry = item as EntryItemViewModelBase;

            cellIdentifier.AppendFormat("notes{0}", entry.Notes.HasValue() ? "yes" : "no");
            cellIdentifier.AppendFormat("_body{0}", !entry.Body.Any() ? "no" : "yes");
            cellIdentifier.AppendFormat("_priority{0}", entry.Priority.HasValue() ? "yes" : "no");
            cellIdentifier.AppendFormat("_prop1{0}", entry.Prop1.HasValue() ? "yes" : "no");
            cellIdentifier.AppendFormat("_prop2{0}", entry.Prop2.HasValue() ? "yes" : "no");
            cellIdentifier.AppendFormat("_warningq{0}", !entry.IsWarningQualifier ? "no" : "yes");
            cellIdentifier.Append("_MEIC");

            return cellIdentifier.ToString();
        }

答案 2 :(得分:0)

首先,我们不应该使用机智内容 - 拥抱或压缩优先级,除非您需要在没有选项的情况下进行更改。如果正确配置了自动布局,则apple给出的默认250:750比例将适合场景的90%。 仅在极少数情况下,引擎根据已满足的约束调整视图大小,我们应该更改拥抱/压缩优先级。

您的原始问题是您的标签被压扁了。 默认情况下,标签启用系统的固有内容大小,默认情况下,引擎将根据标签内容和文本大小决定标签的宽度和高度。 因此,如果您决定不扩展视图的标签,则应将标签右侧的尾随约束设置为视图。一般情况下,它将被设置为“等于”属性,这将不符合我们的要求,因为我们的标签在内在属性上传递,我们不应该为标签提供标准宽度。因此,不是'等于',它应该是'大于或等于'属性到尾随属性。

在您的方案中,您不应该修复标签的高度约束,并且对于任何所需的两行标签,在“with number of line”属性中启用自动换行功能。

您知道您应该始终需要在右侧视图中显示“黄色标签”,“绿色标签”和“紫色标签”,而不管左侧视图在“红色标签”中是否有一行或两行。

因此,修复单元格的静态高度。 在右侧视图中, 修复“橙色”标签的顶部约束,以及“黄色”标签的底部约束。因此,中心“红色”标签将获得明确的高度,根据要求可以容纳一到两条线。并对右侧视图给予足够的约束以满足您的要求。

如果这不能解决您的问题或对我的解决方案进行任何讨论,请在下面发表评论。