UITableView布局弄乱推送segue和返回.(iOS 8,Xcode beta 5,Swift)

mat*_*son 40 uitableview swift ios8

tldr; 自动约束似乎在push segue上中断并返回查看自定义单元格

编辑:我提供了一个github示例项目,显示发生的错误 https://github.com/Matthew-Kempson/TableViewExample.git

我正在创建一个应用程序,它需要自定义UITableCell的标题标签,以允许根据帖子标题的长度变化的行.单元格正确加载到视图中但是如果我按下一个单元格以将推送信号加载到包含WKWebView的视图中,您可以看到,如屏幕截图所示,单元格会立即移动到不正确的位置.通过UINavigationController的后退按钮加载视图时也会看到这一点.

在这个特殊的例子中,我按下了最后一个单元格,标题是"我在巴黎拍了两张照片",所有内容都正确加载.然后如下一个屏幕截图所示,在加载第二个视图的背景中,单元格都会因未知原因向上移动.然后,当我加载视图时,您可以看到屏幕稍微向上移动,我实际上无法滚动任何低于显示的值.这看起来是随机的,就像其他测试一样,当视图加载回来时底部单元格下方的空白区域不会消失.

我还包括一张包含细胞所具有的约束的图片.

图片(我需要更多的声誉才能在这个问题中提供图像,因此他们就在这张影片中):http://imgur.com/a/gY87E

我的代码:

自定义单元格中的方法,允许单元格在旋转时正确调整视图大小:

override func layoutSubviews() {
    super.layoutSubviews()

    self.contentView.layoutIfNeeded()

    // Update the label constaints
    self.titleLabel.preferredMaxLayoutWidth = self.titleLabel.frame.width
    self.detailsLabel.preferredMaxLayoutWidth = self.detailsLabel.frame.width
}
Run Code Online (Sandbox Code Playgroud)

tableview中的代码

override func viewDidLoad() {
    super.viewDidLoad()

    // Create and register the custom cell
    self.tableView.estimatedRowHeight = 56
    self.tableView.rowHeight = UITableViewAutomaticDimension
}
Run Code Online (Sandbox Code Playgroud)

用于创建单元格的代码

    override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
    if let cell = tableView.dequeueReusableCellWithIdentifier("LinkCell", forIndexPath: indexPath) as? LinkTableViewCell {

        // Retrieve the post and set details
        let link: Link = self.linksArray.objectAtIndex(indexPath.row) as Link

        cell.titleLabel.text = link.title
        cell.scoreLabel.text = "\(link.score)"
        cell.detailsLabel.text = link.stringCreatedTimeIntervalSinceNow() + " ago by " + link.author + " to /r/" + link.subreddit

        return cell
    }

    return nil
}
Run Code Online (Sandbox Code Playgroud)

如果您需要更多代码或信息,请询问,我将提供必要的信息

谢谢你的帮助!

Ste*_*her 36

这个错误是由没有tableView:estimatedHeightForRowAtIndexPath:方法引起的.它是UITableViewDelegate协议的可选部分.

这不是它应该如何工作.Apple的文档说:

在加载表视图时,提供行的高度估计可以改善用户体验.如果表包含可变高度行,则计算所有高度可能会很昂贵,因此会导致更长的加载时间.使用估计允许您将几何计算的一些成本从加载时间推迟到滚动时间.

所以这个方法应该是可选的.你认为如果你跳过它,它会回到准确的tableView:heightForRowAtIndexPath:,对吗?但如果你在iOS 8上跳过它,你会得到这种行为.

似乎正在发生什么?我没有内部知识,但看起来如果你没有实现这个方法,UITableView会将其视为估计的行高0.它会在一定程度上弥补这一点(并且至少在某些情况下会在日志中进行投诉),但您仍会看到不正确的大小.这显然是UITableView中的一个错误.您在Apple的一些应用程序中看到了这个错误,包括基本设置等内容.

那么你如何解决它?提供方法!实施tableView: estimatedHeightForRowAtIndexPath:.如果你没有更好(和快速)的估计,那就回来吧UITableViewAutomaticDimension.这将完全解决这个错误.

像这样:

- (CGFloat)tableView:(UITableView *)tableView
estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return UITableViewAutomaticDimension;
}
Run Code Online (Sandbox Code Playgroud)

有潜在的副作用.你提供一个非常粗略的估计.如果您看到此后果(滚动时可能会移动大小),您可以尝试返回更准确的估计值.(请记住:估计.)

也就是说,这种方法不应该返回一个完美的尺寸,只是一个足够好的尺寸.速度比准确性更重要.虽然我在模拟器中发现了一些滚动故障但实际设备上的任何应用程序都没有,无论是iPhone还是iPad.(其实我试着写一个更准确的估计,但很难兼顾速度和精度,而且只是在我的任何应用程序中没有明显的区别.他们都工作完全以及刚刚回国UITableViewAutomaticDimension,这是简单的,是不足以解决错误.)

所以我建议你不要尝试做更多,除非需要更多.如果不需要,做更多的事情比修复错误更容易造成错误.0在某些情况下,您最终可能会返回,并且取决于您何时返回它可能会导致原始问题重新出现.

凯的上述答案似乎有效的原因是它实现tableView:estimatedHeightForRowAtIndexPath:并因此避免了假设0.0当视图消失时它不会返回.也就是说,Kai的答案过于复杂,缓慢,而且不仅仅是回归UITableViewAutomaticDimension.(但是,再次感谢Kai.如果我没有看到你的答案并且受到鼓舞,将它拉开并弄清楚它为何起作用,我就永远不会想到这一点.)]

请注意,您可能还需要强制布局单元格.您认为iOS会在您返回单元格时自动执行此操作,但并非总是如此.(一旦我调查了一下,我会在你需要做的时候进行编辑.)

如果您需要这样做,请在使用此代码之前return cell;:

[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
Run Code Online (Sandbox Code Playgroud)

  • 感谢答案,但我仍然发现当点击视图推送到新的视图控制器时,表格视图的滚动位置会发生变化.这应该解决这个问题吗?我在我的设备上试过并且使用这种方法滚动似乎仍然非常不可靠. (5认同)

Kai*_*rdt 27

这种行为的问题是当您按下segue时,tableView将调用estimatedHeightForRowAtIndexPath可见单元格并将单元格高度重置为默认值.这是在viewWillDisappear通话结束后发生的.如果你回到TableView,所有可见的单元格都搞砸了......

我用一个解决了这个问题estimatedCellHeightCache.我只是将此代码添加到cellForRowAtIndexPath方法中:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    ...
    // put estimated cell height in cache if needed
    if (![self isEstimatedRowHeightInCache:indexPath]) {
        CGSize cellSize = [cell systemLayoutSizeFittingSize:CGSizeMake(self.view.frame.size.width, 0) withHorizontalFittingPriority:1000.0 verticalFittingPriority:50.0];
        [self putEstimatedCellHeightToCache:indexPath height:cellSize.height];
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

现在你必须实现estimatedHeightForRowAtIndexPath如下:

-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return [self getEstimatedCellHeightFromCache:indexPath defaultHeight:41.5];
}
Run Code Online (Sandbox Code Playgroud)

配置缓存

将此属性添加到.h文件中:

@property NSMutableDictionary *estimatedRowHeightCache;
Run Code Online (Sandbox Code Playgroud)

实现放置/获取/重置缓存的方法:

#pragma mark - estimated height cache methods

// put height to cache
- (void) putEstimatedCellHeightToCache:(NSIndexPath *) indexPath height:(CGFloat) height {
    [self initEstimatedRowHeightCacheIfNeeded];
    [self.estimatedRowHeightCache setValue:[[NSNumber alloc] initWithFloat:height] forKey:[NSString stringWithFormat:@"%d", indexPath.row]];
}

// get height from cache
- (CGFloat) getEstimatedCellHeightFromCache:(NSIndexPath *) indexPath defaultHeight:(CGFloat) defaultHeight {
    [self initEstimatedRowHeightCacheIfNeeded];
    NSNumber *estimatedHeight = [self.estimatedRowHeightCache valueForKey:[NSString stringWithFormat:@"%d", indexPath.row]];
    if (estimatedHeight != nil) {
        //NSLog(@"cached: %f", [estimatedHeight floatValue]);
        return [estimatedHeight floatValue];
    }
    //NSLog(@"not cached: %f", defaultHeight);
    return defaultHeight;
}

// check if height is on cache
- (BOOL) isEstimatedRowHeightInCache:(NSIndexPath *) indexPath {
    if ([self getEstimatedCellHeightFromCache:indexPath defaultHeight:0] > 0) {
        return YES;
    }
    return NO;
}

// init cache
-(void) initEstimatedRowHeightCacheIfNeeded {
    if (self.estimatedRowHeightCache == nil) {
        self.estimatedRowHeightCache = [[NSMutableDictionary alloc] init];
    }
}

// custom [self.tableView reloadData]
-(void) tableViewReloadData {
    // clear cache on reload
    self.estimatedRowHeightCache = [[NSMutableDictionary alloc] init];
    [self.tableView reloadData];
}
Run Code Online (Sandbox Code Playgroud)

  • 这太过分了.重要的是`tableView:estimatedHeightForRowAtIndexPath:`存在; 它返回的并不重要.请参阅下面的答案.(但是感谢发布这个,因为否则我永远不会想到这个!):) (4认同)
  • 嗨@Kai,你的解决方案是唯一一个从segue返回后用跳跃式滚动(向上)解决问题的方法.我们在您的代码中创建了一个cocoapod的通用解决方案https://github.com/IndieGoGo/IGTableViewEstimatedHeightCache. (2认同)

Max*_*eod 8

我有同样的问题.表视图有几个不同的单元类,每个单元类的高度不同.此外,其中一个单元类必须显示其他文本,这意味着进一步的变化.

在大多数情况下,滚动是完美的.但是,问题中描述的同样问题表现出来了.也就是说,选择了一个表格单元格并呈现了另一个视图控制器,在返回原始表格视图时,向上滚动非常不稳定.

第一线调查是考虑为什么要重新加载数据.经过实验,我可以确认,在返回到表视图时,数据会重新加载,尽管不会使用reloadData.

看到我的评论ios 8 tableview在弹出后出现视图时自动重新加载

由于没有停用此行为的机制,下一行方法是调查不稳定的滚动.

我得出的结论是,估计返回的估算estimatedHeightForRowAtIndexPath是一个估计的预先计算.登录以控制估计值,您将看到在表视图首次出现时查询每行的委托方法.这是在任何滚动之前.

我很快发现我的代码中的一些高度估计逻辑严重错误.解决这个问题解决了最糟糕的震动.

为了实现完美的滚动,我对上面的答案略有不同.缓存高度,但使用的值来自用户向下滚动时捕获的实际高度:

    var myRowHeightEstimateCache = [String:CGFloat]()
Run Code Online (Sandbox Code Playgroud)

储藏:

func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
    myRowHeightEstimateCache["\(indexPath.row)"] = CGRectGetHeight(cell.frame)
}
Run Code Online (Sandbox Code Playgroud)

从缓存中使用:

func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
    if let height = myRowHeightEstimateCache["\(indexPath.row)"]
    {
        return height
    } 
    else 
    {
    // Not in cache
    ... try to figure out estimate 
    }
Run Code Online (Sandbox Code Playgroud)

请注意,在上面的方法中,您需要返回一些估计值,因为该方法当然会在之前调用didEndDisplayingCell.

我的猜测是,所有这些都存在某种Apple漏洞.这就是为什么这个问题只出现在退出场景中.

底线是这个解决方案与上面的解决方案非常相似.但是,我避免任何棘手的计算,并利用该UITableViewAutomaticDimension行为来缓存显示的实际行高didEndDisplayingCell.

TLDR:通过缓存实际的行高来解决最有可能出现UIKit缺陷的问题.然后查询缓存作为估算方法中的第一个选项.


Ima*_*tit 7

好吧,直到它工作,你可以删除这两行:

self.tableView.estimatedRowHeight = 45
self.tableView.rowHeight = UITableViewAutomaticDimension
Run Code Online (Sandbox Code Playgroud)

并将此方法添加到viewController:

override func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
    let cell = tableView.dequeueReusableCellWithIdentifier("cell") as TableViewCell
    cell.cellLabel.text = self.tableArray[indexPath.row]

    //Leading space to container margin constraint: 0, Trailling space to container margin constraint: 0
    let width = tableView.frame.size.width - 0
    let size = cell.cellLabel.sizeThatFits(CGSizeMake(width, CGFloat(FLT_MAX)))

    //Top space to container margin constraint: 0, Bottom space to container margin constraint: 0, cell line: 1
    let height = size.height + 1

    return (height <= 45) ? 45 : height
}
Run Code Online (Sandbox Code Playgroud)

它在您的测试项目中没有任何其他更改.