0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Swift:FirestoreとcollectionViewを使った投稿一覧の取得・更新・削除処理

Last updated at Posted at 2021-01-31

FirestoreでSNS的なユーザー投稿型のシステムを作る場合、以下のような仕様が必要だと思う。

  • 最初に投稿一覧を降順(日付が新しい順)で表示する
  • 投稿されたらホーム一覧を自動更新する(理想は自分の投稿完了じだけ自動更新)
  • 投稿を削除したら投稿一覧からも自動で削除する
  • いいねやコメントなどのFirestoreのフィールドバリューの更新ではリロードさせない

ezgif-7-51a193c5d13c.gif

よりよりライブラリなどあると思うが基本的なCollectionViewの仕組みだけでやってみた。
具体的には投稿の取得/削除をsnapshot.documentChangescollectionView.insertItems(at:)/collectionView.deleteItems(at:)を使って行う。

コード

import FirebaseFirestore

class ViewController: UIViewController {

 @IBOutlet weak var collectionView: UICollectionView!
 var stories: [Story] = []
 let db = Firestore.firestore()
 var storiesListener: ListenerRegistration?

 override func viewDidLoad() {
        super.viewDidLoad()
		self.setStoriesListener()
    }

func setStoriesListener() {
		self.storiesListener = self.db.collection("stories")
			.order(by: "createTime", descending: true)  // 降順で(日付が新しい順から)取ってくる
			.addSnapshotListener({ (querySnapshot, error) in
				guard let snapshot = querySnapshot else {
					print("Error while getting collection: \(error)")
					return
				}
				snapshot.documentChanges.forEach { diff in
					if (diff.type == .added) {
						let story = Story(document: diff.document)
						let newIndex = Int(diff.newIndex)
						self.stories.insert(story, at: newIndex) // 投稿を格納している配列のindexとFirestore上のindexを揃えてやる
						self.collectionView.insertItems(at: [IndexPath(item: newIndex, section: 0)])
					}
					if (diff.type == .modified) {
						// いいねやコメントなどのフィールドバリューの更新はここが呼ばれる
                        // リアルタイムでいいね数やコメント数の変化を反映したい場合は collectionView.reloadItems(at:)を使うはず
					}
					if (diff.type == .removed) {
						let oldIndex = Int(diff.oldIndex)
						self.stories.remove(at: oldIndex)
						self.collectionView.deleteItems(at: [IndexPath(item: oldIndex, section: 0)])
					}
				}
				
			})


extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate {
	
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.stories.count
   }
   
	func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
		let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! StoryCell
		let story = self.stories[indexPath.row]	
        // cellに画像やラベルテキストを設定する処理			
		return cell
	}		
}

ちなみにcreateTimeでサーバータイムスタンプを使っていたりすると.addedのあとに.modefiedがすぐに呼ばれることに注意。

リスナーをセットするタイミング

上記ではviewDidLoad()でリスナーをセットしていて、Firebaseドキュメントなどで見られるviewWillAppearではない。これはホームタブを移動して帰ってきたときに都度、再取得&描画処理が走るのを防ぎたいからだ。deinitはホームタブの遷移では呼ばれないので他の画面にいってもずっと関しをつづけることができる。

viewWillApperでリスナーをセットしてviewWillDesaperでstoriesListener?.remove()を呼ばないようにするとタブで切り替えて戻ってくるたびにリスナーが重複セットされるのが原因か、やっぱり都度リロードが走ってしまう。

誰か適切なやり方をしっていたら教えてほしいです🥺

reloadData()とinsertItem(at: Index)の違い

reloadDataを呼ぶとcollectionView(_:cellForItemAt:)がcollectionView(_:numberOfItemsInSection)`で返された回数分、0から順番に呼ばれてCellが生成される。

一方でinsertItems(at: Index)を呼ぶと、atで指定したIndexだけで都度collectionView(_:cellForItemAt)が呼ばれる。

reloadData()じゃなんで駄目か

addSnapshotListenerで常に監視をしていれば何かしらの変更は全部拾える。しかしそれでは、1つ投稿があったりいいねやコメントがあるたびにすべてのCellが再リロードされて投稿一覧画面がチカチカしてみれたものじゃなくなる。

かといってリッスンをせずにgetDocumentsで1回だけ取得すると、今度は投稿後や削除に更新が走らずユーザーが不安になる(たいていその作業は別画面で行うはずなので、その作業のcompletionでルートのViewから辿ってホーム画面のgetPostのようなfunctionを呼び出したり、collectionViewを更新したりは難しくて複雑そうだった)。

なのに多くの文献ではこういったやり方がほとんどなくて苦戦した。

参考にした記事

insertやdeleteを用いたきれいな更新は、iOSやFirestoreなどで神記事を量産している @mono さんの以下記事が参考になった。
https://gist.github.com/mono0926/e27131e8e6e1cf27a0dc4b655a240350

newIndexやoldIndexの正確な理解はFirebase公式ドキュメントへ(Firebase Firestore と Cloud Firestore って別物だっけ‥?)
https://firebase.google.com/docs/reference/swift/firebasefirestore/api/reference/Classes/DocumentChange

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?