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

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

ただいまの
回答率

88.23%

collectionviewの引っ張って更新のエラー

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 879

po_tato

score 69

jsonからデータを取得し、それを非同期通信で配列に入れ画面に表示させるページに、
引っ張って画面を更新する機能を実装してみたのですが、
強く下に引っ張ったり、3回のうち1回くらいのペースでクラッシュしてしまいます。
軽く引っ張るくらいで更新処理が進む場合はあまりクラッシュしないですが、
強く引っ張るとほとんどの確率でクラッシュしてしまいます。

予想外の強さで引っ張ってしまった時にcellectioViewのセルのインデックス番号に不具合か何かあるのでしょうか?
また、回避策などございますでしょうか?

ちなみに、引っ張って更新を付ける前は一度もエラーはありませんでした。

エラーコードはこちらです。

Fatal error: Index out of range


文中のprint文の表示は以下のようになります

インデックス:0
インデックス:1
インデックス:2
インデックス:3
インデックス:4
インデックス:5
インデックス:4

コード本文

import UIKit
import Alamofire
import AlamofireImage
import SwiftyJSON

class ViewController: UIViewController,UICollectionViewDataSource,UICollectionViewDelegate{

    @IBOutlet weak var collectionView: UICollectionView!

    //Appdelegateのインスタンス生成
    var appdelegate:AppDelegate = UIApplication.shared.delegate as! AppDelegate

    var imageURL_box:[String] = []
    var title_box:[String] = []
    var publisher_box:[String] = []
    var articleURL_box:[String] = []

    //引っ張って更新のやつ
    private var refreshControl = UIRefreshControl()

    override func viewDidLoad() {
        super.viewDidLoad()



        //下に引っ張って更新
        refreshControl = UIRefreshControl.init()
        refreshControl.attributedTitle = NSAttributedString(string: "引っ張って更新")
        refreshControl.addTarget(self, action: #selector(ViewController.refresh(sender:)), for: .valueChanged)

        if #available(iOS 10.0, *) {
            collectionView.refreshControl = refreshControl
        } else {
            // Fallback on earlier versions
            collectionView.addSubview(refreshControl)
        }

    }
    // refresh処理
    @objc func refresh(sender: UIRefreshControl) {

        self.dateSet()

        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
            // 完了したらUIRefreshControlのendRefreshing()を呼ばないとくるくるが終わらない
            self.refreshControl.endRefreshing()
        }

    }

    override func viewWillAppear(_ animated: Bool) {

        print("viewWillAppear呼ばれた")

        dateSet()

    }

    //非同期通信して配列にデータ入れる
    func dateSet(){


        self.title_box.removeAll()
        self.publisher_box.removeAll()
        self.imageURL_box.removeAll()
        self.articleURL_box.removeAll()


        let request = URLRequest(url: URL(string: "http://~.json")!,
                                 cachePolicy: .reloadIgnoringLocalCacheData,   //キャッシュ残らせない
            timeoutInterval: 5.0)

        //非同期通信
        Alamofire.request(request).validate().responseJSON { response in
            switch response.result {
            case .success(let value):
                let json = JSON(value)
                print(json)
                for i in 0 ..< json.count{
                    self.title_box.append(json[i]["articleTitle"].string!)
                    self.publisher_box.append(json[i]["siteTitle"].string!)
                    self.articleURL_box.append(json[i]["articleURL"].string!)
                    self.imageURL_box.append(json[i]["imageURL"].string!)
                    }
                }

            case .failure(let error):
                print(error)
            }
            self.collectionView.reloadData()
        }
    }






    //セルの個数指定するデリゲートメソッド
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.imageURL_box.count
    }

    //セルに値を追加するデリゲートメソッド
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell

        print("インデックス:\(indexPath.row)")
        cell.news_detail_lbl.text = self.title_box[indexPath.row]
        cell.news_publisher_lbl.text = self.publisher_box[indexPath.row]


        //画像が読み込まれるまでのimage
        let placeholder = UIImage(named: "img1")
        let filter = AspectScaledToFitSizeFilter(size: CGSize(width: 375, height: 130))

        //画像urlが取得出来てない,""の場合 //AlamofireImage使用
        if self.imageURL_box[indexPath.row] != ""{
            cell.newsImage.af_setImage(withURL: URL(string: self.imageURL_box[indexPath.row])!,placeholderImage: placeholder, filter: filter)
        }else{
            cell.newsImage.image = UIImage(named: "img2")
        }


        return cell
    }




}



class CollectionViewCell :UICollectionViewCell{

    @IBOutlet weak var newsImage: UIImageView!
    @IBOutlet weak var news_detail_lbl: UILabel!
    @IBOutlet weak var news_publisher_lbl: UILabel!
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 3

0

CollectionView を更新する部分をメインスレッドにしてみると結果が変わりますか?
通信は非同期で行っていいのですが,ビュー周りはメインスレッドにする必要があります。

DispatchQueue.main.async {
    self.collectionView.reloadData()
}

問題ないということなので
配列の要素数が一致していない可能性があります。
ブレークポイントなどを上記のコード部分に設置し各配列の要素数を確認してください!

//非同期通信
Alamofire.request(request).validate().responseJSON { response in
    switch response.result {
    case .success(let value):
        let json = JSON(value)
        print(json)
        for i in 0 ..< json.count{
            self.title_box.append(json[i]["articleTitle"].string!)
            self.publisher_box.append(json[i]["siteTitle"].string!)
            self.articleURL_box.append(json[i]["articleURL"].string!)
            self.imageURL_box.append(json[i]["imageURL"].string!)
        }
        print(String(self.title_box.count))
        print(String(self.publisher_box.count))
        print(String(self.articleURL_box.count))
        print(String(self.imageURL_box.count))
        DispatchQueue.main.async {
            self.collectionView.reloadData()
        }
    }

    case .failure(let error):
        print(error)
    }
}

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/08/03 15:30

    全て要素数は0と表示されます。removeall()とself.collectionView.reloadData()のタイミングか何かなのでしょうか?

    キャンセル

  • 2019/08/03 23:16

    指定の位置にコードを追加し試すと、全て要素数は120と表示されます。120はjsonの全部の数なので合っています。ちなみに全て要素数は0と表示されますと記載しましたが、それはクラッシュした際の、cellForItemAtメソッドの中にカウント数のプリント文を記載し試した結果です。

    キャンセル

  • 2019/08/04 00:43

    解決方法本文に追記致しました。貴重なアドバイスありがとうございました!

    キャンセル

check解決した方法

-1

〜解決〜
スマートな対応の仕方ではないかもしれないですが、思いっきり引っ張ってもクラッシュもせず、
データの更新もできる状態に出来たので記載致します。
とりあえず、引っ張って更新した際に各配列の要素数が0の場合はクラッシュし、成功する場合は配列へのデータ挿入が出来た場合だと思ったので、そもそもデータがある場合の時だけセルに値をセットするようにしました。
この方法だと何回か引っ張らないとデータの更新が出来ないということになりますが、そもそも軽く引っ張った場合は問題なく更新出来、問題は強く引っ張った場合にたまにクラッシュするということなので、このスマートでないやり方でもとりあえず解決ということにしておきます。

    //セルに値を追加するデリゲートメソッド
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell

        //全ての配列に値が入っている時(このif文追加)
        if self.title_box.count != 0 && self.publisher_box.count != 0 && self.articleURL_box.count != 0 && self.imageURL_box.count != 0{


        cell.news_detail_lbl.text = self.title_box[indexPath.row]
        cell.news_publisher_lbl.text = self.publisher_box[indexPath.row]


        //画像が読み込まれるまでのimage
        let placeholder = UIImage(named: "img1")
        let filter = AspectScaledToFitSizeFilter(size: CGSize(width: 375, height: 130))

        //画像urlが取得出来てない,""の場合 //AlamofireImage使用
        if self.imageURL_box[indexPath.row] != ""{
            cell.newsImage.af_setImage(withURL: URL(string: self.imageURL_box[indexPath.row])!,placeholderImage: placeholder, filter: filter)
        }else{
            cell.newsImage.image = UIImage(named: "img2")
        }
}

        return cell
    }

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

-2

本文にきじ

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

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

関連した質問

同じタグがついた質問を見る