内容
collectionViewでチャットを実装しているのですが、
スクロールするとカクついてしまうので、スムーズにスクロールさせるための方法が知りたいです。
考えられる原因
チャットメッセージを表示するためのChatMessageCell
はUILabel
+AutoLayout
をつかっているため、
描画領域外のCellの高さを事前に計算できておらずスクロール時に描画計算するためスクロールがカクつくのではないかと考えています。
試したこと
UICollectionView
のisPrefetchingEnabled
をtrue
にしましたが、
改善はされませんでした。
ソース
ChatMessageCell
1class ChatMessageCell: UICollectionViewCell { 2 3 static let reuseIdentifier = "chat-message-cell-reuse-identifier" 4 5 let message = UILabel() 6 var messageLeadingAnchor: NSLayoutConstraint! = nil 7 var messageTrailingAnchor: NSLayoutConstraint! = nil 8 9 override init(frame: CGRect) { 10 super.init(frame: frame) 11 12 message.numberOfLines = 0 13 message.preferredMaxLayoutWidth = UIScreen.screenWidth * 0.7 14 contentView.addSubview(message) 15 16 message.translatesAutoresizingMaskIntoConstraints = false 17 message.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true 18 message.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true 19 messageLeadingAnchor = message.leadingAnchor.constraint(equalTo: contentView.leadingAnchor) 20 messageTrailingAnchor = message.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) 21 } 22 23 required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } 24 25 func applyOutcomingStyle() { 26 messageLeadingAnchor.isActive = false 27 messageTrailingAnchor.isActive = true 28 } 29 30 func applyIncomingStyle() { 31 messageLeadingAnchor.isActive = true 32 messageTrailingAnchor.isActive = false 33 } 34}
ChatMessageViewController
1 2import UIKit 3 4class ChatMessageViewController: UIViewController { 5 6 private var messages = [ChatThreadMessage]() 7 private var messageListener: ListenerRegistration? 8 9 enum Section { case main } 10 var messageCollectionDataSource: UICollectionViewDiffableDataSource<Section, ChatThreadMessage>! = nil 11 var currentMessageSnapShot = NSDiffableDataSourceSnapshot<Section, ChatThreadMessage>() 12 13 private let viewer: User 14 private let recipient: User 15 16 private let v: ChatMessageView 17 override func loadView() { view = v } 18 19 init(user: User, recipient: User) { 20 self.viewer = user 21 self.recipient = recipient 22 23 v = ChatMessageView() 24 25 super.init(nibName: nil, bundle: nil) 26 self.title = recipient.username 27 } 28 29 required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } 30 31 override func viewDidLoad() { 32 super.viewDidLoad() 33 34 configDataSource() 35 configFirestore() 36 } 37} 38 39// MARK: - Helpers 40 41extension ChatMessageViewController { 42 private func configDataSource() { 43 messageCollectionDataSource = UICollectionViewDiffableDataSource<Section, ChatThreadMessage> (collectionView: v.collectionView) { 44 [weak self] (collectionView: UICollectionView, indexPath: IndexPath, message: ChatThreadMessage) -> UICollectionViewCell? in 45 guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ChatMessageCell.reuseIdentifier, 46 for: indexPath) as? ChatMessageCell else { return nil } 47 cell.message.text = message.text 48 if message.senderId == self?.viewer.userId { 49 cell.applyOutcomingStyle() 50 } else { 51 cell.applyIncomingStyle() 52 } 53 return cell 54 } 55 currentMessageSnapShot.appendSections([.main]) 56 } 57 58 private func configFirestore() { 59 var shouldAnimateScrolling = false 60 61 let chatRef = hoge 62 self.messageListener = chatRef.addSnapshotListener {[weak self] querySnapshot, error in 63 if let error = error { 64 print("(error.localizedDescription)") 65 return 66 } 67 68 guard let self = self, let snapshot = querySnapshot else { return } 69 snapshot.documentChanges.forEach { change in 70 if change.type == .added, let messageModel = try? change.document.data(as: ChatMessageModel.self) { 71 let chatMessage = ChatThreadMessage(model: messageModel) 72 self.currentMessageSnapShot.appendItems([chatMessage]) 73 } 74 } 75 76 self.messageCollectionDataSource.apply(self.currentMessageSnapShot, animatingDifferences: false) { 77 [weak self] in 78 self?.scrollToBottom(animated: shouldAnimateScrolling) 79 shouldAnimateScrolling = true 80 } 81 } 82 } 83 84 private func scrollToBottom(animated: Bool = true) { 85 guard let bottomMessage = currentMessageSnapShot.itemIdentifiers.last, 86 let bottomIndexPath = messageCollectionDataSource.indexPath(for: bottomMessage) else { return } 87 88 self.v.collectionView.scrollToItem(at: bottomIndexPath, at: .bottom, animated: animated) 89 } 90} 91 92struct ChatThreadMessage: Hashable { 93 let text: String 94 let senderId: String 95 let senderUsername: String 96 let createdAt: Date 97 98 init(model: ChatMessageModel) { 99 text = model.content 100 senderId = model.senderId 101 senderUsername = model.senderUsername 102 createdAt = model.createdAt?.dateValue() ?? Date() 103 } 104}
ChatMessageView
1import UIKit 2 3class ChatMessageView: UIView { 4 5 var collectionView: UICollectionView! = nil 6 7 init() { 8 super.init(frame: .zero) 9 cofigHierarchy() 10 configConstraint() 11 } 12 13 required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } 14} 15 16extension NChatThreadView { 17 private func cofigHierarchy() { 18 collectionView = UICollectionView(frame: .zero, collectionViewLayout: createLayout()) 19 collectionView.isPrefetchingEnabled = true 20 21 collectionView.contentInset.top = 10 22 23 collectionView.register(ChatMessageCell.self, forCellWithReuseIdentifier: ChatMessageCell.reuseIdentifier) 24 addSubview(collectionView) 25 } 26 27 private func configConstraint() { 28 collectionView.translatesAutoresizingMaskIntoConstraints = false 29 NSLayoutConstraint.activate([ 30 collectionView.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor), 31 collectionView.leadingAnchor.constraint(equalTo: self.leadingAnchor), 32 collectionView.trailingAnchor.constraint(equalTo: self.trailingAnchor), 33 collectionView.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor) 34 ]) 35 } 36 37 private func createLayout() -> UICollectionViewLayout { 38 let sectionProvider = { (sectionIndex: Int, 39 layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in 40 41 let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), 42 heightDimension: .estimated(44)) 43 let item = NSCollectionLayoutItem(layoutSize: size) 44 let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitem: item, count: 1) 45 46 let section = NSCollectionLayoutSection(group: group) 47 section.orthogonalScrollingBehavior = .none 48 section.interGroupSpacing = 10 49 section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 15, 50 bottom: 0, trailing: 15) 51 return section 52 } 53 54 return UICollectionViewCompositionalLayout(sectionProvider: sectionProvider) 55 } 56} 57
あなたの回答
tips
プレビュー