UITableView 自定义单元格高度未正确管理 swift 中的动态约束

Posted

技术标签:

【中文标题】UITableView 自定义单元格高度未正确管理 swift 中的动态约束【英文标题】:UITableView custom cell height is not properly managing for dynamic constraints in swift 【发布时间】:2020-01-29 10:27:57 【问题描述】:

我有表格视图并从 xib 中获取自定义单元格,我有很多 当有媒体或更大的文本时,单元格中要扩展的内容,我 已在委托方法中以编程方式管理高度,并且 单元格中的约束但有时当我打开应用程序并 加载单元格重叠的数据但是当我向上向下滚动时 看起来不错。之后我该怎么做才能立即更新 当我从 API 获取数据时重新加载?

我认为代码没有问题,因为它在再次刷新或滚动表格视图后可以工作。 高度委托方法如下:

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

    let newsDict = self.tableDataNewsArray[indexPath.section]
    let newsDataArray = newsDict["news"] as! [NewsBriefModel]
    let newsObject = newsDataArray[indexPath.item]

    if (self.moreExpandableArray.firstIndex(of: newsObject.newsbrief_id) != nil) 
        return 62 + 58 + 4 + 62 //(62:// label minimum height )(46: other than label caponents in cell :: 4: spacing
        // 62: more/less button view height)

    else if (self.lessExpandableArray.firstIndex(of: newsObject.newsbrief_id) != nil) 

        var baseMoreHeight : CGFloat = newsObject.labelHeightForPostText + 58 + 4 + 62 //(46: other than label caponents in cell :: 4: spacing
        // 62: more/less button view height)

        if newsObject.numberOfImagesInPost > 0 
            baseMoreHeight = baseMoreHeight + 110
        
        if newsObject.numberOfDocumentInPost > 0 
            baseMoreHeight = baseMoreHeight + CGFloat((newsObject.numberOfDocumentInPost * 45))
        
        if newsObject.numberOfVideosInPost > 0 
            baseMoreHeight = baseMoreHeight + CGFloat((newsObject.numberOfVideosInPost * 100))
        

        return baseMoreHeight
    


我在 cellForRow 中调用的用于更改 UI 的 Cell 方法:

 // MARK: CELL METHODS

    func showNormalView() 

        self.postTitleLabel.numberOfLines = 3
        self.postTitleTextView.textContainer.maximumNumberOfLines = 3
        self.postTitleTextView.textContainer.lineBreakMode = .byTruncatingTail

        self.mediaViewHeight.constant = 0
        self.documentTableViewHeight.constant = 0
        self.videoTableViewHeight.constant = 0
        self.multiMediaView.isHidden = true
        self.loadImages(urlString: [])
        self.updateConstraintsIfNeeded()
        self.setNeedsLayout()
        self.layoutIfNeeded()
    

    func showMoreView() 

        self.postTitleLabel.numberOfLines = 3
        self.postTitleTextView.textContainer.maximumNumberOfLines = 3
        self.postTitleTextView.textContainer.lineBreakMode = .byTruncatingTail

        self.mediaViewHeight.constant = 50
        self.imageGridViewHeight.constant = 0
        self.documentTableViewHeight.constant = 0
        self.videoTableViewHeight.constant = 0
        self.loadImages(urlString: [])
        self.multiMediaView.isHidden = false
        self.updateConstraintsIfNeeded()
        self.setNeedsLayout()
        self.layoutIfNeeded()
    

【问题讨论】:

您应该在 Xcode 中调试视图层次结构,以检查 unwanted 显示的视图。 我认为单元格约束不合适。 @Paresh.P 如果是这样的话,那么它可能一直都有问题,这个问题只是第一次......在我再次滚动或刷新之后它会正确显示 @AndreasOetjen 是的,我试过的事情是 tableview 单元格高度没有增加。 你能用cellforrow代码和数据源描述更多吗? 【参考方案1】:

我建议使用 UITableView.automaticDimension 而不是手动计算高度,并使用委托模式来更新单元格的高度。

这是我用这种方法得到的结果:

和代码示例:

import UIKit

struct Content 
  let author: String
  let date: String
  let views: UInt
  let recs: UInt
  let content: String


let longText = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
"""

protocol MyCellDelegate: AnyObject 
  func contentDidChange(cell: UITableViewCell)


final class MyCell: UITableViewCell 
  static let reuseIdentifier = "MyCell"

  weak var delegate: MyCellDelegate?

  private var viewsCountLabel: UILabel!
  private var recsLabel: UILabel!
  private var authorLabel: UILabel!
  private var dateLabel: UILabel!
  private var contentLabel: UILabel!
  private var toggleHeightButton: UIButton!

  override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) 
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    setupSubviews()
  

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

  override func prepareForReuse() 
    super.prepareForReuse()
    viewsCountLabel.text = nil
    recsLabel.text = nil
    authorLabel.text = nil
    dateLabel.text = nil
    contentLabel.text = nil
    contentLabel.numberOfLines = 2
  

  public func setContent(_ content: Content) 
    viewsCountLabel.text = "Views: \(content.views)"
    recsLabel.text = "Recs: \(content.recs)"
    authorLabel.text = content.author
    dateLabel.text = content.date
    contentLabel.text = content.content
  

  private func setupSubviews() 
    let leftStackView = createLeftStack()
    let rightStackView = createRightStack()

    let stackView = UIStackView(arrangedSubviews: [leftStackView, rightStackView])
    stackView.axis = .horizontal
    stackView.spacing = 5
    stackView.alignment = .top

    contentView.addSubview(stackView)
    stackView.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
      stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
      stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10),
      stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 3),
      stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -3)
    ])
  

  private func createLeftStack() -> UIStackView 
    let iconView = createIconView()
    viewsCountLabel = createLabel()
    recsLabel = createLabel()
    let stackView = UIStackView(arrangedSubviews: [iconView, viewsCountLabel, recsLabel])
    stackView.axis = .vertical
    stackView.setCustomSpacing(4, after: iconView)
    stackView.alignment = .leading
    return stackView
  

  private func createRightStack() -> UIStackView 
    authorLabel = createLabel()
    dateLabel = createLabel()
    contentLabel = UILabel()
    contentLabel.font = UIFont.systemFont(ofSize: 13, weight: .bold)
    contentLabel.contentMode = .topLeft

    toggleHeightButton = UIButton(type: .system)
    toggleHeightButton.setTitle("Toggle", for: .normal)
    toggleHeightButton.addTarget(self, action: #selector(expandDidTap), for: .touchUpInside)
    toggleHeightButton.heightAnchor.constraint(equalToConstant: 30).isActive = true

    let stackView = UIStackView(arrangedSubviews: [authorLabel, dateLabel, contentLabel, toggleHeightButton])
    stackView.axis = .vertical
    stackView.alignment = .leading
    return stackView
  

  private func createIconView() -> UIView 
    let iconView = UIView()
    iconView.backgroundColor = .blue
    iconView.layer.cornerRadius = 4
    iconView.layer.masksToBounds = true
    let label = UILabel()
    label.font = UIFont.systemFont(ofSize: 8)
    label.numberOfLines = 2
    label.text = "Upload Icon"
    label.textAlignment = .center
    label.textColor = .white
    iconView.addSubview(label)
    label.translatesAutoresizingMaskIntoConstraints = false
    iconView.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
      label.leadingAnchor.constraint(equalTo: iconView.leadingAnchor, constant: 3),
      label.trailingAnchor.constraint(equalTo: iconView.trailingAnchor, constant: -3),
      label.topAnchor.constraint(equalTo: iconView.topAnchor, constant: 3),
      label.bottomAnchor.constraint(equalTo: iconView.bottomAnchor, constant: -3),
      iconView.heightAnchor.constraint(equalToConstant: 40),
      iconView.widthAnchor.constraint(equalToConstant: 40)
    ])
    return iconView
  

  private func createLabel() -> UILabel 
    let label = UILabel()
    label.font = UIFont.systemFont(ofSize: 8, weight: .light)
    label.numberOfLines = 1
    return label
  

  @objc func expandDidTap() 
    let isExpand = contentLabel.numberOfLines != 0
    if isExpand 
      contentLabel.numberOfLines = 0
     else 
      contentLabel.numberOfLines = 2
    
    delegate?.contentDidChange(cell: self)
  


final class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource 
  private var tableView: UITableView 
    view as! UITableView
  

  private var data: [Content] = [] 
    didSet 
      tableView.reloadData()
    
  

  override func loadView() 
    let tableView = UITableView()
    tableView.delegate = self
    tableView.dataSource = self
    tableView.register(MyCell.self, forCellReuseIdentifier: MyCell.reuseIdentifier)
    view = tableView
  

  override func viewDidLoad() 
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) 
      self.data = [
        Content(author: "Author1", date: "15:07", views: 2, recs: 1, content: longText),
        Content(author: "Author2", date: "15:07", views: 2, recs: 1, content: longText),
        Content(author: "Author3", date: "15:07", views: 2, recs: 1, content: longText),
        Content(author: "Author1", date: "15:07", views: 2, recs: 1, content: longText)
      ]
    
  

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
    data.count
  

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    let cell = tableView.dequeueReusableCell(withIdentifier: MyCell.reuseIdentifier, for: indexPath) as! MyCell
    cell.delegate = self
    let content = data[indexPath.row]
    cell.setContent(content)
    return cell
  

  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
    tableView.deselectRow(at: indexPath, animated: true)
  


extension MyViewController: MyCellDelegate 
  func contentDidChange(cell: UITableViewCell) 
    UIView.animate(withDuration: 0.2) 
      self.tableView.beginUpdates()
      self.tableView.endUpdates()
    
  

【讨论】:

【参考方案2】:

我认为最好使用UITableView.automaticDimension,并确保设置estimatedRowHeight,除此之外,确保单元格中的最后一个元素与ContentViewContentView 具有底部约束@

【讨论】:

以上是关于UITableView 自定义单元格高度未正确管理 swift 中的动态约束的主要内容,如果未能解决你的问题,请参考以下文章

使用 UITableView 的可变高度自定义单元格调用 reloadData 时保持 UITableView 的当前位置

自定义 UITableView 单元格图像转换未应用于可重用单元格

数据未显示在 UITableView 的自定义单元格中

UITableView 单元格未正确显示

当单元格获得自定义高度时如何自定义 UITableView 分隔符

UITableView 单元格内容未正确显示