Reputation: 695
After initial table load, everything is working correctly. But after inserting a new cell, and scrolling fast up, you can see some cells recalculating their sizes (animating). It's really odd and happens to 2-3 cells max. I'm using a bottom-up collection view with reversed cells, and custom flow layout, but it has no animation either. Also when scrolling, keyboard hides, so it might be something to do with it..
Cell insert :
self.collectionView?.insertItems(at: [indexPath])
UIView.performWithoutAnimation {
self.collectionView?.reloadItems(at: indexPaths)
self.view.layoutIfNeeded()
}
self.collectionView?.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
Video of the issue:
http://vimple.co/3c39cb325b9c4ec19173fea015b6cc8b
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let newIndex : Int = messagesDataArray.count - 1 - indexPath.item
if messagesDataArray[newIndex].otherPerson == true {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ConversationCellOtherPerson", for: indexPath) as! ConversationCellOtherPerson
cell.data = messagesDataArray[newIndex]
cell.personImageView.image = otherPersonImage
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ConversationCellUser", for: indexPath) as! ConversationCellUser
cell.data = messagesDataArray[newIndex]
return cell
}
}
Also. ConversationCellUser:
class ConversationCellUser : UICollectionViewCell {
let messageLabel = ConversationLabel()
let mainContainerView = UIView()
let textContainerView : UIView = {
let view = UIView()
view.layer.cornerRadius = 20
return view
}()
var data : MessagesInfo? { didSet { updateCell() } }
func updateCell() {
guard let data = data else { return }
messageLabel.text = data.messageText
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
clipsToBounds = true
messageLabel.translatesAutoresizingMaskIntoConstraints = false
textContainerView.translatesAutoresizingMaskIntoConstraints = false
mainContainerView.translatesAutoresizingMaskIntoConstraints = false
mainContainerView.addSubview(textContainerView)
textContainerView.addSubview(messageLabel)
contentView.addSubview(mainContainerView)
NSLayoutConstraint(item: textContainerView, attribute: .right, relatedBy: .equal, toItem: mainContainerView, attribute: .right, multiplier: 1, constant: -10).isActive = true
textContainerView.addConstraintsWithFormat(format: "H:|-14-[v0]-14-|", views: messageLabel)
textContainerView.addConstraintsWithFormat(format: "V:|-10-[v0]-10-|", views: messageLabel)
contentView.addConstraintsWithFormat(format: "H:|[v0]|", views: mainContainerView)
contentView.addConstraintsWithFormat(format: "V:|[v0]|", views: mainContainerView)
textContainerView.backgroundColor = .lightGray
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
transform = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: 0)
updateConstraints()
setNeedsUpdateConstraints()
}
}
ConversationLabel :
class ConversationLabel : BaseMessageLabel {
override init(frame: CGRect) {
super.init(frame: frame)
self.textColor = .white
self.font = UIFont(name: "AvenirNext-Medium", size: 14)
self.lineBreakMode = .byWordWrapping
self.numberOfLines = 0
self.preferredMaxLayoutWidth = 200
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
SizeForItem :
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var height : CGFloat = 80
let text = messagesDataArray[indexPath.row].messageText
height = estimateFrameFor(text).height + 20
return CGSize(width: collectionView.bounds.size.width, height: height)
}
private func estimateFrameFor(_ text : String) -> CGRect {
let size = CGSize(width: 200, height: 1000)
return NSString(string: text).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName : UIFont(name: "AvenirNext-Medium", size: 14)!], context: nil)
}
And the FlowLayout I'm using is this :
Github - jochenschoellig - ChatCollectionViewFlowLayout
Hey, I have created a small project with the problem: Github - testLP
Upvotes: 4
Views: 2409
Reputation: 1090
Can't comment, not enough rep. But as was commented, please provide your code for the ConversationLabel
class, the sizeForItemAtIndexPath
override as well as your custom FlowLayout
class. When you add custom layout classes, it's easy to overlook certain resulting behaviors.
Without those methods given, you won't have much luck here. When you do, I'll look and update my answer.
However, judging from the video, here's what I'd look at first:
Since cells are re-added to the visible part of the collectionview
when their index becomes visible, it is possible that the animation is either due to sizing that you apply inside of sizeForItemAtIndexPath
which might be delayed if whatever cell element you use to apply the size is not updated quickly enough. Or that the animation is somehow visible due to lag, since you do call constraint resetters each time the cell views are brought into view ( this could cause some lag depending on the device).
I'm suggesting the latter, because of your apply(_ layoutAttributes: UICollectionViewLayoutAttributes)
override. Given the documentation here: you're calling updateConstraints()
once, and then using setNeedsUpdateConstraints()
right after, which schedules an updateConstraints()
call on layout of the cell view when it becomes visible (which is when you're seeing the animation). So you're doing the work twice here, which could add to the animation queue. Or, since you're also resetting the transform attribute, you're might also be scheduling an extra animation that you might not have taken into account.
Finally, While this might hint at where this behavior is stemming from, it's impossible to say without your custom FlowLayout class. As mentioned here:
If you subclass and implement any custom layout attributes, you must also override the inherited isEqual: method to compare the values of your properties. In iOS 7 and later, the collection view does not apply layout attributes if those attributes have not changed. It determines whether the attributes have changed by comparing the old and new attribute objects using the isEqual: method.
And, as is referred to in this post about iOS7 and later os builds:
since you override the apply(_ layoutAttributes: UICollectionViewLayoutAttributes)
method, you are also supposed to override the isEqual(_ object: Any?)
method with your own code which compares layout attributes the way you want.
An example of the isEqual override:
override public func isEqual(_ object: Any?) -> Bool {
guard let rhs = object as? DateClass else {
return false
}
let lhs = self
return lhs.date1 == rhs.date1 &&
lhs.date2 == rhs.date2 &&
lhs.date3 == rhs.date3
}
You'd swap out DateClass with your UICollectionViewLayoutAttributes, putting the override inside the scope of your UICollectionViewLayoutAttributes subclass.
So as far what I can tell with what you've given, it seems like there might be accidental over-calling of certain methods in your code. Or perhaps you didn't subclass UICollectionViewLayoutAttributes properly. Again, these are my best solutions for what you provide. Add the missing code, and I will update answer!
Upvotes: 3