我的UICollectionView无法使用Swift平滑滚动

我的UICollectionView无法使用Swift平滑滚动,swift,uicollectionview,uicollectionviewcell,Swift,Uicollectionview,Uicollectionviewcell,我有一个CollectionView,它根据消息类型(如文本、图像)将单元格排成队列 我遇到的问题是,当我向上/向下滚动时,滚动非常不平稳,因此不是很好的用户体验。这只会在第一次加载单元格时发生,之后滚动是平滑的 有什么办法可以解决这个问题吗?在显示单元格之前获取数据所需的时间是否会有问题? 我不太熟悉在后台线程上运行任务等,也不确定可以做哪些更改来完善数据预处理/获取等。。请帮忙 当视图加载时,Gif显示向上滚动,当我尝试向上滚动时,它显示单元格/视图呈波浪状 这是我的func loadCo

我有一个
CollectionView
,它根据
消息
类型(如文本、图像)将单元格排成队列

我遇到的问题是,当我向上/向下滚动时,滚动非常不平稳,因此不是很好的用户体验。这只会在第一次加载单元格时发生,之后滚动是平滑的

有什么办法可以解决这个问题吗?在显示单元格之前获取数据所需的时间是否会有问题?

我不太熟悉在后台线程上运行任务等,也不确定可以做哪些更改来完善数据预处理/获取等。。请帮忙

当视图加载时,Gif显示向上滚动,当我尝试向上滚动时,它显示单元格/视图呈波浪状

这是我的
func loadConversation()
,它加载
消息
数组

func loadConversation(){

        DataService.run.observeUsersMessagesFor(forUserId: chatPartnerId!) { (chatLog) in
            self.messages = chatLog
            DispatchQueue.main.async {
                self.collectionView.reloadData()

                if self.messages.count > 0 {
                    let indexPath = IndexPath(item: self.messages.count - 1, section: 0)

                    self.collectionView.scrollToItem(at: indexPath, at: .bottom , animated: false)

                }
            }
        }//observeUsersMessagesFor

    }//end func
这是我的CellForItem,在这里将单元格排成队列

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> 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
这是我的
ConversationCell
类,它配置了一个自定义单元格,以便通过
cellForItemAt
退出队列(注意:另外还有另一个
ConversationCellImage
自定义单元格类,用于配置图像消息):

时间剖面结果:

编辑:Flowlayout代码

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
编辑:
preferredlayouttributesfitting
我的自定义单元格类中的函数

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> 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
}
解决方案

extension ConversationVC: UICollectionViewDelegateFlowLayout{

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> 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) -> 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

始终确保不应在主线程上下载图像或GIF等数据

这就是为什么您的滚动不平滑的原因。使用GCD或NSOperation队列在后台的单独线程中下载数据。然后始终在主线程上显示下载的图像

使用AlamoFireImagePods,它将在后台自动处理下载的任务

import AlamofireImage

extension UIImageView {

func downloadImage(imageURL: String?, placeholderImage: UIImage? = nil) {
    if let imageurl = imageURL {
        self.af_setImage(withURL: NSURL(string: imageurl)! as URL, placeholderImage: placeholderImage) { (imageResult) in
            if let img = imageResult.result.value {
                self.image = img.resizeImageWith(newSize: self.frame.size)
                self.contentMode = .scaleAspectFill
            }
        }
    } else {
        self.image = placeholderImage
    }
}

}

首先,让我们试着找到出现此问题的确切位置

尝试1:

评论这行

//self.chatPartnerProfileImg.sd_setImage(with: url, placeholderImage:  #imageLiteral(resourceName: "placeholder"), options: [.continueInBackground, .progressiveDownload], completed: nil)
然后运行应用程序查看结果

DispatchQueue.main.async {
     self.chatPartnerProfileImg.sd_setImage(with: url, placeholderImage:  #imageLiteral(resourceName: "placeholder"), options: [.continueInBackground, .progressiveDownload], completed: nil)
}
尝试2:

将该行放入async块以查看结果

DispatchQueue.main.async {
     self.chatPartnerProfileImg.sd_setImage(with: url, placeholderImage:  #imageLiteral(resourceName: "placeholder"), options: [.continueInBackground, .progressiveDownload], completed: nil)
}
尝试3:注释设置角半径的代码

/*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*/
分享您的第1次、第2次和第3次尝试的结果,然后我们可以更好地了解问题所在


希望通过这种方式,我们可以得到闪烁背后的原因

我认为您看到的不规则是因为单元格给定了一个大小,然后覆盖了给定的大小,从而导致布局发生变化。您需要做的是在第一次到第二次创建布局的过程中进行计算

我有一个函数,我用过,像这样

func height(forWidth width: CGFloat) -> CGFloat {
    // do the height calculation here
}
然后,布局将使用此选项首次确定正确的大小,而无需更改它

您可以将其作为单元格或数据上的静态方法。。。或者别的什么

它需要做的是创建一个单元格(不是出列,只是创建一个单元格),然后填充其中的数据。然后做一些调整大小的事情。然后在进行第一次布局传递时,在布局中使用该高度

你的是起伏的,因为集合以20的高度(例如)布置单元格,然后计算所有需要基于该高度的位置。。。然后你去。。。“事实上,这应该是38”,现在你给它一个不同的高度,这个系列必须移动所有的东西。然后,每个细胞都会发生这种情况,从而导致波动

如果我能看到你的布局代码,我可能会帮上更多的忙

编辑

您应该实现委托方法
func collectionView(\uCollectionView:UICollectionView,layout collectionViewLayout:UICollectionViewLayout:UICollectionViewLayout,sizeForItemAt indexPath:indexPath)->CGSize,而不是使用该方法

像这样做

func height(forWidth width: CGFloat) -> CGFloat {
    // do the height calculation here
}
此方法进入视图控制器

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let message = messages[indexPath.item]

    let height: CGFloat

    if let url = message.imageURL {
        height = // whatever height you want for the images
    } else {
        height = // whatever height you want for the text
    }

    return CGSize(width: collectionView.frame.width - 40, height: height)
}
你可能需要对此进行补充,但这会给你一个想法

一旦你做到了这一点。。。从单元格中删除所有代码以更改帧和属性等


还有。。。将您的影子代码放入
awakeFromNib
方法。

谢谢您的回答,我对在后台运行任务与在主线程中运行任务不太熟悉。你能详细说明吗?或者显示需要对我的代码进行哪些更改?我使用SDWebImage异步加载我的图像,我相信它使用GCD和ARC(根据他们的GitHub页面),区别是什么?图像缓存,AlamofireImage是通过图像缓存自己完成的。您的单元格中是否有任何api调用?我已将代码粘贴到我的自定义
ConversationCell
类中。在我的原始问题上,唯一的调用是通过
sd_setImage
加载图像,但即使没有要加载的图像,也会发生这种情况。我还有
loadConversation()
函数,它通过一个名为profile your app的单例类方法使用时间分析器从Firebase获取数据。它将显示哪些代码需要很长时间。在一些奇怪的地方有很多代码。就像设置你的影子。。。应该是从nib唤醒,而不是布局子视图等。。。“切换自动布局”看起来很诡异。。。你为什么这么做?我第一次在任何应用程序上运行时间分析器,我应该寻找什么?如何分享我的结果?@Fogmeister我已经编辑了我的问题,包括我的时间档案结果的屏幕截图,我可以看到加载聊天的自定义单元格是一些最大的罪魁祸首。我只是不知道如何用它来解决我的问题?我已经尝试了你的三个建议,我得到了同样的问题。奇怪的是,一旦单元格加载完毕,滚动就会变得平滑。我已经能够指出是什么导致了这种波动。我从代码中注释掉了我所有的
collectioViewFlowLayout
,现在它可以平滑地滚动。但现在我需要弄清楚如何将单元格自动调整大小添加到我的ap中