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

瞄准iOS 8.1

我正在使用AutoLayout在TableCell中布置许多标签。 其中一些标签是可选的,一些可以包装他们的文本。 它们分成两个“列”,这些列仅仅是TableCell的ContentView中的两个UIViews。 我的约束以编程方式应用。

第二次更新

没有下面的SwiftArchitect的答案 ,我不会解决这个问题,并已经接受了他的答案。 但是,因为我的代码都在代码中,所以在自定义表格单元格中,我还在下面添加了一个单独的答案

UPDATE

在试图阻止标签伸展到比他们需要的更大的尺寸时,我之前将SetContentHuggingPrioritySetContentCompressionResistancePriority设置为1000,因为我SetContentCompressionResistancePriority这是相当于说“ 我希望标签拥抱其内容到其确切的高度我不希望它被垂直压缩 “这个请求显然没有被AutoLayout所遵守,你可以在下面的红色和粉红色的例子中看到。

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

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

  1. 为什么删除拥抱和压缩优先级解决了我的问题?
  2. 我怎样才能得到红色框中的文本(红框不是以后添加的单元格的一部分)不扩展,而不会回到我以前的问题?

在这里输入图像说明

这里有几个截图,当压缩和拥抱的优先级设置时,它看起来像什么样子。 背景颜色用于debugging

在这里输入图像说明

一般的问题是,包含视图(紫色和紫色)​​正在缩小自己的大小。 正如你可以看到在顶部的“优先级3”正在被切断,因为左列容器不需要更高。

在下面的例子中没有优先级标签,但是EventDate被压扁了。

在这里输入图像说明

下面的答案已经被编写和testing。 它适用于iPhone和iPad,纵向和横向。 最高的一列获胜,另一个只占用所需的空间。 它甚至可以修改为垂直居中对象,如果需要的话。 它解决了垂直切割的标签问题,以及dynamic缩放。

在这里输入图像说明

初步build议

  • 如果可以的话,使用Storyboard 。 您可以使用最先进的GUI以可视方式testing所有约束。
  • 不要鼓励拥抱,压缩,甚至UILabel高度:让每个标签垂直需要的空间,只添加顶部和侧边的锚
  • 使用额外的视图作为容器来定义每列的宽度。 使用multiplier得到,比如说三分之二和三分之一。
  • 让这些视图通过将单个高度约束添加到最低标签的底部(leftColumn.bottom等于lowestLeftLabel.bottom)来计算其理想高度
  • 不要dynamic添加或删除视图; 而是隐藏它们,以便保留相关的约束条件。

解决scheme描述

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

左侧和右侧子视图有一个1/2 multiplier ,我为其添加了一个2像素的constant 。 这两列中的标签左右两侧( leading space到容器, trailing space到容器),具有8像素边距。 这确保没有标签从其列出来。

  1. 考虑到你的UITableViewCell的高度是2个内部列中最大的。 换句话说,containerView.height> = left.height containerView.height> = right.height。
  2. 确保你没有删除任何不可见的标签。 view.hidden不会破坏你的约束,这就是你想要的。
  3. 将每个UILabel左侧和右侧固定到容器,最上面也固定到容器,但是每个后续label.top都应该固定在.bottom的一个.bottom上面。 这样,你的内容将stream动 。 如果需要,您可以添加边距。

(*)最后一个关键是将每列的高度与列上的约束联系起来,以等于该列的最低标签的.bottom 。 在上面的例子中,你可以清楚地看到右边的一个蓝色背景的列比左边的列更短。

在这里输入图像说明

当我看到你想要一个代码解决scheme时,我在不到15分钟的时间里使用了一个Storyboard创build了我的示例。 这不仅仅是一个概念,而是一个实际的实现。 它只有0行代码 ,适用于所有iOS设备。 顺便说一句,它也有0个错误

所有约束列表

注意> =撒在这里和那里。 他们是让你的专栏独立高的关键。

LRNSLayoutContraint实际上是相同的。

在这里输入图像说明

在这里输入图像说明

在这里获取故事板,并在那里获得详细的文章。

我已经接受了SwiftArchitect的答案,但是看到我是基于代码的TableCell方法后,我将添加一个单独的答案。 没有他的帮助,我不可能得到这么多。

我使用MVVMCross和Xamarin iOS,我的TableCellinheritance自MvxTableViewCell

创build子视图

从Cell的ctor创build所有必要的UILabels并通过设置view.TranslatesAutoresizingMaskIntoConstraints = falseclosuresAutoResizingMasks

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

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

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

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

设置布局约束(UpdateConstraints())

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

我做的第一件事是将leftColumnContainer的顶部和左侧leftColumnContainer到TableCell.ContentView。 然后将'rightColumnContainer'的顶部和右侧添加到TableCell.ContentView。 devise要求右列小于左侧,所以这是通过缩放来完成的。 我正在使用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的解决scheme的一个关键部分是将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是否可见。 如果它们对于左侧可见的类似约束,则顶部被限制在上面的标签的底部,从而应用顶部和右侧。 如果它们不可见,则从视图中移除

 this.bodyText.RemoveFromSuperview(); 

如果您想知道这些单元格如何与iOS Cell Reuse配合使用,那么接下来我将介绍这些单元格。

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

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

处理细胞再利用

有了这样一组复杂的约束条件和许多可选的单元格,我不希望每次单元格被重复使用时都不得不重新应用这些约束条件。 为此,我在运行时构build并设置重用标识符。

TableSourceinheritance自MvxTableViewSource。 在重载的GetOrCreateCellFor我检查一个特定的reuseIdentifier(正常使用),如果是这样的话调用DequeueReusableCell然而在这个实例中,我遵循封装在自定义Cell类中的例程知道如何build立一个数据特定的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; } 

和build立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(); } 

首先,我们不应该玩机智的内容 – 拥抱或压缩优先,除非有一种情况下,你需要改变,没有select权。 如果自动布局configuration正确,苹果给出的默认比例为250:750将适合90%的情况。 只有极less数情况下,如果引擎根据已满足的约束条件调整视图的大小,我们应该改变拥抱/压缩优先级。

你原来的问题是你的标签被压扁。 默认情况下,标签会启用系统的内部内容大小,默认情况下,引擎将根据标签内容和文本大小决定标签的宽度和高度。 所以,如果你决定你的标签不要扩大视图,那么你应该从你的标签的右侧设置尾随约束到视图。 一般来说,它将设置为“等于”属性,这将不符合我们的要求,因为我们的标签在内在属性上传递,我们不应该为标签提供标准宽度。 因此,而不是“等于”它应该是“大于或等于”财产的尾随财产。

在你的场景中,你不应该为标签固定高度约束,并且为任何需要的两行标签启用“行数”属性的自动换行function。

你知道你应该总是需要在右侧视图中显示“黄色标签”,“绿色标签”和“紫色标签”,而不pipe左侧视图在“红色标签”中有一行还是两行。

所以修复一个静态的单元格高度。 在右侧视图中,修复“橙色”标签的顶部约束,以及“黄色”标签的底部约束。 所以这个中心的“红”标签会得到一个明确的高度,可以根据需要容纳一两行。 并给右侧视图充分的约束,以满足您的要求。

如果这不是解决您的问题或对我的解决scheme的任何讨论,下面评论。