ChatGPT解决这个技术问题 Extra ChatGPT

具有自动布局左边距的 UITableViewCell 在 iPhone 和 iPad 上不同

我正在将带有静态单元格的分组 UITableView 用于选项屏幕/场景。一切都在 Xcode 6.1 / iOS 8.1.x / Storyboard 中使用 Autolayout 完成。在表格组中,有混合类型的单元格,有两种类型会导致我出现问题:

具有自定义样式的单元格和具有“正确细节”样式的单元格

在单元格 #1 上,我可以为标签和前导容器之间的左边距设置一个约束。据我所知,在单元格 #2 上,我无法在 Interface Builder 中设置任何约束。我在单元格#1 中的标签上设置了左边距,使其与单元格#2 中的标签对齐。在 iPhone 上一切看起来都很好,但是如果我在 iPad 上显示同一个表格,其中表格视图容器的大小是屏幕大小的一半,单元格 #2 会获得更多边距(动态?),而单元格 #1 保持绝对边距我在约束中设置。我还尝试使用“相对于边距”属性更改单元格 #1 中的左边距,但无济于事。

苹果手机:

https://i.stack.imgur.com/FygPX.png

iPad(tableview 宽度 = 1/2 屏幕尺寸)

https://i.stack.imgur.com/u8Q5x.png

所以问题是:如何为单元格#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 也可能有所不同,因为现在一切都应该是自适应的。这就是为什么我决定发布这个问题。


C
Community

如何修复它

在与苹果错误报告团队进行了许多示例项目和屏幕截图并剖析 that answer 之后,我发现让您的自定义样式单元格在其边距方面表现一致并且就像默认 UITableViewCells 一样的解决方案,您必须执行以下操作(主要基于 Becky's answer,我强调了不同之处以及它对我有用的原因):

在 IB 中选择单元格的内容视图 转到大小检查器 在 Layout Margins 部分中,选中 Preserve Superview Margins (不要单击加号)(这是关键)对单元格本身执行相同操作(内容视图的父级,如果你会)设置你的约束如下:Label.Leading = Superview.Leading Margin(常数为0)

现在您的所有单元格的标签都将与默认单元格一致!这在 Xcode 7 及更高版本中对我有用,它包括我提到的线程中提到的修复。 IB 和模拟器现在应该显示正确对齐的标签。

https://i.stack.imgur.com/d5GcCm.png

您也可以以编程方式执行其中的一些操作,例如在 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 时,在布局内容时也会考虑超级视图的边距。此边距影响视图边缘与其父视图之间的距离小于相应边距的布局。例如,您可能有一个内容视图,其框架与其父视图的边界精确匹配。当任何超级视图的边距位于内容视图及其自身边距所表示的区域内时,UIKit 会调整内容视图的布局以尊重超级视图的边距。调整量是确保内容也在超级视图的边距内所需的最小量。

因此,如果您希望单元格的内容与 TableView 的边距对齐(如果您愿意,它是曾祖父),您需要让内容的两个上级内容视图和表格单元格本身保留它们自己的超级视图的边距。

为什么这不是默认行为让我感到惊讶:我觉得大多数不想自定义所有内容的开发人员都会默认这种“继承”。


所以我在 8.1 模拟器上尝试了它,确实,边距太窄了,这意味着超级视图的布局边距没有被保留......然而,这是 Apple 在 8.3 中修复的错误,并以某种方式在 iOS 9 中再次修复。
很棒的帖子!这绝对是正确的做法。
如果您查看 Apple 的 UIView 文档中的 preservesSuperviewLayoutMargins,我认为它基本上所做的是将内容视图调整为位于其超级视图的边距内。因此,有效地,如果您将内容视图内的标签约束到内容视图的边距,如果此标志为“是”,它还将添加超级视图的边距。换句话说,如果内容视图的边距是 15,而超级视图的边距是 5,那么您将标签约束到的总边距是 20。显然,超级视图的边距可能会因设备而异,这似乎是其根源问题。
对于 iOS 10 中的 UITableViewCellpreservesSuperviewLayoutMargins 默认为 true
@Manuel 感谢您的评论,我试图弄清楚为什么我的布局在 iOS 11 中是完美的,但在我回到 9 时就坏了。
b
bhr

我遇到了和你一样的问题并想出了一个解决方案。

首先,一点背景知识:从 iOS 8 开始,默认表格视图单元格尊重单元格的 layoutMargins 以适应不同的特征(又名屏幕又名设备)。例如,所有 iPhone(在表单中显示的 iPhone 6 Plus 除外)上的布局边距都是 {8, 16, 8, 16}。在 iPad 上,它们是 {8, 20, 8, 20}。所以现在我们知道有 4 个像素的差异,这很可能是您的自定义表格视图单元格不尊重。

当 layoutMargins 改变时,您的表格视图单元子类需要适应左边距约束。

这是相关的代码片段:

 - (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) 您提供的代码片段在这种情况下只是部分有用,因为 left- 和 rightLayoutMarginConstraint 属性属于您的子类而不属于 UITableViewCell。所以片段不能按原样工作。如果其他人对该解决方案感兴趣,则必须在 GitHub 上查看您的代码。不过还是谢谢你的指点!
A
Anton Tropashko

在阅读了现有的答案并且没有找到明显的编程解决方案之后,我做了一些更多的挖掘,现在对于其他面临这个问题的人来说有一个很好的答案。

首先,没有必要像 other answers 所暗示的那样将 preservesSuperviewLayoutMargins 设置为单元格的视图或内容视图。虽然默认值为 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()
}

斯威夫特 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+:让 margins = contentView.directionalLayoutMargins ... 以防您需要开箱即用地适应 LTR 和 RTL。我想大多数人确实需要这个。


我正在开发的应用程序是针对 iOS 8 的,所以我没有尝试这个解决方案,但我肯定会!关于您的答案:在 IB 中无法“看到”AFAIK 锚点,但它确实可能是它在阴影中所做的。显然,如果您想以编程方式进行操作,那么您的方法是正确的。但是,看到我的解决方案适用于所有使用 IB 的人,您应该检查当约束设置为内容视图的边距时,单元格及其内容视图上的 preservesSuperviewLayoutMargins 是否设置为 true。
这是对我有用的解决方案。我的目标是 iOS 10+,我在我的项目中使用 Swift 4.1。实际上,将 preservesSuperviewLayoutMargins 属性设置为 true 对我不起作用,无论是通过编程方式还是通过界面构建器。我正在使用布局约束。感谢@Dan Loewenherz 省去了很多麻烦!
B
Becky Hansmeyer

通过执行以下操作,我能够获得具有与标准单元格对齐的自定义样式的单元格:

在文档大纲中,为具有自定义样式的单元格选择“内容视图”。转到尺寸检查器。在“布局边距”下拉菜单下,点击“保留超级视图边距”旁边的小加号。选择 iPad 尺寸等级,即“常规宽度 x 常规高度”。选中“保留 Superview 边距”旁边的复选框。通过更新框架解决任何自动布局警告。

这在 Xcode 7 中对我有用;我希望它也能在 Xcode 6 中工作。


感谢分享!不幸的是,Xcode 6 中没有这样的选项。
该死!对于那个很抱歉。好吧,至少将来会有修复它的选项!
d
diyaddict

我在 iPad Air、OS 10.1.1 上测试时遇到了这个问题。表头的缩进比他们应该的要远得多,横向方向的情况更糟。但它们在 OS 11 之前的 iPhone 上都很好。

令人惊讶的解决方案是以下代码行,就在创建表之后(对不起,我只在 C# 中工作,但很容易计算出 Obj-C 和 Swift 等价物):

myTableView.SeparatorInset = myTableView.SeparatorInset;

然后一切都缩进了它应该的!