自定义UITableview单元偶尔重叠/再现在其他单元格的顶部

时间:2018-02-06 19:40:13

标签: ios iphone swift uitableview custom-cell

我在这里真的很奇怪。

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

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

这里变得棘手:一些单元格可能需要进一步的异步调用(为自定义显示获取更多数据),因此需要一个tableView.reloadRows调用...然后,最好的解释是这两个截图:

Wrong Right

在第一个单元格中,那个“曾经想知道你为克朗代克酒吧做些什么吗?”复制在“演员的自我管理”单元格之上,作为包含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
}
asynch推特致电:

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%的时间内发生,大概是网络依赖的。

5 个答案:

答案 0 :(得分:1)

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

也许你可以试试,

tableView.rowHeight = UITableViewAutomaticDimension tableView.estimatedRowHeight = yourPrefferedHeight

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

答案 1 :(得分:1)

原来estimatedRowHeightreloadRows的行为很奇怪,或者至少我不明白。通过切向相关的SO问题,解决方案是在行高可用时缓存它们,并在heightForRow中返回该行高度。

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

答案 2 :(得分:0)

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

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

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

答案 3 :(得分:0)

我假设在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. 在主线程上再次调用configure for
  6. 在主线程中保留在缓存中的推文。
  7. 添加您的TWTRTweetView(您仍然在主线程上),这将在您的单元格返回之前完成。
  8. 记住这一点,在CellForIndexPath返回单元格之前,应该在主线程上添加单元格中用于计算单元格大小的每个组件

答案 4 :(得分:0)

我遇到了类似的问题。

之后
    [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];

(其他可能的解决方法是避免对单元格进行重新初始化。但是,这种初始化单元格的缓存对于具有大量单元格的TableViews不利)