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

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

ただいまの
回答率

89.97%

UICollectionViewDelegateFlowLayoutで変更したセルのサイズがスクロールすると変化する

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,977

yousei

score 5

 前提・実現したいこと

UICollectionViewで1行目は横幅一杯に1枚、2行目以降は1行に2枚画像を表示する画面を開発しています。

イメージ説明

 発生している問題・エラーメッセージ

初回表示はされるですが、画面を下にスクロールし、再度一番上に戻ると1行目の横幅が半分になり、途中の行の片方の画像の横幅が広がってしまいます。スクロールしても最初に表示された状態を保つためにはどのようにすべきでしょうか。

イメージ説明

該当のソースコード

PagingMenuControllerを使用しており、メインのビューコントロラーからimageUrlListをセットしてMyViewControllerを呼び出しています

class MyViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

    var imageUrlList: Array<String>!

    override func loadView() {
        super.loadView()
        // Do any additional setup after loading the view, typically from a nib.

        let myCollectionView = UICollectionView(frame: CGRect(origin: CGPoint.zero, size: self.view.frame.size), collectionViewLayout: UICollectionViewFlowLayout())
        myCollectionView.backgroundColor = UIColor.white
        myCollectionView.register(MyCollection.self, forCellWithReuseIdentifier: "MyCell")

        myCollectionView.delegate = self
        myCollectionView.dataSource = self        

        self.view.addSubview(myCollectionView)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        if indexPath.row == 0 {
            return CGSize(width: collectionView.frame.width, height: collectionView.frame.height * 0.3)
        }

        return CGSize(width: collectionView.frame.width * 0.5, height: collectionView.frame.height * 0.3)
    }


    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let collection = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath) as! MyCollection
        let url = imageUrlList[indexPath.row]

        let thumbnail = collection.contentView.viewWithTag(MyCollection.TAG_THUMBNAIL) as! ThumbnailImageView
        thumbnail.loadImage(urlString: url)

        return collection
    }
}

class MyCollection: UICollectionViewCell {

    static let TAG_THUMBNAIL: Int = 0

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        initView(width: frame.size.width, height: frame.size.height)
    }

    func initView(width: CGFloat, height: CGFloat) {

        let thumbnail: ThumbnailImageView = ThumbnailImageView(frame: CGRect(x: 0, y: 0, width: width, height: height * 0.3))
        thumbnail.tag = MyCollection.TAG_THUMBNAIL
        self.contentView.addSubview(thumbnail)
    }
}

class ThumbnailImageView: UIImageView {

    let CACHE_SEC: TimeInterval = 60 * 60 * 24 // 1 day

    var baseWidth: CGFloat = 0
    var baseHeight: CGFloat = 0

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
    }

    override init (frame: CGRect) {
        super.init(frame: frame)
    }

    func loadImage(urlString: String) {
        self.alpha = 0
        let req = URLRequest(url: NSURL(string: urlString)! as URL, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: CACHE_SEC);
        let session = URLSession(configuration: URLSessionConfiguration.default, delegate: nil, delegateQueue: OperationQueue.main);

        session.dataTask(with: req, completionHandler: { (data, resp, err) in
            if ((err) == nil) {

                // Success
                self.image = UIImage(data: data!);

                UIView.animate(withDuration: 1.0) { () -> Void in
                    self.alpha = 1
                }

            } else {
                // TODO: show default if error
                print("AsyncImageView:Error \(err?.localizedDescription)");
            }
        }).resume();
    }
}

試したこと

セルのサイズを変更しているcollectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSizeの引数のindexPathが描画したセルの順番と合っていないのかと疑いブレークポイントを貼ってデバッグしましたが、スクロール時にブレークポイントに引っかかりませんでした。

補足情報(言語/FW/ツール等のバージョンなど)

  • Swift 3
  • xcode 8.2.1
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • fuzzball

    2017/04/05 16:44

    collection​View(_:​layout:​size​For​Item​At:​)のコードに問題はありません。原因は他にあります。

    キャンセル

  • yousei

    2017/04/05 17:10

    ご確認頂きありがとう御座います。当該のビューコントローラの他の処理を追記しました。

    キャンセル

回答 2

checkベストアンサー

+2

CollectionViewはセルを再利用しますので、ちゃんと初期化し直さないと前回表示した状態が残ってしまいます。
今回の件は、ThumbnailImageViewのframeが最初に生成した時に設定した時のままなためかと思います。

例えば1番目のセルを再度表示した時に画像が半分になるのは、(例えば)6番目のセルを表示した時の大きさのままなためです。
同じように途中のセルが横幅いっぱいになるのは、最初に1番目のセルを表示した時の大きさのまま(MyCollectionをはみ出している)ためです。

対応策は2つあります。
どちらを使用するかは場合によります。1行目のセルとそれ以外は横幅以外に違いがないなら対応策1の方が簡単かと思いますが。

 対応策1

どうやらThumbnailImageViewはセルの横幅いっぱいになればいいみたいですから、autoresizingMaskを設定する。
つまり、MyCollection のinitView(width:, height:)

thumbnail.autoresizingMask = [.flexible​Width]

を追加する。
(コードは試してないのでエラーが出るかもです)

 対応策2

1行目とそれ以外でセルのidentifierを別にして、再利用する際に横幅いっぱいのセルと半分のセルを混在させなくする。

具体的には、

override func loadView() {
    // ...前略
    myCollectionView.backgroundColor = UIColor.white
    myCollectionView.register(MyCollection.self, forCellWithReuseIdentifier: "MyCell")
    myCollectionView.register(MyCollection.self, forCellWithReuseIdentifier: "MyCellTop") // <- この行を追加
    // 以下略...
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

    let identifier = indexPath.row == 0 ? "MyCellTop" : "MyCell"
    let collection = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as! MyCollection
    // 以下略...
}

とすればとりあえず動かすことはできるかと思います。
(例によって動作確認はしてないのでエラーが出たら適宜修正してください)

 蛇足

内容とは関係ないですが、static let TAG_THUMBNAIL: Int = 0となっていますが、UIViewのtagは0がデフォルト値なので、0をセットしても意味がありません。
1以上の値にした方がいいかと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/04/05 21:49

    ご回答頂きありがとう御座います。対応策2で解決することができました!

    キャンセル

+1

MyCollectionのinitView(width:height:)の中で

self.contentView.addSubview(thumbnail)

としていますが、これではthumbnailがどんどん積み重なっていきます。
(再利用されたセルは初期化されるわけではありません)

とりあえずの修正方法として、addする前にremoveしてみます。

self.contentView.viewWithTag(MyCollection.TAG_THUMBNAIL)?.removeFromSuperview()

他の方法としては、すでにaddされていたらaddせずに最小限の初期化だけ行う、などがあります。

 おまけ

画像を裏で読み込んでいるようですが、読み込み中にセルが画面外に出たときの処理(読み込みキャンセルなど)を作らないと、高速にスクロールしたときに表示が乱れるような気がします。
(追記)UIImageViewの中で読み込み処理を行っているので、上に書いたremoveする方法であれば、表示は乱れないですね‥たぶん)

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/04/05 19:01

    試してみましたが、やはりセルの横幅が崩れます。

    キャンセル

  • 2017/04/05 19:11 編集

    セルのcontentViewのbackgroundColorに色を付けて試してもらえますか?画像が小さくなっているだけかも。

    キャンセル

  • 2017/04/05 20:09

    contentView.backgroundColorを設定してみましたが、1行目の右のセル(空白?)は背景色が表示されました

    キャンセル

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

  • ただいまの回答率 89.97%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる