title: My UICollectionView does not scroll smoothly using Swift
body: <p>I have a <code>CollectionView</code> which dequeues a cell depending on the <code>message</code> type (eg; text, image).</p>
  
  <p>The problem I am having is that when I scroll up/down <strong>the scroll is really choppy and thus not a very good user experience.</strong> This only happens the first time the cells are loaded, after that the scrolling is smooth.</p>
  
  <p><strong>Any ideas how I can fix this?, could this be an issue with the time its taking to fetch data before the cell is displayed?</strong></p>
  
  <p>I am not too familiar with running tasks on background threads etc. and not sure what changes I can make to prefect the data pre/fetching etc.. please help!</p>
  
  <p>The Gif shows scroll up when the view loads, it shows the cells/view being choppy as I attempt to scroll up.</p>
  
  <p><a href="https://i.stack.imgur.com/3Cf5H.gif" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/3Cf5H.gif" alt="enter image description here"></a></p>
  
  <p>This is my <code>func loadConversation()</code> which loads the <code>messages</code> array</p>
  
  <pre><code>func loadConversation(){
  
          DataService.run.observeUsersMessagesFor(forUserId: chatPartnerId!) { (chatLog) in
              self.messages = chatLog
              DispatchQueue.main.async {
                  self.collectionView.reloadData()
  
                  if self.messages.count &gt; 0 {
                      let indexPath = IndexPath(item: self.messages.count - 1, section: 0)
  
                      self.collectionView.scrollToItem(at: indexPath, at: .bottom , animated: false)
  
                  }
              }
          }//observeUsersMessagesFor
  
      }//end func
  </code></pre>
  
  <p>This is my cellForItemAt which dequeues cells</p>
  
  <pre><code>func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -&gt; UICollectionViewCell {
  
          let message = messages[indexPath.item]
  
          let uid = Auth.auth().currentUser?.uid
  
  
          if message.fromId == uid {
  
              if message.imageUrl != nil {
                  let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ConversationCellImage", for: indexPath) as! ConversationCellImage
                  cell.configureCell(message: message)
                  return cell
  
              } else {
                  let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ConversationCellSender", for: indexPath) as! ConversationCellSender
                  cell.configureCell(message: message)
                  return cell
  
              }//end if message.imageUrl != nil
  
  
          } else {
  
              if message.imageUrl != nil {
                  let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ConversationCellImageSender", for: indexPath) as! ConversationCellImageSender
                  cell.configureCell(message: message)
                  return cell
  
              } else {
  
              let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ConversationCell", for: indexPath) as! ConversationCell
              cell.configureCell(message: message)
              return cell
  
              }
  
          }//end if uid 
  
      }//end func
  </code></pre>
  
  <p>This is my <code>ConversationCell</code> class which configures a custom cell for dequeueing by <code>cellForItemAt</code> (note: in addition there another <code>ConversationCellImage</code> custom cell class which configures an image message):</p>
  
  <pre><code>class ConversationCell: UICollectionViewCell {
  
      @IBOutlet weak var chatPartnerProfileImg: CircleImage!
      @IBOutlet weak var messageLbl: UILabel!
  
      override func awakeFromNib() {
          super.awakeFromNib()
  
  
          chatPartnerProfileImg.isHidden = false
  
      }//end func
  
      func configureCell(message: Message){
  
          messageLbl.text = message.message
  
          let partnerId = message.chatPartnerId()
  
  
          DataService.run.getUserInfo(forUserId: partnerId!) { (user) in
              let url = URL(string: user.profilePictureURL)
              self.chatPartnerProfileImg.sd_setImage(with: url, placeholderImage:  #imageLiteral(resourceName: "placeholder"), options: [.continueInBackground, .progressiveDownload], completed: nil)
  
          }//end getUserInfo
  
  
      }//end func
  
  
      override func layoutSubviews() {
          super.layoutSubviews()
  
          self.layer.cornerRadius = 10.0
          self.layer.shadowRadius = 5.0
          self.layer.shadowOpacity = 0.3
          self.layer.shadowOffset = CGSize(width: 5.0, height: 10.0)
          self.clipsToBounds = false
  
      }//end func
  
      override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -&gt; UICollectionViewLayoutAttributes {
  
  //toggles auto-layout
          setNeedsLayout()
          layoutIfNeeded()
  
          //Tries to fit contentView to the target size in layoutAttributes
          let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
  
          //Update layoutAttributes with height that was just calculated
          var frame = layoutAttributes.frame
          frame.size.height = ceil(size.height) + 18
          layoutAttributes.frame = frame
          return layoutAttributes
      }
  
  }//end class
  </code></pre>
  
  <p>Time Profile results:</p>
  
  <p><a href="https://i.stack.imgur.com/CEEus.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/CEEus.png" alt="enter image description here"></a></p>
  
  <p>Edit: Flowlayout code</p>
  
  <pre><code>if let flowLayout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout,
      let collectionView = collectionView {
      let w = collectionView.frame.width - 40
      flowLayout.estimatedItemSize = CGSize(width: w, height: 200)
  }// end if-let
  </code></pre>
  
  <p>Edit: <code>preferredLayoutAttributesFitting</code> function in my custom cell class</p>
  
  <pre><code>override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -&gt; UICollectionViewLayoutAttributes {
      //toggles auto-layout
      setNeedsLayout()
      layoutIfNeeded()
  
      //Tries to fit contentView to the target size in layoutAttributes
      let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
  
      //Update layoutAttributes with height that was just calculated
      var frame = layoutAttributes.frame
      frame.size.height = ceil(size.height) + 18
      layoutAttributes.frame = frame
      return layoutAttributes
  }
  </code></pre>
  
  <p><strong>SOLUTION</strong>:</p>
  
  <pre><code>extension ConversationVC: UICollectionViewDelegateFlowLayout{
  
      func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -&gt; CGSize {
  
          var height: CGFloat = 80
  
          let message = messages[indexPath.item]
  
          if let text = message.message {
  
              height = estimateFrameForText(text).height + 20
  
          } else if let imageWidth = message.imageWidth?.floatValue, let imageHeight = message.imageHeight?.floatValue{
  
              height = CGFloat(imageHeight / imageWidth * 200)
  
          }
  
          let width = collectionView.frame.width - 40
  
          return CGSize(width: width, height: height)
      }
  
      fileprivate func estimateFrameForText(_ text: String) -&gt; CGRect {
          let size = CGSize(width: 200, height: 1000)
          let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
          return NSString(string: text).boundingRect(with: size, options: options, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16)], context: nil)
  
      }
  
  }//end extension
  </code></pre>