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

时间:2018-08-10 01:49:22

标签: swift uicollectionview uicollectionviewcell

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

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

有什么想法可以解决此问题吗?这可能与在显示单元格之前获取数据的时间有关吗?

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

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

enter image description here

这是我的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

时间配置文件结果:

enter image description here

编辑:布局代码

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

3 个答案:

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

希望通过这种方式,我们可以了解闪烁的原因。

答案 1 :(得分:0)

始终确保不要将图像或GIF之类的数据下载到主线程上。

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

使用AlamofireImage窗格,它将自动在后台处理下载的任务。

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

}

答案 2 :(得分:0)

我认为您看到的断断续续是因为给了单元格一个大小,然后覆盖了它们给定的大小,这导致布局四处移动。您需要做的是在第一次创建布局期间进行计算。

我有一个像这样使用过的功能...

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

然后,布局可使用它来首次确定正确的尺寸,而不必更改它。

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

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

您之所以动摇,是因为该集合将高度为20的单元格布局(例如),然后根据该位置计算出一切所需的位置……然后您开始……“实际上,应该是38”现在您必须为集合赋予不同的高度,然后它才能移动所有东西。然后,每个单元格都会发生这种情况,因此会导致断断续续。

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

编辑

应该使用委托方法preferredAttributes来代替使用func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize方法。

做这样的事情...

此方法进入视图控制器。

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