UICollectionView异步加载数据后UI未稳定更新问题求助
问题:异步加载媒体资源后UICollectionView无法稳定更新占位符
我正在开发一款消息应用,从服务器获取消息及关联媒体资源,使用UICollectionView展示消息,初始显示占位符,待实际数据加载完成后替换。
通过多种异步调用获取数据(如KingfisherManager加载图片、FileStorage处理音视频文件、AppleMusicAPI获取音乐曲目),数据获取完成后尝试重载对应section更新单元格,但单元格并非总能更新为已加载的数据,而是持续显示占位符。
核心数据获取与UI更新代码
func createMessage(messageDictionary: [String: Any], completion: @escaping (MKMessage?) -> Void) { let message = Message(dictionary: messageDictionary) let mkMessage = MKMessage(message: message) if message.type == kSPOTIFYLINK { print("spotify link") let placeholderSpotifyURL = URL(string: "") ?? URL(fileURLWithPath: "") let placeholderTrack = SpotifyTrackItem(title: "", artists: [""], album: "", artwork: nil, trackURLPreview: placeholderSpotifyURL, spotifyURL: placeholderSpotifyURL, placeholder: true) mkMessage.kind = .custom(placeholderTrack) // 先检查缓存 if let cachedTrack = SpotifyCache.object(forKey: message.spotifyLink as NSString) { mkMessage.kind = .custom(cachedTrack) if let section = mkMessage.section { print(section) let indexSet = IndexSet(integer: section) DispatchQueue.main.async { self.messageCollectionView.messagesCollectionView.reloadSections(indexSet) } } } else { spotifyMusicApi.fetchSpotifySongInfo(from: message.spotifyLink) { [weak self] trackItem, error in if let trackItem = trackItem { print(trackItem) // 存入缓存 self?.SpotifyCache.setObject(trackItem, forKey: message.spotifyLink as NSString) mkMessage.kind = .custom(trackItem) if let section = mkMessage.section { print(section) let indexSet = IndexSet(integer: section) DispatchQueue.main.async { self?.messageCollectionView.messagesCollectionView.reloadSections(indexSet) } } } else if let error = error { print(error) } } } } // 其他类型数据的异步调用逻辑... }
分页加载旧消息逻辑
override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { if refreshController.isRefreshing { getOldMessagesInBackground() } } private func getOldMessagesInBackground() { var query: Query = FirebaseReference(.Messages).document(FUser.currentId()).collection(chatId) .order(by: kDATE, descending: true) .limit(to: 10) if let lastSnapshot = lastDocumentSnapshot { query = query.start(afterDocument: lastSnapshot) } query.getDocuments { (snapshot, error) in guard let snapshot = snapshot else { return } if snapshot.documents.isEmpty { print("no more messages") self.messagesCollectionView.refreshControl = nil self.areAllMessagesLoaded = true return } let newMessages = ((self.dictionaryArrayFromSnapshot(snapshot.documents)) as NSArray).sortedArray(using: [NSSortDescriptor(key: kDATE, ascending: false)]) as! [Dictionary<String, Any>] self.loadedMessageDictionaries = newMessages + self.loadedMessageDictionaries self.lastDocumentSnapshot = snapshot.documents.last DispatchQueue.main.async { self.maxMessageNumber = self.loadedMessageDictionaries.count - self.displayingMessagesCount - 1 self.minMessageNumber = self.maxMessageNumber - kNUMBEROFMESSAGES self.insertOldMessages(newMessages) self.messagesCollectionView.reloadDataAndKeepOffset() self.refreshController.endRefreshing() } } } private func insertOldMessage(_ messageDictionary: Dictionary<String, Any>) { let incoming = IncomingMessage(collectionView_: self) incoming.createMessage(messageDictionary: messageDictionary) { [weak self] message in guard let self = self, let message = message else { return } message.section = 0 if !self.mkmessages.contains(where: { $0.messageId == message.messageId }) { self.mkmessages.insert(message, at: 0) } } } private func insertOldMessages(_ messages: [Dictionary<String, Any>]) { for messageDictionary in messages { insertOldMessage(messageDictionary) displayingMessagesCount += 1 } }
初始消息加载逻辑
private func downloadChats() { DispatchQueue.global(qos: .background).async { FirebaseReference(.Messages).document(FUser.currentId()).collection(self.chatId).limit(to: 10).order(by: kDATE, descending: true).getDocuments { (snapshot, error) in guard let snapshot = snapshot else { self.initialLoadCompleted = true return } self.loadedMessageDictionaries = ((self.dictionaryArrayFromSnapshot(snapshot.documents)) as NSArray).sortedArray(using: [NSSortDescriptor(key: kDATE, ascending: true)]) as! [Dictionary<String, Any>] self.lastDocumentSnapshot = snapshot.documents.last if let lastMessageDate = (self.lastDocumentSnapshot?[kDATE] as? Timestamp)?.dateValue() { if lastMessageDate > self.latestMessageDate { self.latestMessageDate = lastMessageDate } } DispatchQueue.main.async { self.insertMessages() self.initialLoadCompleted = true self.listenForNewChats() DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.messagesCollectionView.reloadData() self.messagesCollectionView.scrollToLastItem(animated: false) } } } } } private func insertMessages() { maxMessageNumber = loadedMessageDictionaries.count - displayingMessagesCount minMessageNumber = maxMessageNumber - kNUMBEROFMESSAGES if minMessageNumber < 0 { minMessageNumber = 0 } for i in (minMessageNumber ..< maxMessageNumber) { let messageDictionary = loadedMessageDictionaries[i] insertMessage(messageDictionary) displayingMessagesCount += 1 } if displayingMessagesCount <= loadedMessageDictionaries.count { if messagesCollectionView.refreshControl == nil { messagesCollectionView.refreshControl = refreshController self.areAllMessagesLoaded = false } } else { messagesCollectionView.refreshControl = nil } } private func insertMessage(_ messageDictionary: Dictionary<String, Any>) { let messageId = messageDictionary[kOBJECTID] as? String if let messageId = messageId, !mkmessages.contains(where: { $0.messageId == messageId }) { markMessageAsRead(messageDictionary) let incoming = IncomingMessage(collectionView_: self) incoming.createMessage(messageDictionary: messageDictionary) { [weak self] message in guard let self = self, let message = message else { return } self.mkmessages.append(message) let section = self.mkmessages.count - 1 message.section = section self.messagesCollectionView.performBatchUpdates({ self.messagesCollectionView.insertSections([self.mkmessages.count - 1]) if self.mkmessages.count >= 2 { self.messagesCollectionView.reloadSections([self.mkmessages.count - 2]) } }, completion: { [weak self] _ in self?.messagesCollectionView.reloadDataAndKeepOffset() }) } } }
推测的问题成因
- mkMessage的section属性错误:异步任务完成触发UI更新时,section值可能已因消息列表变化而改变,导致UI更新指向错误的单元格。
- 消息加载导致section索引偏移:重载单元格时,新消息插入或旧消息加载会改变消息列表的section位置,之前存储的section无法指向正确的单元格。
- 提前调用completion:设置占位符并启动异步操作后立即调用completion,导致消息在异步操作未完成时就被展示,后续数据加载完成后无法正确更新UI。
内容的提问来源于stack exchange,提问作者fun lab
相关产品推荐
相关产品推荐

