UITableView 重新加载部分时的内容插入更改

Posted

技术标签:

【中文标题】UITableView 重新加载部分时的内容插入更改【英文标题】:Content Inset change when UITableView Reload Sections 【发布时间】:2019-01-28 06:16:33 【问题描述】:

我有一个可扩展的UITableView。当用户点击标题时,相关单元格将显示动画(RowAnimation.Fade),然后UITableView 滚动到该标题(扩展标题)。当用户再次点击该标题时,它会折叠。

我想要实现:我需要一个带有标题和单元格的可扩展 UITableView。当用户点击标题时,需要使用RowAnimation.Fade 打开单元格,然后滚动到该标题。

奖励:另外,如果我可以在用户点击标题时获得箭头动画会很棒,但我认为这会导致另一个错误,因为我们在同一个线程(主线程)上运行了这么多动画

我的问题是,当用户点击标题时,tableView 内容插入会发生变化,并且整个标题会变为负 Y 位置。于是出现了一个奇怪的动画。 (例如,标题,看起来是单元格的中心)但是,动画完成后,一切看起来都是正确的。

func toggleSection(header: DistrictTableViewHeader, section: Int) 
self.selectedHeaderIndex = section
self.cities[section].isCollapsed = !self.cities[section].isCollapsed
let contentOffset = self.tableView.contentOffset
self.tableView.reloadSections(IndexSet(integer: section), with: UITableView.RowAnimation.fade)
self.tableView.scrollToRow(at: IndexPath(row: NSNotFound, section: section) /* you can pass NSNotFound to scroll to the top of the section even if that section has 0 rows */, at: UITableView.ScrollPosition.top, animated: true)

另外:我设置了标题和单元格的高度,如下所示。

func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat 
    return 1

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat 
    return 60

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
    return 140

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? 
    let header: DistrictTableViewHeader = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerId) as! DistrictTableViewHeader
    //let header = DistrictTableViewHeader()

    header.customInit(title: self.cities[section].name, section: section, delegate: self,isColapsed: self.cities[section].isCollapsed,isSelectedHeader: section == selectedHeaderIndex ? true : false)
    return header

我的自定义 headerView 类:

protocol ExpandableHeaderViewDelegate 
func toggleSection(header: DistrictTableViewHeader, section: Int)


class DistrictTableViewHeader: UITableViewHeaderFooterView 
var delegate: ExpandableHeaderViewDelegate?
var section: Int!

let nameLabel: UILabel = 
   let l = UILabel()
    l.textColor = Color.DistrictsPage.headerTextColor
    return l
()

private let arrowImage: UIImageView = 
  let i = UIImageView()
    let image = UIImage(named: "ileri")?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate)
    i.image = image
    i.contentMode = .scaleAspectFit
    return i
()
var willAnimate: Bool = false
var isColapsed: Bool!
    didSet
        expandCollapseHeader()
    


private func expandCollapseHeader()
    if(willAnimate)
        if(!self.isColapsed)
            let degrees : Double = 90 //the value in degrees
            self.nameLabel.textColor = Color.Common.garantiLightGreen
            self.arrowImage.tintColor = Color.Common.garantiLightGreen
            self.arrowImage.transform = CGAffineTransform.init(rotationAngle: CGFloat(degrees * .pi/180))
            self.contentView.backgroundColor = UIColor(red:0.97, green:0.97, blue:0.97, alpha:1.0)
        else
            let degrees : Double = 0 //the value in degrees
            self.nameLabel.textColor = Color.DistrictsPage.headerTextColor
            self.arrowImage.tintColor = UIColor.black
            self.arrowImage.transform = CGAffineTransform.init(rotationAngle: CGFloat(degrees * .pi/180))
            self.contentView.backgroundColor = UIColor.white
        
    else
        if(!isColapsed)
            let degrees : Double = 90 //the value in degrees
            self.nameLabel.textColor = Color.Common.garantiLightGreen
            self.arrowImage.tintColor = Color.Common.garantiLightGreen
            self.arrowImage.transform = CGAffineTransform.init(rotationAngle: CGFloat(degrees * .pi/180))
            self.contentView.backgroundColor = UIColor(red:0.97, green:0.97, blue:0.97, alpha:1.0)
        else
            let degrees : Double = 0 //the value in degrees
            self.nameLabel.textColor = Color.DistrictsPage.headerTextColor
            self.arrowImage.tintColor = UIColor.black
            self.arrowImage.transform = CGAffineTransform.init(rotationAngle: CGFloat(degrees * .pi/180))
            self.contentView.backgroundColor = UIColor.white
        
        layoutSubviews()
    


override init(reuseIdentifier: String?) 

    super.init(reuseIdentifier: reuseIdentifier)
    self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(selectHeaderAction)))
    nameLabel.translatesAutoresizingMaskIntoConstraints = false
    nameLabel.font = UIFont.systemFont(ofSize: 22)
    nameLabel.textColor = Color.DistrictsPage.headerTextColor
    contentView.addSubview(nameLabel)
    nameLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
    nameLabel.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 15).isActive = true

    arrowImage.tintColor =  UIColor(red:0.32, green:0.36, blue:0.36, alpha:1.0)
    arrowImage.translatesAutoresizingMaskIntoConstraints = false
    contentView.addSubview(arrowImage)
    arrowImage.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
    arrowImage.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -20).isActive = true
    arrowImage.widthAnchor.constraint(equalToConstant: 20).isActive = true



required init?(coder aDecoder: NSCoder) 
    fatalError("init(coder:) has not been implemented")

func rotate(_ toValue: CGFloat) 
    self.transform = CGAffineTransform.init(rotationAngle: toValue)

@objc func selectHeaderAction(gestureRecognizer: UITapGestureRecognizer) 
    let cell = gestureRecognizer.view as! DistrictTableViewHeader
    delegate?.toggleSection(header: self, section: cell.section)



func customInit(title: String, section: Int, delegate: ExpandableHeaderViewDelegate,isColapsed: Bool, isSelectedHeader: Bool) 
    self.nameLabel.text = title
    self.nameLabel.accessibilityIdentifier = title
    self.section = section
    self.delegate = delegate
    self.willAnimate = isSelectedHeader
    self.isColapsed = isColapsed


override func layoutSubviews() 
    super.layoutSubviews()
    self.contentView.backgroundColor = UIColor.white



在下图中,可以清楚地看到错误。当“一些数据”和“另一个城市”打开时,你点击“一些数据”。出现动画错误。 “另一个城市”越过它的牢房,然后上升。应该做的是“另一个城市”应该留在它的位置,然后在“某些数据”单元格关闭时向上移动。 示例项目: https://github.com/emreond/tableViewLayoutIssue

【问题讨论】:

为什么这个问题会被否决?你能解释一下吗? 您好@Emre,我刚刚阅读了您的问题,但我无法确定动画中的问题,您可以制作小视频或 gif 并将链接放在这里...以便我可以得到确切的问题。还有一件事......如果您有任何其他示例(可能是应用程序或设计)来描述您的预期行为,那么这将更好地理解您的问题。顺便说一句,我不是一个反对你的问题的人。 您好,我编辑了我的问题并试图在屏幕截图解释中更清楚地解释它。另一个城市标题超出它的单元格,然后向上移动。但是,它应该保持不变,因为我只关闭了一些数据而不是另一个城市。 假设A,B和C是3个部分,现在如果你关闭B部分,那么很明显tableview的contentOffset将保持不变。因此,C 部分将向上。另外,如果 (contentOffset.y + screensize.height) 将大于 contentSize,那么在这种情况下,contentOffset 的 y 位置也会更新(在这种情况下会下降)。 我认为这是正常行为,如果我对您的问题的理解是正确的。 【参考方案1】:

经过几天的搜索和尝试,我发现将UITableViewStyle 更改为组可以解决问题。因此,我将初始化UITableView 更改为

let tableView = UITableView.init(frame: CGRect.zero, style: .grouped)

除了滚动,我还需要添加CATransaction 来捕捉完成。

CATransaction.begin()
DispatchQueue.main.async 
self.tableView.beginUpdates()
CATransaction.setCompletionBlock 
    // Code to be executed upon completion
        self.tableView.scrollToRow(at: IndexPath(row: NSNotFound, section: section) /* you can pass NSNotFound to scroll to the top of the section even if that section has 0 rows */, at: UITableView.ScrollPosition.top, animated: true)

    self.tableView.reloadSections(IndexSet.init(integer: section), with: UITableView.RowAnimation.fade)
    self.tableView.endUpdates()

CATransaction.commit()

【讨论】:

【参考方案2】:

遇到同样的问题,你可以用这个来解决:

UIView.performWithoutAnimation 
    self.tableView.reloadSections(IndexSet(integer: section), with: .fade)

当然,你会失去动画,但也会失去闪烁。

【讨论】:

但我也想要我在问题中提出的动画。

以上是关于UITableView 重新加载部分时的内容插入更改的主要内容,如果未能解决你的问题,请参考以下文章

UITableView 两次重新加载数据,但没有正确显示数据

在 UITableView 中重新加载部分

为什么我的UITableView重新加载其所有节标题?

重新加载包含 UITableView 的 UIViewController 时的 EXEC_BAD_ACCESS

在uitableview中插入行并重新加载

如何在我查看 UITableView 时重新加载它