我正在为我的应用实现一个简单的信使,用户可以在其中聊天。 messenger基于UICollectionView(JSQMessagesViewController),其中每条消息由一个UICollectionView行表示。每条消息还有一个顶部标签,用于显示消息的发送时间。该标签最初是隐藏的(高度= 0),当用户点击特定消息(行)时,通过相应地设置高度来显示标签。 (高度= 25)
我面临的问题是显示标签的实际动画。 (身高变化)。在它到达它的位置之前,行的一部分将该行覆盖几个像素。此外,当隐藏标签时,动画首先将高度设置为零,然后文本淡出覆盖下面看起来非常糟糕的消息的一部分。
所以基本上我想要实现的是摆脱前面提到的那两个问题。
代码:
override func collectionView(_ collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForCellTopLabelAt indexPath: IndexPath!) -> CGFloat {
if indexPath == indexPathTapped {
return 25
}
let messageCurrent = messages[indexPath.item]
let messagePrev: JSQMessage? = indexPath.item - 1 >= 0 ? messages[indexPath.item - 1] : nil
if messageCurrent.senderId == messagePrev?.senderId || messagePrev == nil {
return 0
}
else{
return 25
}
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, didTapMessageBubbleAt indexPath: IndexPath!) {
if let indexPathTapped = indexPathTapped, indexPathTapped == indexPath {
self.indexPathTapped = nil
}
else{
indexPathTapped = indexPath
}
collectionView.reloadItems(at: [indexPath])
// UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveLinear, animations: {
// collectionView.performBatchUpdates({
// collectionView.reloadItems(at: [indexPath])
// }, completion: nil)
// }, completion: nil)
}
演示:(对不起质量)
如果有人可以帮助我,我真的很感激,因为我已经花了几个小时试图找出它而没有任何地方。
提前谢谢!
修改
我尝试了@jamesk提出的解决方案如下:
override func collectionView(_ collectionView: JSQMessagesCollectionView!, didTapMessageBubbleAt indexPath: IndexPath!) {
if let indexPathTapped = indexPathTapped, indexPathTapped == indexPath {
self.indexPathTapped = nil
}
else{
indexPathTapped = indexPath
}
UIView.animate(withDuration: 0.25) {
collectionView.performBatchUpdates(nil)
}
}
并覆盖apply
的{{1}}:
JSQMessagesCollectionViewCell
然而,这些变化导致:
我还尝试了第二种解决方案,使布局无效:
extension JSQMessagesCollectionViewCell {
override open func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
layoutIfNeeded()
}
}
结果如下:
答案 0 :(得分:4)
似乎有两个问题。第一个问题是对reloadItems(at:)
的调用仅限于旧单元格与新单元格之间的交叉渐变 - 它不会在旧单元格的布局属性和新单元格的布局属性之间进行插值。第二个问题是,似乎没有任何代码指示您选择的单元格在应用新布局属性时根据需要执行布局传递。
JSQMessagesViewController框架使用UICollectionViewFlowLayout和UICollectionViewFlowLayoutInvalidationContext的子类,因此我们可以在更新和动画项目时利用流布局的失效行为。所需要的只是使布局属性(即位置)无效并委托受单元格高度变化影响的项目的度量(即大小)。
下面的代码是为了与JSQMessagesViewController的release_7.3
分支中包含的Swift示例项目一起使用而编写的:
override func collectionView(_ collectionView: JSQMessagesCollectionView!, didTapMessageBubbleAt indexPath: IndexPath!) {
// Determine the lowest item index affected by the change in cell size.
// Lesser of previous tapped item index (if any) and current tapped item index.
let minItem = min(tappedIndexPath?.item ?? indexPath.item, indexPath.item)
// Update tapped index path.
tappedIndexPath = (tappedIndexPath == indexPath ? nil : indexPath)
// Prepare invalidation context spanning all affected items.
let context = JSQMessagesCollectionViewFlowLayoutInvalidationContext()
let maxItem = collectionView.numberOfItems(inSection: 0) - 1
let indexPaths = (minItem ... maxItem).map { IndexPath(item: $0, section: 0) }
context.invalidateItems(at: indexPaths) // Must include all affected items.
context.invalidateFlowLayoutAttributes = true // Recompute item positions (for all affected items).
context.invalidateFlowLayoutDelegateMetrics = true // Recompute item sizes (needed for tapped item).
UIView.animate(withDuration: 0.25) {
collectionView.collectionViewLayout.invalidateLayout(with: context)
collectionView.layoutIfNeeded() // Ensure layout pass for visible cells.
}
}
上述代码应该具有合理的性能。
虽然必须始终重新计算受影响物品的位置,但不必如上所述重新计算所有受影响物品的大小。仅重新计算轻敲项目的大小就足够了。但是由于invalidateFlowLayoutDelegateMetrics
属性的效果始终应用于每个无效项,要实现更窄的方法,您需要使用两个流布局失效上下文并在它们之间划分项(或实现自定义失效上下文相应的无效行为)。除非乐器另有说明,否则它可能不值得。
答案 1 :(得分:0)
插入数据后,尝试添加这段代码。
collectionView.reloadItems(at: [indexPath])
UIView.animate(withDuration: 0.6) {
self.view.layoutIfNeeded()
}