swiftのUICollectionViewの更新

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 3,230

etemon

score 16

swift入門者です。今サーバーのデータベースの情報をUICollectionViewCellに持ってくる作業をやっているのですが。なぜかうまくいきません。下のコードでは問題なく動くのですが、、、

import UIKit

class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {

    @IBOutlet weak var myCollectionView: UICollectionView!
    var articleArray:[Dictionary<String,String>] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        accessJSON()
    }
    /*===========================セル==================================*/
    //セルの数
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return articleArray.count
    }
    //セルの内容
    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! MyCollectionViewCell
        cell.myLabel.text = articleArray[indexPath.item]["title"]
        return cell
    }
    /*============================データベース=================================*/
    func accessJSON(){
        let urlStr = "自分のURL"
        if let url = NSURL(string: urlStr) {
            let urlSession = NSURLSession.sharedSession()
            let task = urlSession.dataTaskWithURL(url, completionHandler: self.getJSON)
            task.resume()
        }
    }
    func getJSON(data: NSData?, res: NSURLResponse?, error: NSError?){
        do {
            let jsonArray = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSArray
            for (var i=0; i<jsonArray.count; i++) {
                var articleDic:[String:String] = [:]
                if let id = jsonArray[i]["id"] as? String{
                    articleDic["id"] = id
                }
                if let title = jsonArray[i]["title"] as? String{
                    articleDic["title"] = title
                }
                if let image = jsonArray[i]["image"] as? String{
                    articleDic["image"] = image
                }
                articleArray.append(articleDic)
            }
            dispatch_async(dispatch_get_main_queue()){
                print(self.articleArray)
                self.myCollectionView.reloadData()
            }
        } catch {
            print("エラー")
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}


上のコードでは問題なく動くのですが、コードが複雑になってデータベースからJSONの情報を取ってくる部分だけをModel.swiftにまとめようとしたとき、UICollectionViewが更新されないです(やろうとしていることは上と同じです)。エラーが出るのは下のコードです。
Model.swift

import UIKit

class Model: NSObject {

    var articleArray: [Dictionary<String,String>] = []

    func accessJSON(){
        let urlStr = "http://steins-t.com/kuchiren5/api.php?json"
        if let url = NSURL(string: urlStr) {
            let urlSession = NSURLSession.sharedSession()
            let task = urlSession.dataTaskWithURL(url, completionHandler: self.getJSON)
            task.resume()
        }
    }

    func getJSON(data: NSData?, res: NSURLResponse?, error: NSError?){
        do {
            let jsonArray = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSArray
            for (var i=0; i<jsonArray.count; i++) {
                var articleDic:[String:String] = [:]
                if let id = jsonArray[i]["id"] as? String{
                    articleDic["id"] = id
                }
                if let title = jsonArray[i]["title"] as? String{
                    articleDic["title"] = title
                }
                if let image = jsonArray[i]["image"] as? String{
                    articleDic["image"] = image
                }
                articleArray.append(articleDic)
            }
            dispatch_async(dispatch_get_main_queue()){
                let hoge = ViewController()
     //↓ここでエラーが出る!!!
                hoge.myCollectionView.reloadData()
            }
        } catch {
            print("エラー")
        }
    }

}


ViewController.swift

import UIKit

class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {

    @IBOutlet weak var myCollectionView: UICollectionView!
    //var articleArray:[Dictionary<String,String>] = []

    override func viewDidLoad() {
        super.viewDidLoad()
    //Modelクラスのオブジェクトを作ってaccessJSONプロパティを実行
        let model = Model()
        model.accessJSON()
    }
    /*===========================セル==================================*/
    //セルの数
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 8
    }
    //セルの内容
    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! MyCollectionViewCell
        cell.myLabel.text = "テスト"
        return cell
    }
   override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}


なにか良い方法はないのでしょうか?みなさんの知恵をぜひお借りしたいです(>.<);;

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 2

checkベストアンサー

+1

let hoge = ViewController()
を実行すると、現在表示されているViewControllerとは別の新しいViewControllerが生成されてhogeに代入されます。さらにその新しいViewConrtrollerはstoryboardで配置した部品もロードされていませんからhogeのmyCollectionViewはnilです。このため、reloadData()がエラーになったのだと思います。

本来やりたかったのは、新しいViewControllerを生成するのではなく、現在表示されているViewControllerのmyCollectionViewの表示を更新したかっただけのはずです。

こういう時は、ModelにJSONデータの取得を依頼(accessJSON呼び出し)した時に、呼び出し元のViewConrtrollerにデータ取得の完了を教えてもらえるようなインターフェースを作れば良いです。そしてその通知をもらってViewControllerの中でreloadDataすればよいです。
NCC1701さんも指摘していますが、モデルの中でビューを操作すべきではありません。

呼び出し元に完了通知するインターフェースを作る方法はいくつかあります。
http://sakebook.hatenablog.com/entry/2015/08/12/133756
とかを読んで勉強してください。
私としては、ここではクロージャを使用することをお勧めします。クロージャをマスターしておくと、今後別の場面への応用もしやすいと思います。


あと、ついでに指摘しておくと、ViewController.swiftのviewDidLoadで
let model = Model()
としていますが、このmodel変数はローカル変数のため、viedDidLoadを終了すると消滅します。これに伴い、もし他にこのモデルオブジェクトを参照している変数がなければ、モデルオブジェクト自体も消滅します。
今はnumberOfItemsInSectioncellForItemAtIndexPathの実装が仮コーディング状態なので別に困ってないのだと思いますが、ちゃんとコーディングするなら、これらの関数の中でモデルにアクセスしてモデルの内容を取り出す処理が必要です。
そのために少なくともmodelはViewControllerのプロパティとして定義する必要があります。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

0

エラーメッセージが不明のため,想像です。

ViewControllerからmodel.accessJSON()を呼び出し,そのmodelの中で呼び出している

let hoge = ViewController()
hoge.myCollectionView.reloadData()

は循環参照になってませんか(あるいはdeallocされてない?)?

let model = Model()
model.accessJSON()
self.myCollectionView.reloadData()//追加


reloadData()をする場所を変えて試してみてください。

なお,MVCの観点からModelクラスがControllerクラスの振る舞いを操作するのはあまり良くないように思えます。(ModelがViewControllerのmyCollectionView.reloadData()というメソッドを実行している)極端な言い方をすると,Modelクラスは「値は返してやるが,その後その値をもとにして何するのかは知らん。興味もない」みたいな態度をとるオブジェクトの設計で良いと思います。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

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