【问题标题】:Custom UITableview Cell Occasionally Overlapping/Reproducing On Top Of Other Cell自定义 UITableview 单元格偶尔会在其他单元格之上重叠/复制
【发布时间】:2018-02-06 19:40:12
【问题描述】:

我在这里遇到了非常奇怪的问题。

基本上,用户来到一个附有 TableView 的 View。后端调用了一些数据,并在完成块中将该数据提供给 TableView 显示。

在 4 个可重用的 cellForRow 1 中,自定义单元格根据某种逻辑出列,然后调用该单元格的配置方法。

这就是它变得棘手的地方:一些单元格可能需要进一步的异步调用(为它们的自定义显示获取更多数据),因此需要一个 tableView.reloadRows 调用......那么,这两个可以最好地解释发生了什么截图:

在第一个单元格中,“有没有想过你会为克朗代克酒吧做什么?”复制在“演员的自我管理”单元的顶部,作为包含 Twitter 信息 DID 的单元,需要额外的异步调用来获取该数据。

有什么想法吗?我在 begin/endUpdates 块内运行 tableView.reloadRows 调用,但没有骰子。

cellForRow:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let postAtIndexPath = posts[indexPath.row]
    let cell: PostCell!

    if postAtIndexPath.rawURL == "twitter.com" {
        cell = tableView.dequeueReusableCell(withIdentifier: "TwitterCell", for: indexPath) as! TwitterCell
    } else if !postAtIndexPath.rawURL.isEmpty {
        cell = tableView.dequeueReusableCell(withIdentifier: "LinkPreviewCell", for: indexPath) as! LinkPreviewCell
    } else if postAtIndexPath.quotedID > -1 {
        cell = tableView.dequeueReusableCell(withIdentifier: "QuoteCell", for: indexPath) as! QuoteCell
    } else {
        cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as! PostCell
    }

    cell.isUserInteractionEnabled = true

    cell.privacyDelegate = self
    cell.shareDelegate = self
    cell.likeDelegate = self
    cell.linkPreviewDelegate = self
    cell.reloadDelegate = self
    postToIndexPath[postAtIndexPath.id] = indexPath

    if parentView == "MyLikes" || parentView == "Trending" {
        cell.privatePostButton.isHidden = true
        cell.privatePostLabel.isHidden = true
    }

    cell.configureFor(post: postAtIndexPath,
                         liked: likesCache.postIsLiked(postId: postAtIndexPath.id),
                         postIdToAttributions: postIdToAttributions,
                         urlStringToOGData: urlStringToOGData,
                         imageURLStringToData: imageURLStringToData)

    cell.contentView.layoutIfNeeded()

    return cell
}

异步 ​​Twitter 通话:

private func processForTwitter(og: OpenGraph, urlLessString: NSMutableAttributedString, completion:(() -> ())?) {
    let ogURL = og[.url]!
    let array = ogURL.components(separatedBy: "/")
    let client = TWTRAPIClient()
    let tweetID = array[array.count - 1]
    if let tweet = twitterCache.tweet(forID: Int(tweetID)!)  {
        for subView in containerView.subviews {
            subView.removeFromSuperview()
        }

        let tweetView = TWTRTweetView(tweet: tweet as? TWTRTweet, style: .compact)
        containerView.addSubview(tweetView)
        tweetView.translatesAutoresizingMaskIntoConstraints = false
        tweetView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
        tweetView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
        containerView.leftAnchor.constraint(equalTo: tweetView.leftAnchor).isActive = true
        containerView.rightAnchor.constraint(equalTo: tweetView.rightAnchor).isActive = true
        containerView.isHidden = false
        postText.isHidden = false

    } else {
        client.loadTweet(withID: tweetID, completion: { [weak self] (t, error) in
            if let weakSelf = self {
                if let tweet = t {
                    weakSelf.twitterCache.addTweetToCache(tweet: tweet, forID: Int(tweetID)!)
                }
            }

            if let complete = completion {
                complete()
            }
        })`enter code here`
    }

    if urlLessString.string.isEmpty {
        if let ogTitle = og[.title] {
            postText.text = String(htmlEncodedString: ogTitle)
        }
    } else {
        postText.attributedText = urlLessString
    }
}

tableviewcontroller 中的完成块:

func reload(postID: Int) {

    tableView.beginUpdates()
    self.tableView.reloadRows(at: [self.postToIndexPath[postID]!], with: .automatic)
    tableView.endUpdates()

}

注意:这不会 100% 发生,它可能取决于网络。

【问题讨论】:

  • 你可以试试 tableView.reloadData 代替 tableView.reloadRows
  • 一个快速的建议 - 查看您的代码我没有看到任何后台调用,您可以尝试一下。将网络调用置于后台并在主线程上重新加载表格单元格。

标签: ios iphone swift uitableview custom-cell


【解决方案1】:

我想问题在于计算单元格上存在的组件的大小。就像在数据可用之前设置单元格并设置框架和子视图一样。

也许你可以试试,

tableView.rowHeight = UITableViewAutomaticDimension tableView.estimatedRowHeight = yourPreferedHeight

但您需要确保您的自动布局约束是正确的,并且如果所有组件都有高度,则单元格可以确定它的高度。

【讨论】:

  • 是的,我设置了自动尺寸和估计行高,但没有骰子。不过,我会仔细检查我的限制,也许那里有问题。
  • 也许可以尝试在单元格的 contentView 中添加一个单独的视图,并将所有单元格子视图添加到新添加的视图中。并尝试为新视图提供一些预定义的高度约束,并查看单元格是否重叠,然后您可以根据单元格上每个单独视图的高度来安排高度约束。
【解决方案2】:

在异步调用之后,尝试调用而不是tableView.reloadRows

tableView.beginUpdates()
tableView.setNeedsLayout()
tableView.endUpdates()

数据似乎已填充,但单元格的大小并未得到反映。如果你打电话给reloadRows,恐怕它不会重新计算以下单元格的新位置——这意味着如果单元格第 3 行调整到更大的高度,它会变得更大,但后面的单元格不会被进一步推下.

【讨论】:

  • 这....似乎正在工作!我移动了一些我的逻辑(以前 reloadRows 调用使用的是我在第一次异步调用之后加载的缓存),现在......看起来很开心?至少,我还没有观察到它在起作用!谢谢!
  • 我说得太早了 - 它还在发生:-/
  • @NickCoelius 你能把cellForRowAt 的代码和异步下载代码包括进来吗?
  • 用代码编辑。 Q现在变长了,呜呜。这是在尝试你的答案之前的代码,你的答案的版本只涉及将约束代码等移动到 loadTweet 回调中,然后显然添加 setNeedsDisplsy
  • @NickCoelius 你试过打电话给reloadRows AND setNeedsDisplay 吗?
【解决方案3】:

事实证明,estimatedRowHeightreloadRows 周围的行为很奇怪,或者至少我不明白;解决方案,通过一个切向相关的 SO 问题,是在行高可用时缓存行高,如果在heightForRow 中可用,则返回该行高。

我猜它在重新加载时不会重新计算高度或其他东西?真的不明白为什么会这样,但我很高兴它确实有效!

【讨论】:

  • 在我的情况下,禁用估计高度有效 (tableView.estimatedRowHeight=0)。在表格视图上调用 begin/endUpdates 后,我得到了重叠,其中单元格的高度需要稍微改变。
  • @alex-i 您的建议对我来说效果很好,但是当我最初尝试将estimatedRowheight 设为零时,单元格中的所有内容都重叠了。你能帮我解决这个问题吗?
  • @SRI 我只能猜测问题。我建议设置tableView.rowHeight = <some_value>;,同时确保你也实现了-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath委托方法
【解决方案4】:

我假设 processForTwitter 在后台/异步上从 configureFor 中调用。如果是真的,这就是错误。您的 api 调用应该在后台,但是如果您从缓存中获取推文,那么 addSubview 应该在主线程上并在返回单元格之前完成。尝试这样做,确保在主线程上调用 processForTwitter 并将其配置为

   private func processForTwitter(og: OpenGraph, urlLessString: NSMutableAttributedString, completion:(() -> ())?) {
        let ogURL = og[.url]!
        let array = ogURL.components(separatedBy: "/")
        let client = TWTRAPIClient()
        let tweetID = array[array.count - 1]
        if let tweet = twitterCache.tweet(forID: Int(tweetID)!)  {
            for subView in containerView.subviews {
                subView.removeFromSuperview()
            }

            let tweetView = TWTRTweetView(tweet: tweet as? TWTRTweet, style: .compact)
            containerView.addSubview(tweetView)
            tweetView.translatesAutoresizingMaskIntoConstraints = false
            tweetView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
            tweetView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
            containerView.leftAnchor.constraint(equalTo: tweetView.leftAnchor).isActive = true
            containerView.rightAnchor.constraint(equalTo: tweetView.rightAnchor).isActive = true
            containerView.isHidden = false
            postText.isHidden = false

        } else {
    // run only this code in background/async

            client.loadTweet(withID: tweetID, completion: { [weak self] (t, error) in
                if let weakSelf = self {
                    if let tweet = t {
                        weakSelf.twitterCache.addTweetToCache(tweet: tweet, forID: Int(tweetID)!)
                    }
                }
// completion on main

                if let complete = completion {
  DispatchQueue.main.async {
 complete()

    }   
                }
            })
        }

        if urlLessString.string.isEmpty {
            if let ogTitle = og[.title] {
                postText.text = String(htmlEncodedString: ogTitle)
            }
        } else {
            postText.attributedText = urlLessString
        }
    }

现在的顺序是这样的

  1. 第一次加载单元格
  2. 在缓存中找不到推文
  3. processForTwitter 中的异步网络调用和单元格返回时没有正确的数据
  4. 异步调用获取推文。并在主线程上调用完成,导致重新加载
  5. 在主线程上再次调用配置
  6. 在缓存中找到推文,同时保留在主线程上。
  7. 添加您的 TWTRTweetView(您仍在主线程上),这将在您的单元格返回之前完成。

记住这一点,在 cellForIndexPath 中返回单元格之前,应在主线程中添加单元格中将在计算单元格大小方面发挥作用的每个组件

【讨论】:

  • 不确定这是否可行,我很确定我在不同点从主线程调用了所有内容。就目前而言,我实际上就在昨天终于找到了一个解决方案 - 显然,估计行高度和 reloadRows 周围存在奇怪的行为,所以我现在缓存行高并返回如果在 heightForRow 中可用的话......就像一个魅力!
【解决方案5】:

我遇到了类似的问题。之后

    [self.tableView beginUpdates];
    [self configureDataSource]; // initialization of cells by dequeuing 
    [self.tableView insertSections:[[NSIndexSet alloc] initWithIndex:index] withRowAnimation:UITableViewRowAnimationFade];
    [self.tableView endUpdates];

还有在

之后
    [self.tableView beginUpdates];
    [self configureDataSource]; // initialization of cells by dequeuing  
    [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:index] withRowAnimation:UITableViewRowAnimationFade];
    [self.tableView endUpdates];

我的 TableViewController 的两行重叠了。

然后我发现了问题所在。

在 beginUpdate 和 endUpdate 之后是为单元格调用的两个方法:estimatedHeightForRowAtIndexPath 和 heightForRowAtIndexPath。但是没有调用 cellForRowAtIndexPath。

其中一个重叠单元已出列,但我仅在 cellForRowAtIndexPath 方法中将其设置为文本/值。因此,单元格高度计算正确,但内容不正确。

我的解决方法是在单元格出列后也设置文本/值。所以唯一的区别在于我的 configureDataSource 方法:

    [self.tableView beginUpdates];
    [self configureDataSource]; // init of cells by dequeuing and set its texts/values  
    // delete or insert section
    [self.tableView endUpdates];

(其他可能的解决方法是避免重新初始化单元格。但是这种初始化单元格的缓存对于具有大量单元格的 TableView 来说并不好)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-23
    • 1970-01-01
    • 2017-02-28
    • 1970-01-01
    • 2015-08-07
    相关资源
    最近更新 更多