I have a UICollectionView that sizes cells heights automatically so depending on how much text is inside the cell it will size the height appropriately.
This works perfectly fine I can click all the buttons, scroll up or down, etc, but the problem is that when I call reloadData(), the collectionViewCell's constraints get screwed up and they stack on top of each other for some reason.
Here is a picture of the collectionView before reloadData()
is called:
Here is a picture of the collectionVIew after I call reloadData()
:
Anyone possibly know why this is happening and how I can fix it?
Here is my code for the 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)
}
}
Here is my code for 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()
}
}
Thanks in advance!
Finally figured how you can fix this problem if anyone else is having this issue.
So what I did was I decided to use a UITableView instead of a UICollectionView. When you change to a UITableView you can access the variableUITableView.automaticDimension
.
Side note: You can only use a UITableView if you have one column.
So this is what I did for my 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")
}
}
Then you need to create a containerView that you can later put all the cells contents inside:
private let containerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
Add contentView.addsubview(containerView)
and put all cell contents inside the containerView so your cell can automatically size itself:
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()
func :
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)
}
Hopefully this helps someone! If anyone ever figures out how to call reloadData()
with a dynamic collectionViewCell height without screwing up the cell constraints let me know! I tried for 2 weeks and still never found a solution :(.