2017-01-28 7 views
4

Ich implementiere einen einfachen Messenger für meine App, in dem die Benutzer untereinander chatten können. Der Messenger basiert auf UICollectionView (JSQMessagesViewController), wobei jede Nachricht durch eine UICollectionView-Zeile repräsentiert wird. Jede Nachricht hat auch eine obere Beschriftung, die verwendet wird, um anzuzeigen, wann die Nachricht gesendet wurde. Diese Beschriftung ist anfangs ausgeblendet (Höhe = 0) und wenn der Benutzer die bestimmte Nachricht (Zeile) antippt, wird die Beschriftung angezeigt, indem die Höhe entsprechend eingestellt wird. (height = 25)JSQMessagesView Probleme bei der Änderung der Zeilenhöhe

Das Problem, mit dem ich konfrontiert bin, ist die eigentliche Animation der Anzeige des Etiketts. (Höhenänderung). Ein Teil der Reihe überlagert die Reihe um einige Pixel, bevor sie an ihre Position gelangt. Wenn die Beschriftung zurückverdeckt wird, setzt die Animation zuerst die Höhe auf Null, und dann wird der Text ausgeblendet, der einen Teil der unteren Nachricht überlagert, was wirklich schlecht aussieht.

Also im Grunde, was ich versuche zu erreichen ist, diese beiden zuvor genannten Probleme loszuwerden.

Code:

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) 

    } 

Demo: (Sorry für die Qualität)

enter image description here

Ich würde wirklich schätzen, wenn jemand mir dabei helfen konnte, wie ich bereits ausgegeben haben mehrere Stunden versuchen, es herauszufinden, ohne irgendwo zu kommen.

Vielen Dank im Voraus!

EDIT:

ich die Lösung von @jamesk wie folgt vorgeschlagen versucht:

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) 
     } 
} 

Und überschreiben die apply von JSQMessagesCollectionViewCell:

extension JSQMessagesCollectionViewCell { 

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

jedoch diese Änderungen in Folge:

enter image description here

Ich habe auch versucht die zweite Lösung mit dem Layout ungültig zu machen:

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() 
     } 

    } 

die in der folgenden Folge:

enter image description here

Antwort

2

Es scheint zwei Probleme zu geben. Das erste Problem besteht darin, dass der Aufruf an reloadItems(at:) auf das Überblenden zwischen der alten Zelle und der neuen Zelle beschränkt ist - es interpoliert nicht zwischen den Layoutattributen für die alte Zelle und den Layoutattributen für die neue Zelle. Das zweite Problem ist, dass es scheinbar keinen Code gibt, der die ausgewählte Zelle anweist, einen Layout-Durchlauf durchzuführen, wenn dies erforderlich ist, wenn neue Layout-Attribute darauf angewendet werden.

Die JSQMessagesViewController Framework verwendet Subklassen von UICollectionViewFlowLayout und UICollectionViewFlowLayoutInvalidationContext, so können wir die Ungültigkeits Verhalten der Strömung Layout nutzen, bei der Aktualisierung und Elemente zu animieren. Alles, was benötigt wird, ist, die Layoutattribute (d. H. Position) ungültig zu machen und Metriken (d. H. Größe) für die Elemente zu delegieren, die von der Änderung der Zellenhöhe betroffen sind.

Der folgende Code für die Verwendung mit dem Swift Beispielprojekt geschrieben wurde in dem release_7.3 Zweig des JSQMessagesViewController enthalten:

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. 
    } 
} 

Der obige Code sollte vernünftigerweise performant sein.

Während die Positionen der betroffenen Elemente immer neu berechnet werden müssen, ist es nicht erforderlich, die Größe aller betroffenen Elemente wie oben beschrieben neu zu berechnen. Es wäre ausreichend, nur die Größe des angezapften Elements neu zu berechnen. Da der Effekt der invalidateFlowLayoutDelegateMetrics -Eigenschaft immer auf jedes invalidierte Element angewendet wird, müssen Sie zur Implementierung dieses engeren Ansatzes zwei Ungültigkeitskontexte für den Ablaufplanentwurf verwenden und die Elemente zwischen ihnen aufteilen (oder einen benutzerdefinierten Invalidierungskontext mit dem entsprechenden Ungültigkeitsverhalten implementieren). . Es ist wahrscheinlich nicht wert, es sei denn, Instruments sagt Ihnen etwas anderes.

+0

Ich habe versucht, beide Lösungen, die Sie vorgeschlagen, aber keiner von ihnen funktioniert (siehe Frage EDIT). Hast du eine Ahnung, was schief gelaufen ist? – user3559787

+0

Ich habe diese Dinge versucht, aber es funktioniert immer noch nicht. – user3559787

+0

Ich denke, die einfachste wäre, wenn Sie nur einen Link zu Ihrem Beispiel (das Sie bereits haben). Ich werde sehen, was ich anders gemacht habe und es hier erneut hochladen. – user3559787

0

Nach dem Einfügen der Daten dieses Stück Code versuchen hinzufügen.

collectionView.reloadItems(at: [indexPath]) 

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

Vielen Dank für den Vorschlag, aber es machte keinen Unterschied. Die Animation verhält sich immer noch gleich. – user3559787

Verwandte Themen