うるおいらんど

【Swift】UICollectionViewで二重に表示されるのを防ぐ

SwiftUICollectionView

追記があります。

UICollectionViewを用いて画像を表示させていたのですが、スクロールすると以前の画像と重なって表示されるという事態に陥りました。

少しだけハマったけれどあまり時間もかからずに解決したのでよかったです。

 

原因としてはセルを再利用するときに再利用前の画像がそのまま表示されてしまっている、ということ。

 

addSubviewをするとデータが蓄積される

まず、collectionView:cellForItemAtIndexPath:のメソッド内でセルをカスタマイズします。セルを再利用するよ〜と宣言しておきます。

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
     let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell",forIndexPath:indexPath)
      //ここでセルをカスタマイズ
      return cell
}

 

そもそもここでセルをカスタマイズするのがあまりよろしくないらしいが、とりあえずそれは置いといて。

このセルの上にImageViewを配置してそこに画像を表示させます。

let imageView = UIImageView()
let img = UIImage(named:"image.jpg")! as UIImage
imageView.frame = CGRectMake(0,0,100,100)
imageView.image = img
cell.contentView.addSubview(imageView)

こんな感じ。

addSubviewをするときにcellに直接addSubviewもできますが、contentViewを挟んだほうが良いらしい。

そしてこのaddSubviewしたものはどんどん蓄積されていきます。二重に表示されるどころか、メモリ効率も悪くなってしまいます。

 

解決方法

addSubviewをする前にcellのsubviewを削除しておきます。

for subview in cell.contentView.subviews{
      subview.removeFromSuperview()
}

これをaddSubviewの前に書いておきます。そうすることでsubviewの中身を初期化しておくことができます。

 

ラベルとかも色々そこで書いていたのでとにかくカオスな状況になっていたのが解決してよかったです。

メソッド内のコードはこんな感じ。

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
     let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell",forIndexPath:indexPath)
     for subview in cell.contentView.subviews{
           subview.removeFromSuperview()
     }
      //ここでセルをカスタマイズ
     let imageView = UIImageView()
     let img = UIImage(named:"image.jpg")! as UIImage
     imageView.frame = CGRectMake(0,0,100,100)
     imageView.image = img
     cell.contentView.addSubview(imageView)
     return cell
}

 

参考にしているのがSwiftでもなければUICollectionViewでもないあたりがオモシロイ。笑

あとはデータの削除が終わればUICollectionViewに関してはほぼ完成です。頑張っていこー(´・ω・`)

参考リンク

Additional Notes追記

UICollectionViewCellのサブクラスを作ろう!

ついでにSwift4にも対応。

基本的にcollectionView(_:cellForItemAt:)内ではcellにaddSubviewしません。

UICollectionViewのサブクラスを適当に作って(CustomCellとしました)

import UIKit

class CustomCell: UICollectionViewCell {
    
    var label:UILabel!

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        //xibを使わない場合はこちらでセットアップ
        setup()
    }
    
    func setup() {
        label = UILabel(frame:self.contentView.frame)
        label.text = "らべる"
        self.contentView.addSubview(label)
    }
    
}

自分はxibを滅多に使わないのでこういう書き方してます。 んでこれをCollectionViewに登録

collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "cell")

そしたらあとはcollectionView(_:cellForItemAt:)内で

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell:CustomCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCell
        cell.label.text = "テキストだけを変える"
        
        return cell
    }

とすれば二重には表示されないはずです。

二重に表示される主な原因は、

  1. collectionView(_:cellForItemAt:)内でcellに直接addSubviewしている
  2. collectionView(_:cellForItemAt:)内で非同期処理をしている場合

の2点だと思います。

2番目は本当によく使うんですが、 cellが再利用された時点では非同期の読み込みが終わってなく、前のセルの情報が残っちゃうんですね。 なので

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell:CustomCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCell
        //二重に表示されないように再利用時にimageView.imageを消しておく
        cell.imageView.image = nil
        
        //非同期で画像イメージを読み込む
        DispatchQueue.global().async {
            //適当に読み込んだ image があるとする
            DispatchQueue.main.async {
                //データをセル内にのせる
                cell.imageView.image = image
            }
        }
        
        return cell
    }

みたいにしておく必要があります。

と思ったけどこれ、二重には表示されない気がする。 でも何かしら対策はしてないと画像とテキストがあってないって事態になりがちです。

とりあえずcollectionView(_:cellForItemAt:)内ではaddSubviewしないようにしよう!ってことでした。

どうしてもaddSubviewしないといけないんじゃ・・・って人は本記事のようにremove処理しておけば良いハズ。

余談ですが、このUICollectionViewCellは何個でもCollectionViewにregisterで登録できるんですね。 なので1行目はこのレイアウトだけどそれ以降は別のレイアウトにしたい!って時はUICollectionViewCellのサブクラスを複数用意して、そちらを場合分けして使うのが良さげです。

ちょっと今次の記事の追記を行なっていてまさに二重になる現象に出くわしました!!!!!!!! カスタムセルで

    func setup() {
        label = UILabel(frame:self.frame)
        label.text = "らべる"
        self.contentView.addSubview(label)
    }

としてaddSubviewしていたらなんと再現してしまったwwww

えっちゃんとカスタムセル作ってるし、今もうチェックマークつける話ししてるんだけど・・・って気持ちで笑ってしまいました。

解決策は

    func setup() {
        label = UILabel(frame:self.contentView.frame)
        label.text = "らべる"
        self.contentView.addSubview(label)
    }

labelのframeにself.frameを設定していると二重に表示されました。 これをself.contentView.frameにすると直りました。

フィィ

Comments

コメントはありません。

現在コメントフォームは工事中です。