質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

1280閲覧

【Swift】スクロールするたびにCollectionViewのセルの順序が変わる

nekokichi

総合スコア54

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2019/02/04 11:14

編集2019/02/04 11:18

メルカリのような通販アプリを作っていて、出品した商品を画像付きでタイムライン形式で表示する画面を作っています。

イメージ説明

別画面でFirebaseにデータを保存して、ホーム画面でデータを取得し、取得した順に表示したいのですが..。

今直面している問題は、スクロールして隠れたセルの画像(配置?)がなぜか入れ替わっていることです。

イメージ説明

データを格納する配列を出力しましたが、

  • スクロール中に同じデータを新たに取得してはいない
  • 配列内の順序が変わってはもいない

という結果で原因がわかりません。

UICollectionViewの元々の仕様なのか、それとも単に隠れたセルは読み込む前の状態に戻ってしまうからなのか、何が問題なのでしょうか?

どうか、ご回答よろしくお願いします。

Swift

1import UIKit 2import Firebase 3import FirebaseUI 4import SDWebImage 5 6class Home: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource { 7 8 @IBOutlet weak var collectionView: UICollectionView! 9 10 //Firebaseから取得したItemIDを格納する 11 var photos = [String]() 12 //cellForItemAtが実行されたカウント 13 var count_cellfunc = 0 14 15 override func viewDidLoad() { 16 super.viewDidLoad() 17 18 collectionView.delegate = self 19 collectionView.dataSource = self 20 downloadImageData() 21 } 22 23 func numberOfSections(in collectionView: UICollectionView) -> Int { 24 return 1 25 } 26 27 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 28 return photos.count 29 } 30 31 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 32 count_cellfunc += 1 33 let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) 34 if count_cellfunc >= photos.count { 35 return cell 36 } 37 //imageViewを宣言 38 let imageView = cell.contentView.viewWithTag(1) as! UIImageView 39 let storageref = Storage.storage().reference(forURL: "gs://bookshare-b78b4.appspot.com").child("userID").child("Item").child(photos[indexPath.row]) 40 imageView.sd_setImage(with: storageref) 41 return cell 42 } 43 44 //FirebaseからItemのURLを取得する 45 func downloadImageData() { 46 let ref = Database.database().reference(fromURL: "https://bookshare-b78b4.firebaseio.com/") 47 self.photos = [String]() 48 ref.child("Item").observe(.value) { (snap) in 49 for item in snap.children { 50 let snapdata = item as! DataSnapshot 51 //1つのデータ 52 print(snapdata) 53 let item = snapdata.value as! [[String:String]] 54 self.photos.append(item[0]["ItemID"]!) 55 print(self.photos) 56 } 57 self.collectionView.reloadData() 58 } 59 } 60 61}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

ソース拝見したところ、UICollectionViewにおけるセルの再利用のされ方に、やや誤解があるように見えます。

cellForItem(at:)のメソッド内の処理について
dequeueReusableCell(withReuseIdentifier:for:)メソッドで生成(or再利用)されたセルを取得・返却していますが、再利用される際には同じindexPathのセルが再利用されるわけではありません。

例えば、一度下にスクロールしてセルが表示された後に、上にスクロールしなおした場合、画面外に出た下のセルが新しく上から表示されるセルに再利用されたりします。

提示していただいたソースの以下の箇所で、cellの中身を更新せずに再利用されたセルを返却しているので、count_cellfuncの値がphotos.countを上回った段階から再利用されたセルがそのまま表示されることになります。

※そのため、上にスクロースしたときに、画面外に出ていったものがまた上から出てきたり、順番がおかしくなったり、みたいな現象が発生してるものと思われます。

swift

1func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 2 count_cellfunc += 1 3 let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) 4 /* ------ ここから ------ */ 5 if count_cellfunc >= photos.count { 6 /* 中身を更新せずにcellを返却してしまっている。 */ 7 return cell 8 } 9 /* ------ ここまで ------ */ 10 /*中略*/ 11}

簡単な対応としては、上記のcellを返却しているif文の箇所をコメントアウトすれば、毎回cellの画像がindexPathに応じたものにセットし直されることになるので想定している挙動になるかと思います。


補足

swift

1let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)

上記の処理でCollectionViewから"cell"というIDの付いたセルを取得しています。
この時、CollectionViewは同じIDを持った再利用可能なセルが有ればそれを取得し、
再利用可能なものがなければ新しく生成したセルを返却してきます。

再利用可能かどうかは、基本的に画面に表示中かどうかで判断されているようなので、
画面外に出たセルが再利用される形になります。
※ちなみに、画面に表示中の有効なセルはCollectionViewのvisibleCellsという配列に格納されています。

前述にも軽く書きましたが、dequeueReusableCell(withReuseIdentifier:for:)メソッドの引数のIDが同じものの中でセルの再利用が行われます。
極端な話、すべてのセルで異なるIDを使えば再利用が行われず、indexPathでユニークにすることも可能です。。
※ただし、すべてのセルがメモリ上にキャッシュされることになってしまうので、推奨される方法ではありません。
(あと、事前にセルのIDを決めて準備しておかないといけないので、面倒です。)

作ったセルは再利用されるものと思って実装しておいたほうが、今回の様な現象を見た時に対処しやすいと思います。

投稿2019/02/05 01:41

編集2019/02/05 08:58
fumiaki

総合スコア56

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nekokichi

2019/02/05 08:00

ご指摘のあったコードをコメントアウトしたら解決しました。 1つ質問があるのですが、 ・"return cell"は既に使用したセル(いずれかの画像がセットされた)を返却している という解釈でよろしいでしょうか? 1度作られたセルは固定されず、取得・返却のプロセスを必ず踏むという理解で構いませんか?
fumiaki

2019/02/05 08:58

return cellが必ずしも既に使用したセルを返却している、とは限らないですが元のソースではcount_cellfuncで判定を行っていたので、ほぼ再利用されたものが表示されていたと思います。 ※セルの再利用については回答に補足を追記しましたのでそちらも参照ください。 基本的には作られたセルは再利用される前提で考えておくのが良いと思います。
nekokichi

2019/02/06 03:32

追加の質問にも答えていただきありがとうございます!! 基礎的な内容なのに理解していなかった自分が情けないです..。 しっかり覚えておきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問