iOS 8 UITableView分隔符缩进0无效。

685

我有一个应用程序,其中 UITableView 的分隔线内缩设置为自定义值 - 右边是 0,左边也是 0。这在 iOS 7.x 中完美地运行,然而在 iOS 8.0 中,我发现分隔线内缩被设置为右侧的默认值 15。即使在 xib 文件中它被设置为 0,它仍然显示不正确。

我如何去除 UITableViewCell 分隔线边距?


你试过把插图设置为0.001而不是0吗?我记得曾经遇到过类似的问题,边缘插图对于0没有反应,但对于0.001(或类似的小数)有反应。 - freshking
我尝试过了,但它也没有起作用。 - user3570727
4
我建议将分隔符的颜色设置为 [UIColor clearColor],并自己绘制分隔符。这样你会更加灵活。 - freshking
2
我仍然无法相信这仍然不是UITableViewCellSeparatorStyle默认值之一。即使被接受的答案在我看来也是一个非常肮脏的hack。 - Enrico Susatyo
43个回答

1077

iOS 8.0在单元格和表视图上引入了layoutMargins属性。

这个属性在iOS 7.0上不可用,所以在分配之前,您需要确保进行检查!

简单的解决方法是子类化您的单元格并覆盖布局边距属性,如@user3570727所建议的那样。 但是,您将失去任何系统行为(例如从安全区域继承边距),因此我不建议使用以下解决方案:

(ObjectiveC)

-(UIEdgeInsets)layoutMargins { 
     return UIEdgeInsetsZero // override any margins inc. safe area
}

(Swift 4.2):

override var layoutMargins: UIEdgeInsets { get { return .zero } set { } }

如果您不想覆盖该属性,或需要有条件地设置它,请继续阅读。


除了 layoutMargins 属性之外,苹果还为您的单元格添加了一个 属性,可以防止其继承您的表视图边距设置。当设置此属性时,您的单元格允许独立配置自己的边距而不受表视图的限制。将其视为覆盖操作。

该属性称为 preservesSuperviewLayoutMargins,设置为 NO 将允许单元格的 layoutMargin 设置覆盖 TableView 上设置的任何 layoutMargin。它既节省时间(您不必修改 Table View 的设置),也更简洁。请参考 Mike Abdullah 的答案以获取详细说明。

注意:以下是 Mike Abdullah 答案中所述的 单元格级边距设置的干净实现。将单元格的 preservesSuperviewLayoutMargins=NO 设置为确保您的表视图不会覆盖单元格设置。如果您实际上希望整个表视图具有一致的边距,请相应地调整代码。

设置单元格边距:

-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Remove seperator inset
    if ([cell respondsToSelector:@selector(setSeparatorInset:)]) {
           [cell setSeparatorInset:UIEdgeInsetsZero];
    }

    // Prevent the cell from inheriting the Table View's margin settings
    if ([cell respondsToSelector:@selector(setPreservesSuperviewLayoutMargins:)]) {
        [cell setPreservesSuperviewLayoutMargins:NO];
    }

    // Explictly set your cell's layout margins
    if ([cell respondsToSelector:@selector(setLayoutMargins:)]) {
        [cell setLayoutMargins:UIEdgeInsetsZero];
    }
}

Swift 4:

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    // Remove seperator inset
    if cell.responds(to: #selector(setter: UITableViewCell.separatorInset)) {
        cell.separatorInset = .zero
    }
    // Prevent the cell from inheriting the Table View's margin settings
    if cell.responds(to: #selector(setter: UITableViewCell.preservesSuperviewLayoutMargins)) {
        cell.preservesSuperviewLayoutMargins = false
    }
    // Explictly set your cell's layout margins
    if cell.responds(to: #selector(setter: UITableViewCell.layoutMargins)) {
        cell.layoutMargins = .zero
    }
}

将单元格上的preservesSuperviewLayoutMargins属性设置为NO,应该可以防止表格视图覆盖单元格边距。在某些情况下,它似乎不能正常工作。

如果所有尝试都失败了,您可以强制更改表格视图的边距:

-(void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    // Force your tableview margins (this may be a bad idea)
    if ([self.tableView respondsToSelector:@selector(setSeparatorInset:)]) {
        [self.tableView setSeparatorInset:UIEdgeInsetsZero];
    }

    if ([self.tableView respondsToSelector:@selector(setLayoutMargins:)]) {
        [self.tableView setLayoutMargins:UIEdgeInsetsZero];
    }
} 

Swift 4:

func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    // Force your tableview margins (this may be a bad idea)
    if tableView.responds(to: #selector(setter: UITableView.separatorInset)) {
        tableView.separatorInset = .zero
    }
    if tableView.responds(to: #selector(setter: UITableView.layoutMargins)) {
        tableView.layoutMargins = .zero
    }
}

...然后你就完成了!这应该在iOS 7和8上起作用。


编辑: Mohamed Saleh提醒我注意可能发生的iOS 9变化。如果您想自定义插图或边距,则需要将表视图的cellLayoutMarginsFollowReadableWidth设置为NO。您的情况可能会有所不同,文档记录得不是很好。

此属性仅存在于iOS 9中,请在设置之前进行检查。

if([myTableView respondsToSelector:@selector(setCellLayoutMarginsFollowReadableWidth:)])
{
    myTableView.cellLayoutMarginsFollowReadableWidth = NO;
} 

Swift 4:

if myTableView.responds(to: #selector(setter: self.cellLayoutMarginsFollowReadableWidth)) {
    myTableView.cellLayoutMarginsFollowReadableWidth = false
}

(以上代码来自iOS 8 UITableView分隔符缩进0无效)

编辑:这是一个纯Interface Builder的方法:

TableViewAttributesInspector TableViewCellSizeInspector

注意:iOS 11会改变和简化很多此行为,将会有一个更新...


2
对于在UIPopoverController中的表格视图,我必须像上面展示的那样同时执行这两个操作,而不仅仅是在willDisplayCell方法中执行。 - Zhang
44
我发现尽管我已经写了以上所有的代码,但仍需按照@bffmike的建议添加cell.preservesSuperviewLayoutMargins = NO,否则在滚动一段时间后,插图仍会出现。(什么鬼?) - inorganik
如果我们想要仅为分组的tableView中的一种类型的单元格设置边距,该怎么办? - SAHM
2
在我的数据源方法中,加入cell.preservesSuperviewLayoutMargins后,这对我起作用了:- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath - mts
1
你是否有这个答案在iOS 11上的更新?它已经不起作用了。 - SAHM
显示剩余13条评论

260

哎呀!在你的Cell子类中尝试做这个操作后:

- (UIEdgeInsets)layoutMargins
{
    return UIEdgeInsetsZero;
}

或者将 cell.layoutMargins = UIEdgeInsetsZero; 进行设置也对我起到了作用。


13
这个答案中的第一个解决方案对我有用,第二个解决方案没有用。 - spybart
5
不要只使用cell.layoutMargins = UIEdgeInsetsZero;,因为它会在iOS 7.x上崩溃。最好在使用之前先检查选择器是否存在。请参阅下面的解决方案。 - Ricky
3
他没有设置cell.layoutMargins... 当你已经有自定义单元格时,这个解决方案非常有效。 - Sjoerd Perfors
11
在iOS 7中,这将正常工作,因为该方法不会被调用!但是您还需要设置表视图的layoutMargins,否则它将无法工作。 - chris stamper
2
覆盖访问器对我起作用了,但所有其他解决方案都没有。即使在IB中配置separatorInset也不起作用。表视图背景颜色也存在问题。这真的是令人头痛的事情。 - glasz
显示剩余7条评论

180

在盲目尝试解决问题之前,让我们花点时间了解问题。

在调试器中快速查看会告诉您,分隔线是UITableViewCell的子视图。似乎该单元格本身对这些行的布局负有相当大的责任。

iOS 8引入了“布局边距”的概念。默认情况下,视图的布局边距在所有侧面上为8pt,并且它们从祖先视图中“继承”。

据我们所知,在布置其分隔线时,UITableViewCell选择尊重左侧布局边距,并使用它来约束左插入。

把它们放在一起,要实现真正的零插入所需的是:

  • 将左布局边距设置为0
  • 停止任何继承的边距覆盖它

这么说吧,这是一个非常简单的任务:

cell.layoutMargins = UIEdgeInsetsZero;
cell.preservesSuperviewLayoutMargins = NO;

需要注意以下几点:

  • 这段代码只需要在单元格中运行一次(毕竟你只是在配置单元格的属性),并且选择何时执行它没有什么特别之处。按照你认为最干净的方式去做。
  • 遗憾的是,这两个属性都无法在 Interface Builder 中配置,但如果需要,可以为 preservesSuperviewLayoutMargins 指定一个用户定义的运行时属性。
  • 显然,如果您的应用还针对早期的操作系统版本,您需要在 iOS 8 及以上运行时才能执行上述代码。
  • 与其设置 preservesSuperviewLayoutMargins,你可以配置祖先视图(例如表格)也具有 0 的左边距,但这似乎本质上更容易出错,因为你无法控制整个层次结构。
  • 将只左边距设为 0,而让其他的保持不变可能会更加干净。
  • 如果你想在普通样式的表格底部的 "额外" 分隔线上拥有 0 插入,我猜这也需要在表格级别指定相同的设置(没有尝试过!)。

3
谢谢!这确实是更简明的答案。比起重写两个方法并使用蛮力解决问题,这要好得多。 - LunaCodeGirl
16
在iOS 8中,对于我而言,如果没有[self.tableView setSeparatorInset:UIEdgeInsetsMake(0, 0, 0, 0)];这一行代码,它将无法正常工作。 - davetw12
1
你有我的支持,感谢清晰的解释。我只想提一下,TableView本身也有layoutMargins,在某些情况下可能需要设置,除了separatorInset。 - chris stamper
3
不,@MikeAbdullah,这不是巫术形式。为了避免iOS 8中UITableViewCell的默认“separatorInset”,必须在UITableViewUITableViewCell上都将UIEdgeInsets设置为零。如果你不做这两个操作,左侧缩进仍然大于零。我已经多次确认过这一点。 - davetw12
默认情况下,preservesSuperviewLayoutMarginsNO - esh
显示剩余8条评论

60

我认为这是我在这里所问的同样问题:Remove SeparatorInset on iOS 8 UITableView for XCode 6 iPhone Simulator

iOS 8中,所有继承自UIView的对象都有一个新属性。因此,在iOS 7.x中设置SeparatorInset的解决方案将无法去除你在iOS 8上看到的UITableView上的空白部分。

新属性称为"layoutMargins"。

@property(nonatomic) UIEdgeInsets layoutMargins
Description   The default spacing to use when laying out content in the view.
Availability  iOS (8.0 and later)
Declared In   UIView.h
Reference UIView Class Reference

iOS 8 UITableView setSeparatorInset:UIEdgeInsetsZero setLayoutMargins:UIEdgeInsetsZero

解决方案:

-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{

    if ([tableView respondsToSelector:@selector(setSeparatorInset:)]) {
        [tableView setSeparatorInset:UIEdgeInsetsZero];
    }

    if ([tableView respondsToSelector:@selector(setLayoutMargins:)]) {
        [tableView setLayoutMargins:UIEdgeInsetsZero];
    }

   if ([cell respondsToSelector:@selector(setLayoutMargins:)]) {
        [cell setLayoutMargins:UIEdgeInsetsZero];
   }
}

如果您在未检查layoutMargins是否存在的情况下设置cell.layoutMargins = UIEdgeInsetsZero; ,则该应用程序将在iOS 7.x上崩溃。因此,在setLayoutMargins: UIEdgeInsetsZero之前最好先检查layoutMargins是否存在。


如果部署目标设置为iOS 7,该方法如何使用? - Petar
我将tableView的代码放在viewDidLoad中,它们正常工作。我认为在willDisplayCell()中每次调用它们是不必要的。 - Abdullah Umer

47
您可以在应用启动之前(加载UI之前)使用UIAppearance一次性设置为默认全局设置:
// iOS 7:
[[UITableView appearance] setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine];
[[UITableView appearance] setSeparatorInset:UIEdgeInsetsZero];

[[UITableViewCell appearance] setSeparatorInset:UIEdgeInsetsZero];

// iOS 8:
if ([UITableView instancesRespondToSelector:@selector(setLayoutMargins:)]) {

    [[UITableView appearance] setLayoutMargins:UIEdgeInsetsZero];
    [[UITableViewCell appearance] setLayoutMargins:UIEdgeInsetsZero];
    [[UITableViewCell appearance] setPreservesSuperviewLayoutMargins:NO];

}

这样,您可以保持您的UIViewController代码的清洁,并且如果需要,您总是可以重写它。


42

iOS引入了layoutMargins属性,适用于单元格和表视图。

这个属性在iOS 7.0中不可用,所以在分配之前需要确保检查!

但是,苹果为您的单元格添加了一个名为“preservesSuperviewLayoutMargins”的属性,可以防止它继承您的表视图边距设置。 这样,您的单元格可以独立配置其自己的边距而不受表视图的影响。 将其视为覆盖。

此属性称为 preservesSuperviewLayoutMargins ,将其设置为NO可以允许您使用自己单元格的layouMargin设置覆盖表视图的layouMargin设置。 它既可以节省时间(您不必修改表视图的设置),而且更加简洁。 有关详细说明,请参阅Mike Abdullah的答案。

注意:这是正确的实现方式,如Mike Abdullah的答案所述;将单元格的preservesSuperviewLayoutMargins设置为NO将确保您的表视图不会覆盖单元格设置。

第一步-设置单元格边距:

/*
    Tells the delegate that the table view is about to draw a cell for a particular row.
*/
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell,
    forRowAtIndexPath indexPath: NSIndexPath)
{
    // Remove separator inset
    if cell.respondsToSelector("setSeparatorInset:") {
        cell.separatorInset = UIEdgeInsetsZero
    }

    // Prevent the cell from inheriting the Table View's margin settings
    if cell.respondsToSelector("setPreservesSuperviewLayoutMargins:") {
        cell.preservesSuperviewLayoutMargins = false
    }

    // Explictly set your cell's layout margins
    if cell.respondsToSelector("setLayoutMargins:") {
        cell.layoutMargins = UIEdgeInsetsZero
    }
}

将单元格的preservesSuperviewLayoutMargins属性设置为NO 应该可以防止您的表视图覆盖单元格边距。在某些情况下,它似乎无法正常工作。

第二步——只有在所有其他方法都失败的情况下,您才可以强制更改您的表视图边距:

/*
    Called to notify the view controller that its view has just laid out its subviews.
*/
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    // Force your tableview margins (this may be a bad idea)
    if self.tableView.respondsToSelector("setSeparatorInset:") {
        self.tableView.separatorInset = UIEdgeInsetsZero
    }

    if self.tableView.respondsToSelector("setLayoutMargins:") {
        self.tableView.layoutMargins = UIEdgeInsetsZero
    }
}

...然后你就完成了!这适用于iOS 8以及iOS 7。

注意:在我的情况下,我使用iOS 8.1和7.1进行测试,我只需要使用这个说明的第一步骤。

只有在渲染的单元格下面没有填充的单元格时,才需要执行第二步骤,即如果表格比表格模型中的行数大。不执行第二步会导致不同的分隔符偏移量。


1
您不需要在willDisplayCell方法中检查选择器,因为该方法本身已经是iOS 8+的,并且不会从旧版本调用。 - Rivera

39

在Swift中,这会稍微麻烦一些,因为layoutMargins是一个属性,所以您必须重写getter setter。

在Swift中,这有点令人讨厌,因为layoutMargins是一个属性,所以您必须重写其getter setter。
override var layoutMargins: UIEdgeInsets {
  get { return UIEdgeInsetsZero }
  set(newVal) {}
}

这将有效地使layoutMargins只读,在我的情况下这是可以的。


只有这个解决方案对我有效。而且我不需要在方法中添加额外的代码行。我使用了自定义单元格。 - Ramis
2
对于静态单元格,除了覆盖layoutMargins之外,您仍然需要在storyboard中将单元格的分隔符样式更改为“自定义插图”,并将左侧值设置为“0”。 - hufeng03

24

对于iOS 9,您需要添加:

if([myTableView respondsToSelector:@selector(setCellLayoutMarginsFollowReadableWidth:)])
{
    myTableView.cellLayoutMarginsFollowReadableWidth = NO;
} 

更多细节请参考问题


12
为每个版本添加一些代码,以便初始外观保持不变,这很有趣。 - Christian

21

Swift 2.0 扩展

我想分享一个我制作的扩展,可以去除tableview单元格分隔符的边距。

extension UITableViewCell {
    func removeMargins() {

        if self.respondsToSelector("setSeparatorInset:") {
            self.separatorInset = UIEdgeInsetsZero
        }

        if self.respondsToSelector("setPreservesSuperviewLayoutMargins:") {
            self.preservesSuperviewLayoutMargins = false
        }

        if self.respondsToSelector("setLayoutMargins:") {
            self.layoutMargins = UIEdgeInsetsZero
        }
    }
}

用于上下文:

    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CustomCell

    cell.removeMargins()
    return cell

16

迅速:

override func viewDidLoad() {
    super.viewDidLoad()

    if self.tableView.respondsToSelector("setSeparatorInset:") {
        self.tableView.separatorInset = UIEdgeInsetsZero
    }
    if self.tableView.respondsToSelector("setLayoutMargins:") {
        self.tableView.layoutMargins = UIEdgeInsetsZero
    }

    self.tableView.layoutIfNeeded()            // <--- this do the magic
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
     ...

    if cell.respondsToSelector("setSeparatorInset:") {
        cell.separatorInset = UIEdgeInsetsZero
    }
    if cell.respondsToSelector("setLayoutMargins:") {
        cell.layoutMargins = UIEdgeInsetsZero
    }

    return cell
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接