具有自动布局左边距的 UITableViewCell 在 iPhone 和 iPad 上不同
Posted
技术标签:
【中文标题】具有自动布局左边距的 UITableViewCell 在 iPhone 和 iPad 上不同【英文标题】:UITableViewCell with autolayout left margin different on iPhone and iPad 【发布时间】:2015-02-09 19:42:17 【问题描述】:我正在使用带有静态单元格的分组UITableView
作为选项屏幕/场景。一切都在 Xcode 6.1 / ios 8.1.x / Storyboard 中使用 Autolayout 完成。在表格组中,有混合类型的单元格,有两种类型会导致我出现问题:
-
具有自定义样式的单元格和
具有“右细节”样式的单元格
在单元格 #1 上,我可以为标签和前导容器之间的左边距设置一个约束。据我所知,在单元格 #2 上,我无法在 Interface Builder 中设置任何约束。我在单元格#1 中的标签上设置了左边距,使其与单元格#2 中的标签对齐。在 iPhone 上一切看起来都很好,但是如果我在 iPad 上显示同一个表格,其中表格视图容器的大小是屏幕大小的一半,单元格 #2 会获得更多边距(动态?),而单元格 #1 保持绝对边距我在约束中设置。我还尝试使用“相对于边距”属性更改单元格 #1 中的左边距,但无济于事。
iPhone:
iPad(tableview 宽度 = 1/2 屏幕尺寸)
所以问题是:如何为单元格#1 中的标签设置约束,使其像单元格#2 一样对齐。
这里还有一个 Xcode 6.1 示例项目的链接,演示了该问题。在 iPhone 和 iPad 上运行以查看差异:
https://dl.dropboxusercontent.com/u/5252156/Code/tableViewTest.zip
这个问题可能与Layout static table cell for iPhone and iPad 有关,但对于 iOS 8 也可能有所不同,因为现在一切都应该是自适应的。这就是为什么我还是决定发布这个问题。
【问题讨论】:
【参考方案1】:如何解决它
在与苹果错误报告团队进行了许多示例项目和屏幕截图并剖析that answer 之后,我发现让您的自定义样式单元格在其边距方面表现一致并且就像默认 UITableViewCells 一样的解决方案,您必须执行以下操作(主要基于 Becky's answer,我已经强调了不同之处以及它对我有用的原因):
在 IB 中选择您单元格的内容视图
转到尺寸检查器
在 Layout Margins 部分,选中 Preserve Superview Margins(不要点击加号)
(这是关键)对单元格本身进行相同操作(如果愿意,可以是内容视图的父级)
如下设置约束:Label.Leading = Superview.Leading Margin(常量为 0)
现在您的所有单元格的标签都将与默认单元格一致!这在 Xcode 7 及更高版本中对我有用,它包括我提到的线程中提到的修复。 IB 和模拟器现在应该显示正确对齐的标签。
您也可以通过编程方式完成其中的一些操作,例如在 View Controller 的类中:
cell.preservesSuperviewLayoutMargins = true
cell.contentView.preservesSuperviewLayoutMargins = true
或者你可以通过在启动时调用 UIAppearance 来设置它(我只知道 Swift,抱歉):
UITableViewCell.appearance().preservesSuperviewLayoutMargins = true
UITableViewCell.appearance().contentView.preservesSuperviewLayoutMargins = true
它的工作原理和原理
正如Ethan 好心指出的那样,Apple 自己在 UIView 上的文档对preservesSuperviewLayoutMargins
的描述如下:
当该属性的值为
true
时,布局内容时也会考虑superview的边距。此边距影响视图边缘与其父视图之间的距离小于相应边距的布局。例如,您可能有一个内容视图,其框架与其父视图的边界精确匹配。当任何超级视图的边距位于内容视图及其自身边距所表示的区域内时,UIKit 会调整内容视图的布局以尊重超级视图的边距。调整量是确保内容也在超级视图的边距内所需的最小量。
因此,如果您希望单元格的内容与 TableView 的 边距对齐(如果您愿意,它是曾祖父),您需要拥有内容的两个优势,即内容视图和表格单元格本身,保留自己的superview的边距。
为什么这不是默认行为让我感到惊讶:我觉得大多数不想自定义所有内容的开发人员会默认这种“继承”。
【讨论】:
所以我在 8.1 模拟器上尝试过,确实,边距太窄,这意味着超级视图的布局边距 没有 被保留......但是这是一个错误Apple 已在 8.3 中修复,并以某种方式在 iOS 9 中再次修复。 很棒的帖子!这绝对是正确的做法。 如果您查看 Apple 的 UIView 文档中的 preservesSuperviewLayoutMargins,我认为它基本上所做的是调整内容视图以使其位于其超级视图的边距内。因此,有效地,如果您将内容视图内的标签约束到内容视图的边距,如果此标志为 YES,它还将添加超级视图的边距。换句话说,如果内容视图的边距是 15,而超级视图的边距是 5,那么您将标签约束到的总边距是 20。显然,超级视图的边距可能会因设备而异,这似乎是其根源问题。preservesSuperviewLayoutMargins
在 iOS 10 中默认为 true
UITableViewCell
。
@Manuel 感谢您的评论,我试图弄清楚为什么我的布局在 iOS 11 中是完美的,但当我回到 9 时就坏了。【参考方案2】:
我遇到了和你一样的问题并想出了一个解决方案。
首先,一点背景知识:从 iOS 8 开始,默认表格视图单元格尊重单元格的 layoutMargins
以适应不同的特征(又名屏幕又名设备)。例如,所有 iPhone 上的布局边距(在表单中显示的 iPhone 6 Plus 除外)都是8, 16, 8, 16
。在 iPad 上,他们是 8, 20, 8, 20
。所以现在我们知道有 4 个像素的差异,这很可能是您的自定义表格视图单元格没有考虑到的。
您的表格视图单元子类需要在 layoutMargins 更改时适应左边距约束。
下面是相关代码sn-p:
- (void)layoutMarginsDidChange
[super layoutMarginsDidChange];
self.leftLayoutMarginConstraint.constant = self.layoutMargins.left;
self.rightLayoutMarginConstraint.constant = self.layoutMargins.right;
适应代码中的布局边距可以让您始终为标题标签获得正确的填充。
您还可以查看我的 UITableViewCell 子类之一,它已经尊重 layoutMargins:https://github.com/bhr/BHRExtensions/blob/master/BHRExtensions/Utilities/BHRTitleAndValueTableCell.m
干杯
【讨论】:
感谢您为我指明正确的方向!对答案的两个评论:(1)我没有使用自定义表格视图单元格,而是 Xcode 中的静态单元格,因此我必须首先创建一个子类并将其分配给设置表中的所有单元格。 (2) 您提供的代码 sn-p 在这种情况下只是部分有用,因为 left- 和 rightLayoutMarginConstraint 属性属于您的子类而不属于 UITableViewCell。所以 sn-p 不能按原样工作。如果其他人对该解决方案感兴趣,则必须在 GitHub 上查看您的代码。不过还是谢谢你的指点!【参考方案3】:在阅读了现有答案并没有找到明显的程序化解决方案后,我进行了更多挖掘,现在为其他面临此问题的人提供了一个很好的答案。
首先,没有必要将 preservesSuperviewLayoutMargins
设置为单元格的视图或内容视图,因为 other answers 暗示。虽然默认值为false
,但将其更改为true
并没有我能看到的明显效果。
使这个工作真正起作用的关键是UIView
上的layoutMarginsGuide
属性。使用这个值,我们可以轻松地将任何子视图的leadingAnchor
固定到指南的leadingAnchor
。这是它在代码中的样子(很可能是 IB 在幕后所做的事情,如 Jonas's answer)。
在UITableViewCell
子类中,您可以执行以下操作:
override func updateConstraints()
let margins = contentView.layoutMarginsGuide
let leading = margins.leadingAnchor
subview1.leadingAnchor.constraintEqualToAnchor(leading).active = true
subview2.leadingAnchor.constraintEqualToAnchor(leading).active = true
super.updateConstraints()
Swift 4.1 更新
override func updateConstraints()
let margins = contentView.layoutMarginsGuide
let leading = margins.leadingAnchor
subview1.leadingAnchor.constraint(equalTo: leading).isActive = true
subview2.leadingAnchor.constraint(equalTo: leading).isActive = true
super.updateConstraints()
就是这样!如果您正在为 iOS 9 之前的 iOS 版本进行开发,则需要替换布局锚点并改用 layoutMargins
插入。
注意:如果您希望使用更简洁的语法,我编写了一个库来使锚定更漂亮。它被称为SuperLayout,可在 Cocoapods 上使用。在源文件的顶部,导入SuperLayout
:
import SuperLayout
然后在您的布局块中,使用~~
、≤≤
和≥≥
来固定约束:
override func updateConstraints()
let margins = contentView.layoutMarginsGuide
subview1.leadingAnchor ~~ margins.leadingAnchor
subview2.leadingAnchor ~~ margins.leadingAnchor
super.updateConstraints()
ios 11+:让边距 = contentView.directionalLayoutMargins ... 如果您需要开箱即用地适应 LTR 和 RTL。我想大多数人确实需要它。
【讨论】:
我正在开发的应用程序是为 iOS 8 开发的,所以我没有尝试这个解决方案,但我肯定会!关于您的答案:在 IB 中无法“看到”AFAIK 锚点,但它确实可能是它在阴影中所做的。显然,如果您想以编程方式进行操作,那么您的方法是正确的。但是,看到我的解决方案适用于所有使用 IB 的用户,您应该检查当约束设置为内容视图的边距时,单元格及其内容视图的 both 上的 preservesSuperviewLayoutMargins 是否设置为 true。跨度> 这是对我有用的解决方案。我的目标是 iOS 10+,我在我的项目中使用 Swift 4.1。实际上,将preservesSuperviewLayoutMargins
属性设置为 true 对我不起作用,无论是通过编程方式还是通过界面构建器。我正在使用布局约束。感谢@Dan Loewenherz 省去了很多麻烦!【参考方案4】:
通过执行以下操作,我能够获得具有与标准单元格对齐的自定义样式的单元格:
-
在文档大纲中,选择“内容视图”的单元格
自定义样式。
转到尺寸检查器。
在“布局边距”下拉菜单下,点击“保留 Superview 边距”旁边的小加号。
选择 iPad 尺寸等级,即“常规宽度 x 常规高度”。
选中“保留 Superview 边距”旁边的复选框。
通过更新框架解决任何自动布局警告。
这在 Xcode 7 中对我有用;我希望它也能在 Xcode 6 中工作。
【讨论】:
感谢分享!不幸的是,Xcode 6 中没有这样的选项。 该死!对于那个很抱歉。好吧,至少将来会有一个选项来修复它!【参考方案5】:我在 iPad Air、OS 10.1.1 上进行测试时遇到了这个问题。表头的缩进比他们应该的要远得多,横向方向的情况更糟。但它们在 OS 11 之前的 iPhone 上运行良好。
令人惊讶的解决方案是以下代码行,就在创建表之后(抱歉,我只使用 C#,但很容易计算出 Obj-C 和 Swift 等效项):
myTableView.SeparatorInset = myTableView.SeparatorInset;
然后一切都按原样缩进!
【讨论】:
以上是关于具有自动布局左边距的 UITableViewCell 在 iPhone 和 iPad 上不同的主要内容,如果未能解决你的问题,请参考以下文章
带有自动边距的网格 - Windows Phone 8.1 Silverlight