由UITableView分割线说开去

RafaelFauld 8年前
   <p>昨天遇到个老生常谈的问题,UITableView分割线。我们知道,iPhone上默认的分割线左边留有15个单位长度,想要去掉这个长度需要一番设置,网上杂七杂八很多,就不说了,主要分析的是问题原因。</p>    <p>说下历史,iOS7之后,加入了separatorInset属性,separatorInset虽然是UIEdgeInsets类型,但是只有左右生效,也就是说,separatorInset定义了分割线到左边和右边的距离。自从有了这个家伙,分割线就不会延伸到table view的边界。显然,iOS7的时代,只要设置了separatorInset为UIEdgeInsetsZero,那么皆大欢喜。这里可能有人注意到,UITableView和UITableViewCell都有这个属性。区别在于 :</p>    <ol>     <li>UITableView设置的是所有cell的separatorInset,这有点类似rowHeight。我们可以通过table view设置全局cell的inset,也可以单独为某个cell指定inset,后者的优先级大于前者。separatorInset默认为UIEdgeInsetsMake(0.0, 15.0, 0.0, 0.0)</li>     <li>除了全局设置,UITableView额外设置table view底部(无cell部分)的分割线</li>    </ol>    <p>分别设置,看下效果</p>    <pre>  <code class="language-objectivec">self.tableView.separatorColor = [UIColor orangeColor];  self.tableView.separatorInset = UIEdgeInsetsMake(0.0, 50.0, 0.0, 0.0);</code></pre>    <pre>  <code class="language-objectivec">- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section  {      return 10;  }    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  {      UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];        NSLog(@"%@",NSStringFromUIEdgeInsets(cell.separatorInset));        cell.separatorInset = UIEdgeInsetsMake(0.0, 15.0 + 5 * indexPath.row, 0.0, 0.0);        cell.textLabel.text = [NSString stringWithFormat:@"row%ld",indexPath.row];        return cell;  }</code></pre>    <p><img src="https://simg.open-open.com/show/f35395d256b0998069b622a0bce88c1f.jpg"></p>    <p>4612396A-85BF-491A-8C6D-E25D55DA3021.jpeg</p>    <p>首先,table view设置全局cell的inset为UIEdgeInsetsMake(0.0, 50.0, 0.0, 0.0)。其次,根据所在row单独指定inset。</p>    <p>现在我们清楚separatorInset干嘛之后,现在看下layoutMargins。iOS8之后,加入了layoutMargins属性,layoutMargins定义了视图边界与子视图边界之间间距,非控制器根视图默认为UIEdgeInsetsMake(8.0, 8.0, 8.0, 8.0),即上下左右各8个单位长度。控制器根视图默认为UIEdgeInsetsMake(0.0, 16.0, 0.0, 16.0)或者UIEdgeInsetsMake(0.0, 20.0, 0.0, 20.0),取决于当前size class。我们可以改变非根视图的layoutMargins,但是不可以改变根视图的layoutMargins,它由系统设置管理。我们在根视图上添加一个灰色view,看下具体在自动布局中表现</p>    <p>代码</p>    <pre>  <code class="language-objectivec">[NSLayoutConstraint constraintWithItem:lightGrayView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTopMargin multiplier:1.0 constant:0.0].active = YES;  [NSLayoutConstraint constraintWithItem:lightGrayView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeftMargin multiplier:1.0 constant:0.0].active = YES;  [NSLayoutConstraint constraintWithItem:lightGrayView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottomMargin multiplier:1.0 constant:0.0].active = YES;  [NSLayoutConstraint constraintWithItem:lightGrayView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRightMargin multiplier:1.0 constant:0.0].active = YES;</code></pre>    <p>VFL</p>    <pre>  <code class="language-objectivec">[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[lightGrayView]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(lightGrayView)]];  [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[lightGrayView]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(lightGrayView)]];</code></pre>    <p>IB</p>    <p><img src="https://simg.open-open.com/show/fa9d1803ee373c42bfad8fd42ea9fb48.jpg"></p>    <p>A7502C31-F192-40AC-AD77-59FD3A8A9747.jpeg</p>    <p>上面3个都是一个意思,将灰色view的上、下、左、右分别约束到根视图的上margin、下margin、左margin、右margin。以4.7英寸为例</p>    <p>竖屏左右间距为16.0</p>    <p><img src="https://simg.open-open.com/show/eb3dca75c3c914dd6adaef0f071cd783.jpg"></p>    <p>F70411BA-601E-47C1-8D57-4B0EFF19EBE4.jpeg</p>    <p>横屏左右间距为20.0</p>    <p><img src="https://simg.open-open.com/show/34a409c1cb029c7c3642aa564ac371c5.jpg"></p>    <p>DC50D195-6A8F-4C12-B1D3-6D965A3BE662.jpeg</p>    <p>iOS8的时代,不但需要设置separatorInset为UIEdgeInsetsZero,而且需要保证cell的layoutMargins为UIEdgeInsetsZero。</p>    <p>这里有个深坑,我在之前工程仅仅设置了separatorInset和layoutMargins为UIEdgeInsetsZero,确实有效,但是这次同样的设置,依旧留有15个单位长度,到底怎么回事。看了下文档,发现与layoutMargins一起加入iOS8豪华午餐的还有一个属性</p>    <pre>  <code class="language-objectivec">/**   *  preservesSuperviewLayoutMargins 表明当前视图是否保留父视图的margins,设置为YES,如果当前视图的margins小于父视图的margins,那么当前视图使用父视图的margins,默认为NO   */</code></pre>    <p>我们在之前灰色view上添加一个橙色view,设置如下</p>    <pre>  <code class="language-objectivec">lightGrayView.layoutMargins = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0);  lightGrayView.preservesSuperviewLayoutMargins = YES / NO;    UIView *orangeView = [[UIView alloc] init];  orangeView.backgroundColor = [UIColor orangeColor];  orangeView.translatesAutoresizingMaskIntoConstraints = NO;  [lightGrayView addSubview:orangeView];    [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[orangeView]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(orangeView)]];  [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[orangeView]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(orangeView)]];</code></pre>    <p>preservesSuperviewLayoutMargins = YES</p>    <p><img src="https://simg.open-open.com/show/2acf3381f1d78c8c7681748166eb0e0e.png"></p>    <p>IMG_0281.PNG</p>    <p>preservesSuperviewLayoutMargins = NO</p>    <p><img src="https://simg.open-open.com/show/26c4bdf2b1cc122ccc885bc6ea8953fc.png"></p>    <p>IMG_0282.PNG</p>    <p>当preservesSuperviewLayoutMargins为YES时,灰色view的layoutMargins为UIEdgeInsetsMake(10.0, 16.0, 10.0, 16.0)</p>    <p>当preservesSuperviewLayoutMargins为NO时,灰色view的layoutMargins为UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0)</p>    <p>显然,这次留有15个单位长度应该是preservesSuperviewLayoutMargins为YES,保留了父视图的layoutMargins。这里顺带一提,分割线所处的结构</p>    <pre>  <code class="language-objectivec">|--UITableView      |--UITableViewWrapperView 私有类,preservesSuperviewLayoutMargins默认为YES          |--UITableViewCell              |--UITableViewCellSeparatorView 私有类,分割线</code></pre>    <p>那么到底什么原因造成上次cell的preservesSuperviewLayoutMargins为NO,这次cell的preservesSuperviewLayoutMargins为YES。思考了下,问题出在IB还是代码,上次table view是IB加载,这次table view是代码加载,通过控制台打印证实了我的推测。这时,我觉得还不对,分割线的父视图是cell,更准确说,cell如果是IB加载,preservesSuperviewLayoutMargins为NO,cell如果是代码加载,preservesSuperviewLayoutMargins为YES。</p>    <p>这就真的很坑爹了,cell的加载方式不同,造成默认设置不同。</p>    <p>我们新建一个CustomCell类,采用不同注册方式,看下控制台打印的结果</p>    <pre>  <code class="language-objectivec">//from code  [self.tableView registerClass:[CustomCell class] forCellReuseIdentifier:CellIdentifier];  //from nib  [self.tableView registerNib:[UINib nibWithNibName:@"CustomCell" bundle:nil] forCellReuseIdentifier:CellIdentifier];</code></pre>    <pre>  <code class="language-objectivec">- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  {      UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];        NSLog(@"preservesSuperviewLayoutMargins:%d",cell.preservesSuperviewLayoutMargins);        cell.textLabel.text = [NSString stringWithFormat:@"row%ld",indexPath.row];        return cell;  }</code></pre>    <p>总结一下,分割线移除左边15个单位长度的步骤 :</p>    <ol>     <li>separatorInset设置为UIEdgeInsetsZero,至于是table view还是table view cell,随你</li>     <li>table view cell的layoutMargins设置为UIEdgeInsetsZero</li>     <li>确定cell的加载方式,如果from nib,那么步骤1、2足矣。如果from code,那么两种处理方式      <ul>       <li>preservesSuperviewLayoutMargins设置为NO</li>       <li>table view的layoutMargins设置为UIEdgeInsetsZero</li>      </ul> </li>    </ol>    <p>至此,妈妈再也不用担心我的分割线了。</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/1274343055a7</p>    <p> </p>