我的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中