关于 UITableView's Separator Inset 的探究

前言

这是一次关于 UITableView’s Separator Inset 的探究,请结合 Demo:https://github.com/tangqi92/QTSeparatorInsetTableView.git 阅读本文。


需求

我相信,在平日的开发中,UITableView 绝对算得上使用最平凡的 UIKit 控件之一,并且必定会涉及到 UITableView/UITableViewCell 分割线的设置。

下面,我们就来聊一聊,关于 UITableView/UITableViewCell 分割线的相关内容:

如上图所示,按设计师的要求,在 UITableViewCell 的分割线需要距离左右边距各 16pt。

卧槽,这不很简单嘛!

是吗?


边距为 0

对于如此简单的页面,自定义 Cell 并添加分割线的方式虽然可行,但未免有些小题大做了。所以,在这里我直接使用系统自带的 UITableViewCell 来实现。

天真的我,想都没想,直接设置 self.tableView.separatorInset = UIEdgeInsetsMake(0, 16, 0, 16);,然后分别测试了 iOS 8.x、iOS 9.x、iOS 10.x,结果显示效果一致,完美!

但出于验证自己答案和寻找更多实现方式的目的,我决定看看大家都是如何实现的,于是 Google 一番后,找到了:iOS 8 UITableView separator inset 0 not working 。该问题的描述是「为何在 iOS 8 中设置 UITableView 分割线边距为 0 不起作用」,我想这不简单嘛,于是 self.tableView.separatorInset = UIEdgeInsetsZero; ,然后做了测试,显示效果见下表:

系统版本 测试用例 实现效果
iOS 8.x self.tableView.separatorInset = UIEdgeInsetsZero; NO
iOS 9.x self.tableView.separatorInset = UIEdgeInsetsZero; NO
iOS 10.x self.tableView.separatorInset = UIEdgeInsetsZero; YES

WTF(黑人问号脸???),这是什么情况?看来我真是「Too Young, Too Naive」…于是我又做了如下测试:

系统版本 测试用例 实现效果
iOS 8.x self.tableView.separatorInset = UIEdgeInsetsMake(0, 5, 0, 5); NO
iOS 9.x self.tableView.separatorInset = UIEdgeInsetsMake(0, 5, 0, 5); NO
iOS 10.x self.tableView.separatorInset = UIEdgeInsetsMake(0, 5, 0, 5); YES
系统版本 测试用例 实现效果
iOS 8.x self.tableView.separatorInset = UIEdgeInsetsMake(0, 25, 0, 25); YES
iOS 9.x self.tableView.separatorInset = UIEdgeInsetsMake(0, 25, 0, 25); YES
iOS 10.x self.tableView.separatorInset = UIEdgeInsetsMake(0, 25, 0, 25); YES

一定是姿势不对,嗯,一定的!


历史回顾

为了探究其缘由,在经过一番查阅资料后,关于「分割线边距」(Separator Inset),我们可以得到如下总结:

Prior to iOS 7.x

分割线默认边距为 0

iOS 7.x

新增 separatorInset 属性,默认边距为 15,此时直接设置 separatorInset 值为 UIEdgeInsetsZero 即可。

我们再来看下文档中关于 separatorInset 的解释:

iOS 8.x

新增 layoutMargins 属性,如直接设置 separatorInsetUIEdgeInsetsZero 无效。

我们再来看下文档中关于 layoutMargins 的解释:

中间又涉及到 preservesSuperviewLayoutMargins 属性,其解释:

简单来讲就是:layoutMargins 是 View 的 bounds 的边距,默认为 8pt

如果 preservesSuperviewLayoutMargins 属性设置为 YES,那么父控件的 layoutMargins 边距就将会影响其子控件。

iOS 9.x

新增 cellLayoutMarginsFollowReadableWidth 属性,其解释:

1
A Boolean value that indicates whether the cell margins are derived from the width of the readable content guide.

该属性的作用:当设置为 YES 时,在 iPad 上,当 TableView 横屏时,会根据内容留有空白,如下图所示:

iOS 10.x

直接设置 tableview 的 separatorInset 即可。


实现

所以,为了能完美实现「分割线边距可控」的需求,我们需要如下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
// If cell margins are derived from the width of the readableContentGuide.
// NS_AVAILABLE_IOS(9_0),需进行判断
// 设置为 NO,防止在横屏时留白
if ([tableView respondsToSelector:@selector(setCellLayoutMarginsFollowReadableWidth:)]) {
tableView.cellLayoutMarginsFollowReadableWidth = NO;
}

// Prevent the cell from inheriting the Table View's margin settings.
// NS_AVAILABLE_IOS(8_0),需进行判断
// 阻止 Cell 继承来自 TableView 相关的设置(LayoutMargins or SeparatorInset),设置为 NO 后,Cell 可以独立地设置其自身的分割线边距而不依赖于 TableView
if ([cell respondsToSelector:@selector(setPreservesSuperviewLayoutMargins:)]) {
[cell setPreservesSuperviewLayoutMargins:NO];
}

// Remove seperator inset.
// NS_AVAILABLE_IOS(8_0),需进行判断
// 移除 Cell 的 layoutMargins(即设置为 0)
if ([cell respondsToSelector:@selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}

// Explictly set your cell's layout margins.
// NS_AVAILABLE_IOS(7_0),需进行判断
// 根据需求设置相应的边距
if ([cell respondsToSelector:@selector(setSeparatorInset:)]) {
[cell setSeparatorInset:UIEdgeInsetsMake(0, 16, 0, 16)];
}
}

好啦,以上就是由一个简单的需求「设置分割线边距」引发的一系列探究,希望对你有所帮助。


疑问

对于 iOS 10.x 直接设置 self.tableView.separatorInset = UIEdgeInsetsMake(0, xx, 0, xx); 便能实现效果,我到目前仍没有找到明确的答案,如果你正巧看到这,又正巧知道原因,希望能告知我一下,先在此表示感谢:)


参考

  1. iOS 8 UITableView separator inset 0 not working
  2. Hiding Table Separators on a Cell-by-Cell Basis
我知道是不会有人点的,但万一有人想不开呢?