Loading more messages with JSQMessages in swift

So I’m using the awesome JSQMessagesViewController for my chat app, and this week I got to the point of loading more messages when the user tap on JSQMessagesLoadEarlierHeaderView or just when he scrolls to the top.

It took a bit of fiddling since I add to prepend my array with the previous messages, reload the collectionView with new indexes and scroll back to the position I was before.

In the end I came up with two solutions, one using the native function didTapLoadEarlierMessagesButton , and the other, much better in my opinion, using a library to detect when the user scroll to the top, thus creating an infinite scrolling.

 

Loading more messages on tap in JSQMessages

override func collectionView(collectionView: JSQMessagesCollectionView!, header headerView: JSQMessagesLoadEarlierHeaderView!, didTapLoadEarlierMessagesButton sender: UIButton!) {
        println("Load earlier messages tapped!")
        
        showLoadEarlierMessagesHeader = false

        if let msgArray = JSQmessages.ref(0) {
        
            let lastMessageId = JSQmessages[0].message.id
            println("Last message id: \(lastMessageId)")
            
            query.conversationHistory(currentConversation.id, lastMessageId: lastMessageId!) { (history:[ModelMessage]) -> () in

                if !history.isEmpty {
                    
                    CATransaction.begin()
                    CATransaction.setDisableActions(true)
                    
                    let oldBottomOffset = self.collectionView.contentSize.height - self.collectionView.contentOffset.y
                    
                    self.collectionView.performBatchUpdates({
                        // indexPaths for earlier messages
                        let lastIdx = history.count - 1
                        //let lastIdx = self.messagesCountIndexes
                        var indexPaths: [AnyObject] = []
                        for i in 0...lastIdx {
                            indexPaths.append(NSIndexPath(forItem: i, inSection: 0))
                        }
                        
                        //Convert the messages in modelbridge
                        var messages = [ModelMessageBridge]()
                        
                        for message in history {
                            messages.append(ModelMessageBridge(message: message))
                        }
                        
                        // insert messages and update data source.
                        
                        for message in messages {
                            self.JSQmessages.insert(message, atIndex: 0)
                        }
                        self.collectionView.insertItemsAtIndexPaths(indexPaths)
                        
                        // invalidate layout
                        self.collectionView.collectionViewLayout.invalidateLayoutWithContext(JSQMessagesCollectionViewFlowLayoutInvalidationContext())
                        
                        println("*** inserted \(messages.count) messages")
                        }, completion: {(finished) in
                            
                            //scroll back to current position

                            self.finishReceivingMessageAnimated(false)
                            self.collectionView.layoutIfNeeded()
                            self.collectionView.contentOffset = CGPointMake(0, self.collectionView.contentSize.height - oldBottomOffset)
                            CATransaction.commit()
                            
                            self.showLoadEarlierMessagesHeader = true
                    })
                    
                }
                else {
                    println("No more messages to load.")
                }
            }
            
        }
        
    }

 

Infinite scrolling in JSQMessages in swift

To implement infinite scrolling in JSQMessages I used SVPullToRefresh “top-infinitescrolling” branch .

To install it, just drop

pod 'SVPullToRefresh', :git => 'https://github.com/gabro/SVPullToRefresh.git', :branch => 'top-infinitescrolling'

in your podfile,  run pod install, and add these lines in your bridging header:

#import <SVProgressHUD/SVProgressHUD.h>
#import <SVPullToRefresh/SVPullToRefresh.h>

Now we are all set, and we can implements addInfiniteScrollingWithActionHandler  in viewDidLoad() :

collectionView.addInfiniteScrollingWithActionHandler( { () -> Void in
    self.loadMore()   
}, direction: UInt(SVInfiniteScrollingDirectionTop) )

We create a method loadMore()  in the class, and put the code that was in didTapLoadEarlierMessagesButton. You might also want to disable the springiness during the process, as it looks a bit messed up. Here is the final code:

func loadMore() {
        println("Load earlier messages triggered by scroll!")
        if let msgArray = JSQmessages.ref(0) {
            
            collectionView.collectionViewLayout.springinessEnabled = false
            self.collectionView.infiniteScrollingView.startAnimating()
            
            let lastMessageId = JSQmessages[0].message.id
            
            query.conversationHistory(currentConversation.id, lastMessageId: lastMessageId!) { (history:[ModelMessage]) -> () in
                
                if !history.isEmpty {
                    
                    CATransaction.begin()
                    CATransaction.setDisableActions(true)
                    
                    let oldBottomOffset = self.collectionView.contentSize.height - self.collectionView.contentOffset.y
                    
                    self.collectionView.performBatchUpdates({
                        // indexPaths for earlier messages
                        let lastIdx = history.count - 1
                        var indexPaths: [AnyObject] = []
                        for i in 0...lastIdx {
                            indexPaths.append(NSIndexPath(forItem: i, inSection: 0))
                        }
                        
                        //Convert the messages in modelbridge
                        var messages = [ModelMessageBridge]()
                        for message in history {
                            messages.append(ModelMessageBridge(message: message))
                        }
                        
                        // insert messages and update data source.
                        for message in messages {
                            self.JSQmessages.insert(message, atIndex: 0)
                        }
                        self.collectionView.insertItemsAtIndexPaths(indexPaths)
                        
                        // invalidate layout
                        self.collectionView.collectionViewLayout.invalidateLayoutWithContext(JSQMessagesCollectionViewFlowLayoutInvalidationContext())
                        
                        }, completion: {(finished) in
                            
                            //scroll back to current position
                            self.finishReceivingMessageAnimated(false)
                            self.collectionView.layoutIfNeeded()
                            self.collectionView.contentOffset = CGPointMake(0, self.collectionView.contentSize.height - oldBottomOffset)
                            CATransaction.commit()
                            
                            self.collectionView.infiniteScrollingView.stopAnimating()
                            
                            self.collectionView.collectionViewLayout.springinessEnabled = true
                    })
                }
                else {
                    self.collectionView.infiniteScrollingView.stopAnimating()
                    self.collectionView.collectionViewLayout.springinessEnabled = true
                    println("No more messages to load.")
                }
            }
            
        }
    }

 

I hope this helps, I didn’t comment it too much since it’s quite self explanatory, but don’t hesitate to drop me a line in case you have any questions.