2017-01-28 25 views
4

我正在为我的应用程序实现一个简单的信使,用户可以在它们自己之间聊天。该Messenger是基于UICollectionView(JSQMessagesViewController),其中每个消息由一个UICollectionView行表示。每封邮件还有一个顶部标签,用于显示邮件的发送时间。这个标签最初是隐藏的(height = 0),当用户点击特定的消息(行)时,通过相应地设置高度来显示标签。 (高度= 25)JSQMessagesView动画行高变化的问题

我面临的问题是显示所述标签的实际动画。 (高度变化)。行的一部分在达到其位置之前将行下方的行覆盖几个像素。同样,当隐藏标签时,动画首先将高度设置为零,然后文本淡出覆盖部分消息,该消息看起来非常糟糕。

所以基本上我试图实现的是摆脱这两个前面提到的问题。

代码:

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) 

    } 

演示:(很抱歉的质量)

enter image description here

我真的很感激,如果有人可以帮助我,因为我已经花了几个小时试图找出没有得到任何地方。

预先感谢您!

编辑:

我尝试作为继@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) 
     } 
} 

,并覆盖applyJSQMessagesCollectionViewCell的:

extension JSQMessagesCollectionViewCell { 

    override open func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) { 
     super.apply(layoutAttributes) 
     layoutIfNeeded() 
    } 
} 

但是这些变化导致:

enter image description here

我也试图与无效的布局的第二溶液:

override func collectionView(_ collectionView: JSQMessagesCollectionView!, didTapMessageBubbleAt indexPath: IndexPath!) { 
     if let indexPathTapped = indexPathTapped, indexPathTapped == indexPath { 
      self.indexPathTapped = nil 
     } 
     else{ 
      indexPathTapped = indexPath 
     } 

     var paths = [IndexPath]() 
     let itemsCount = collectionView.numberOfItems(inSection: 0) 

     for i in indexPath.item...itemsCount - 1 { 
      paths.append(IndexPath(item: i, section: 0)) 
     } 


     let context = JSQMessagesCollectionViewFlowLayoutInvalidationContext() 
     context.invalidateItems(at: paths) 

     UIView.animate(withDuration: 0.25) { 
      self.collectionView?.collectionViewLayout.invalidateLayout(with: context) 
      self.collectionView?.layoutIfNeeded() 
     } 

    } 

这导致了以下内容:

enter image description here

回答

2

似乎有两个问题。第一个问题是,对reloadItems(at:)的调用限于旧的单元格和新的单元格之间的交叉淡入淡出 - 它不会在旧单元格的布局属性和新单元格的布局属性之间进行插值。第二个问题是,在应用新的布局属性时,如果需要的话,似乎没有任何代码指示您选择的单元格执行布局传递。

JSQMessagesViewController框架使用UICollectionViewFlowLayoutUICollectionViewFlowLayoutInvalidationContext的子类,因此我们可以在更新和动画项目时利用流布局的失效行为。所有需要的是使布局属性(即位置)无效,并为受单元高度变化影响的项目委托指标(即大小)。

下面是为与雨燕示例项目使用编写的代码包含在JSQMessagesViewController的release_7.3分支:

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属性的效果总是应用于每个无效项目,为了实现这种较窄的方法,您需要使用两个流布局无效上下文并将它们之间的项目分开(或者实现具有相应无效行为的自定义无效上下文) 。除非仪器另有说明,否则这可能是不值得的。

+0

我试过了你提出的两种解决方案,但是他们都没有工作(请看编辑问题)。你有什么想法出了什么问题? – user3559787

+0

我尝试过这些东西,但它仍然无法正常工作。 – user3559787

+0

我认为最简单的方法就是,如果你只是提供一个链接到你的例子(你已经有)。我会看到我做了什么不同,并重新上传到这里。 – user3559787

0

插入数据尝试添加这段代码后。

collectionView.reloadItems(at: [indexPath]) 

UIView.animate(withDuration: 0.6) { 
    self.view.layoutIfNeeded() 
} 
+0

谢谢你的建议,但它没有什么区别。动画仍然表现相同。 – user3559787