调用 reloadData() 后自行调整动态 UICollectionViewCell 高度的大小不正确

Posted

技术标签:

【中文标题】调用 reloadData() 后自行调整动态 UICollectionViewCell 高度的大小不正确【英文标题】:Self sizing dynamic UICollectionViewCell height improperly sizes itself after calling reloadData() 【发布时间】:2022-01-16 02:30:43 【问题描述】:

我有一个 UICollectionView 可以自动调整单元格的高度,因此根据单元格内的文本量,它会适当地调整高度。

这工作得很好,我可以单击所有按钮,向上或向下滚动等,但问题是当我调用 reloadData() 时,collectionViewCell 的约束被搞砸了,它们出于某种原因相互堆叠.

这是调用reloadData()之前collectionView的图片:

这是我打电话给reloadData()后收集的照片:

任何人都可能知道为什么会发生这种情况以及我该如何解决?

这是我的 CustomCollectionView 代码:

class CustomCollectionView: UICollectionView 
    
public let bottomRefresh = CollectionViewBottomRefresh()

init() 
    let layout = UICollectionViewFlowLayout()
    layout.minimumLineSpacing = 0
    layout.minimumInteritemSpacing = 0
    layout.scrollDirection = .vertical
    layout.estimatedItemSize = CGSize(width: UIScreen.main.bounds.width, height: 50)
    super.init(frame: .zero, collectionViewLayout: layout)
    alwaysBounceVertical = true
    backgroundColor = .systemBackground
    delaysContentTouches = false
    showsVerticalScrollIndicator = false
    register(PostView.self, forCellWithReuseIdentifier: "post")


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


override func touchesShouldCancel(in view: UIView) -> Bool 
    if view is UIButton || view is UITextField 
        return true
    
    return super.touchesShouldCancel(in: view)

这是我的 CollectionViewCell 代码:

class PostView: UICollectionViewCell, 

override init(frame: CGRect) 
    super.init(frame: frame)
    contentView.addSubview(commentsButton)
    contentView.addSubview(kuduAppTeamDeleteButton)
    contentView.addSubview(titleLabel)
    contentView.addSubview(infoButton)
    contentView.addSubview(imageViewButton)
    contentView.addSubview(likeButton)
    contentView.addSubview(followButton)
    contentView.addSubview(profile)
    contentView.addSubview(likeCount)
    contentView.addSubview(date)
    contentView.addSubview(line)
    addConstraints()


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


public func setupView(post: Post) 
    guard let post = fb.posts.firstIndex(where:  p in p.id == post.id ) else  return 
    self.post = post
    guard let user = fb.users.firstIndex(where:  user in user.id == fb.posts[post].uid ) else  return 
    self.user = user
    
    if fb.currentUser.likes.contains(fb.posts[post].id) 
        self.likeButton.setImage(UIImage(systemName: "hand.thumbsup.fill"), for: .normal)
        self.likeButton.tintColor = UIColor.theme.blueColor
     else 
        self.likeButton.setImage(UIImage(systemName: "hand.thumbsup"), for: .normal)
        self.likeButton.tintColor = .label
    
    let result = String(format: "%ld %@", locale: Locale.current, fb.posts[post].likeCount, "")
    likeCount.text = result
    
    
    //Button Actions
    infoButton.addAction(infoButtonAction, for: .touchUpInside)
    likeButton.addAction(likeButtonAction, for: .touchUpInside)
    followButton.addAction(followButtonAction, for: .touchUpInside)
    imageViewButton.addAction(imageViewButtonAction, for: .touchUpInside)
    profile.addAction(profileAction, for: .touchUpInside)
    commentsButton.addAction(commentsButtonAction, for: .touchUpInside)
    kuduAppTeamDeleteButton.addAction(kuduAppTeamDeleteButtonAction, for: .touchUpInside)
    
    //Date
    let dateFormatter = DateFormatter()
    dateFormatter.timeStyle = .none
    dateFormatter.dateStyle = .long
    let dateString = dateFormatter.string(from: fb.posts[post].date)
    date.text = dateString
    
    //Set follow button text
    if self.fb.currentUser.following.contains(fb.users[user].id) 
        self.followButton.label.text = "Unfollow"
     else 
        self.followButton.label.text = "Follow"
    
    
    //Set imageview image
    imageViewButton.setImage(fb.posts[post].image, for: .normal)
    
    imageViewButton.imageView!.contentMode = .scaleAspectFill
    
    //Set user image
    profile.usernameLabel.text = fb.users[user].username
    profile.profileImage.image = fb.users[user].profileImage
    if profile.profileImage.image == UIImage(systemName: "person.circle.fill") 
        profile.profileImage.tintColor = UIColor.theme.accentColor
    
    
    //Set post title
    titleLabel.text = fb.posts[post].title


override func prepareForReuse() 
    //Remove all actions
    infoButton.removeAction(infoButtonAction, for: .touchUpInside)
    likeButton.removeAction(likeButtonAction, for: .touchUpInside)
    imageViewButton.removeAction(imageViewButtonAction, for: .touchUpInside)
    profile.removeAction(profileAction, for: .touchUpInside)
    commentsButton.removeAction(commentsButtonAction, for: .touchUpInside)
    followButton.removeAction(followButtonAction, for: .touchUpInside)
    kuduAppTeamDeleteButton.removeAction(kuduAppTeamDeleteButtonAction, for: .touchUpInside)
    
    //Remove any other text or images
    for subview in imageViewButton.subviews 
        if let subview = subview as? UIImageView, subview.image == UIImage(systemName: "play.circle.fill") 
            subview.removeFromSuperview()
        
    
    imageViewButton.setImage(nil, for: .normal)
    kuduAppTeamDeleteButton.color = nil
    profile.profileImage.image = nil
    titleLabel.text = nil
    date.text = nil
    self.followButton.label.text = nil


// Sets a requried width and a dynamic height that changes depending on what is in the cell. So we can have searchbar as first cell heigh 50, and post in other cells with height of view.bounds.width.
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes 
    let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0)
    layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
    return layoutAttributes


private func addConstraints() 
    kuduAppTeamDeleteButton.height(30)
    kuduAppTeamDeleteButton.width(UIScreen.main.bounds.width / 4)
    kuduAppTeamDeleteButton.bottom(to: commentsButton)
    kuduAppTeamDeleteButton.leftToRight(of: commentsButton, offset: 5)
    
    imageViewButton.width(UIScreen.main.bounds.width)
    imageViewButton.height(UIScreen.main.bounds.width * 9/16)
    imageViewButton.topToSuperview()
    
    infoButton.leftToRight(of: titleLabel, offset: 6)
    infoButton.topToBottom(of: imageViewButton, offset: 15)
    infoButton.width(30)
    infoButton.height(30)
    
    titleLabel.horizontalToSuperview(insets: UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 40))
    titleLabel.topToBottom(of: imageViewButton, offset: 5)
    titleLabel.height(min: 50)
    
    likeButton.topToBottom(of: titleLabel, offset: 10)
    likeButton.trailingToSuperview(offset: 10)
    likeButton.height(32)
    likeButton.width(32)
    
    profile.leadingToSuperview(offset: 5)
    profile.topToBottom(of: titleLabel, offset: 10)
    profile.widthToSuperview(multiplier: 0.4)
    
    likeCount.trailingToLeading(of: likeButton, offset: -5)
    likeCount.topToBottom(of: titleLabel, offset: 15)
    
    followButton.topToBottom(of: titleLabel, offset: 5)
    followButton.trailingToLeading(of: likeCount, offset: -10)
    followButton.height(50)
    followButton.width(UIScreen.main.bounds.width / 4)
    
    date.bottom(to: commentsButton, offset: -5)
    date.trailingToSuperview(offset: 5)
    
    commentsButton.topToBottom(of: profile, offset: 10)
    commentsButton.leadingToSuperview(offset: 5)
    
    line.horizontalToSuperview()
    line.bottom(to: commentsButton)
    line.height(1)
    
    contentView.bottom(to: line)
    contentView.widthToSuperview()

提前致谢!

【问题讨论】:

【参考方案1】:

终于想通了,如果其他人遇到这个问题,你可以如何解决这个问题。

所以我决定使用 UITableView 而不是 UICollectionView。当您更改为 UITableView 时,您可以访问变量UITableView.automaticDimension

附注:只有一列才能使用 UITableView。

这就是我为 UITableView 所做的:

class MyTableView: UITableView 

public let bottomRefresh = TableViewBottomRefresh()

init() 
    super.init(frame: .zero, style: .plain)
    rowHeight = UITableView.automaticDimension
    estimatedRowHeight = 500
    separatorStyle = .none
    allowsSelection = false
    delaysContentTouches = false
    alwaysBounceVertical = true
    showsVerticalScrollIndicator = false
    register(MyTableViewCell.self, forCellReuseIdentifier: "MyCell")

然后你需要创建一个containerView,以后可以将所有单元格内容放入其中:

    private let containerView: UIView = 
   let view = UIView()
    view.translatesAutoresizingMaskIntoConstraints = false
    return view
()

添加contentView.addsubview(containerView)并将所有单元格内容放入containerView中,以便您的单元格可以自动调整大小:

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) 
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    contentView.addSubview(containerView)
    containerView.addSubview(profileImage)
    containerView.addSubview(username)
    containerView.addSubview(editProfileButton)
    addConstraints()

addconstraints()函数:

    private func addConstraints() 
    profileImage.topToSuperview()
    profileImage.centerXToSuperview()
    profileImage.widthToSuperview(multiplier: 1/2)
    profileImage.height(UIScreen.main.bounds.width / 2)
    
    username.topToBottom(of: profileImage, offset: 10)
    username.horizontalToSuperview()
    username.height(50)
    
    editProfileButton.height(50)
    editProfileButton.widthToSuperview(multiplier: 1/3)
    editProfileButton.centerXToSuperview()
    editProfileButton.topToBottom(of: username, offset: 10)
    
    containerView.widthToSuperview()
    containerView.bottom(to: editProfileButton)

希望这对某人有所帮助!如果有人知道如何使用动态 collectionViewCell 高度调用 reloadData() 而不会破坏单元格约束,请告诉我!我尝试了 2 周,但仍然没有找到解决方案:(。

【讨论】:

以上是关于调用 reloadData() 后自行调整动态 UICollectionViewCell 高度的大小不正确的主要内容,如果未能解决你的问题,请参考以下文章

具有自我调整大小的单元格的 UICollectionViewFlowLayout 在 reloadData 后崩溃

使用自我调整大小的单元格后,reloadData 不再起作用

UITableView, reloadData 重新加载单元格但不调整表格大小

调用 reloadData 时 UITableView 有不需要的动画

仅在 reloadData 完成后调用函数

在 UICollectionView 上调用 reloadData 后 contentSize 未更新