【问题标题】:My UICollectionView does not scroll smoothly using Swift我的 UICollectionView 没有使用 Swift 平滑滚动
【发布时间】:2018-08-10 01:49:22
【问题描述】:

我有一个CollectionView,它根据message 类型(例如,文本、图像)使单元格出列。

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

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

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

当视图加载时 Gif 显示向上滚动,当我尝试向上滚动时,它显示单元格/视图不稳定。

这是我的 func loadConversation(),它加载了 messages 数组

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

这是我的 cellForItemAt 将单元格出列

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 自定义单元格类,它配置了一个图像消息):

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

时间概况结果:

编辑:流程布局代码

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

编辑:preferredLayoutAttributesFitting 我的自定义单元格类中的函数

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

【问题讨论】:

  • 您的单元格中是否有任何 api 调用??
  • 我已将代码粘贴到我原来的问题上面的自定义ConversationCell 类中,唯一的调用是通过sd_setImage 加载图像,但即使没有图像也会发生这种情况装载。我还有 loadConversation() 函数,它通过一个名为的单例类方法从 Firebase 获取数据
  • 使用时间分析器分析您的应用程序。它将显示哪个代码需要很长时间。你在奇怪的地方有很多代码。就像设置你的影子......应该从笔尖而不是布局子视图等中醒来......并且“切换自动布局”的东西看起来很狡猾......你为什么这样做?
  • 我第一次在任何应用程序上运行时间分析器,我应该寻找什么?如何分享我的结果?
  • @Fogmeister 我已经编辑了我的问题以包含我的时间配置文件结果的屏幕截图,我可以看到加载聊天的自定义单元格是一些最大的罪魁祸首。我只是不知道如何解决我的问题?

标签: swift uicollectionview uicollectionviewcell


【解决方案1】:

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

尝试1:

评论这一行

//self.chatPartnerProfileImg.sd_setImage(with: url, placeholderImage:  #imageLiteral(resourceName: "placeholder"), options: [.continueInBackground, .progressiveDownload], completed: nil)

然后运行您的应用程序以查看结果。

尝试 2:

将该行放在异步块中以查看结果。

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 的结果,然后我们可以更好地了解问题所在。

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

【讨论】:

  • 我已经尝试了您的所有三个建议,但我遇到了同样的问题。奇怪的是,一旦单元格加载完毕,滚动就很流畅了。
  • 我已经能够指出是什么导致了断断续续。我从我的代码中注释掉了我所有的collectioViewFlowLayout,它现在可以顺利滚动。但现在我需要弄清楚如何将单元格自动调整大小添加到我的应用程序中,以便单元格高度自动调整。
【解决方案2】:

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

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

使用 AlamofireImage pods,它会在后台自动处理下载的任务。

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

}

【讨论】:

  • 感谢您的回复,我对在后台和主线程中运行任务不太熟悉。你能详细说明吗?或者也许显示需要对我的代码进行哪些更改?
  • 我正在使用 SDWebImage 异步加载我的图像,我相信它使用 GCD 和 ARC(根据他们的 GitHub 页面),有什么区别?
  • 图片缓存,AlamofireImage是自己做图片缓存的。
【解决方案3】:

我认为您看到的不稳定是因为单元格被赋予了一个大小,然后覆盖了它们被赋予的大小,这导致布局发生了变化。您需要做的是在第一次创建布局期间进行计算。

我有一个这样用过的函数……

func height(forWidth width: CGFloat) -> CGFloat {
    // do the height calculation here
}

然后布局会使用它来确定正确的大小,而无需更改它。

您可以将其作为单元格或数据上的静态方法...或其他东西。

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

你的很不稳定,因为集合布置了高度为 20 的单元格(例如),然后计算一切需要基于此的位置......然后你去......“实际上,这应该是 38”既然你给了它一个不同的高度,这个集合必须移动所有的东西。然后每个细胞都会发生这种情况,因此会导致波动。

如果我能看到您的布局代码,我可能会提供更多帮助。

编辑

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

做这样的事情......

这个方法进入视图控制器。

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 方法中。

【讨论】:

  • 我已经在我的 VC 中添加了我的 UICollectionViewFlowLayout 代码和我的自定义单元格 class 中的 preferredLayoutAttributesFitting 代码
  • @Roggie 我更新了获得正确高度的更好方法。
  • 我现在已经用一些测试高度 CGFloat 值为 100 进行了建议的更改。单元格根本没有显示,我得到了不间断的控制台错误,例如:The behavior of the UICollectionViewFlowLayout is not defined because: 2018-08-10 21:56:18.142459+0930 vipeeps[12101:5937397] the item width must be less than the width of the UICollectionView minus the section insets left and right values, minus the content insets left and right values. 2018-08-10 21:56:18.142518+0930 vipeeps[12101:5937397] Please check the values returned by the delegate.
  • @Roggie 看看错误:D 单元格的宽度必须小于集合视图的宽度减去部分插图。所以我可能弄错了宽度。尝试对宽度使用不同的计算。您可以从传递给函数的collectionViewcollectionViewLayout 获取集合视图的宽度和大小部分的插图。我建议在开头使用guard let flowLayout = layout as? UICollectionVIewFloaLayout else { return .zero },然后使用 flowLayotu 中的部分插图。
  • 这对我来说是全新的,我正在努力解决它 :) 我的部分插图是故事板中的顶部:4,底部:4,左/右:0,所以我认为我的单元格是collectionView.frame.width - 40,它小于我的 collectionview 的宽度:/
猜你喜欢
  • 1970-01-01
  • 2015-05-26
  • 2018-02-02
  • 1970-01-01
  • 2016-08-07
  • 1970-01-01
  • 1970-01-01
  • 2011-02-12
  • 2018-07-27
相关资源
最近更新 更多