19348


swiftで、PhotosとUICollectionViewを利用し、カメラロールにある写真全てを表示するプログラムを作成しているのですが、UICollectionViewの部分でうまくいかず困っています。  
ストーリーボードを使用していないせいで、viewWithTag(1)がnilなっているというところまではわかったのですが、対処方法がわからない状態です。  
できるだけ、ストーリーボードは使用せずに解決したいと思っています・・・。  
お力添えいただけたら幸いです。よろしくお願いいたします。

[code]

    import Foundation
    import UIKit
    import Photos
    
    class AlbumView: UIViewController,UICollectionViewDelegate, UICollectionViewDataSource{
    
    var photoAssets = [PHAsset]()
    var collectionView: UICollectionView!
    
    override func viewDidLoad() {
        println("now I show you alubum view!")
        super.viewDidLoad()
    
        //background image
        self.view.backgroundColor = UIColor(patternImage: UIImage(named: "cotton-a.png")!)
    
        // レイアウト作成
        let flowLayout = UICollectionViewFlowLayout()
        flowLayout.scrollDirection = .Vertical
        flowLayout.minimumInteritemSpacing = 5.0
        flowLayout.minimumLineSpacing = 5.0
        flowLayout.itemSize = CGSizeMake(100, 100)
    
        getAllPhotosInfo()
    
        // コレクションビュー作成
        collectionView = UICollectionView(frame: view.frame, collectionViewLayout: flowLayout)
        collectionView.registerClass(UICollectionViewCell.classForCoder(), forCellWithReuseIdentifier: "cell")
        collectionView.dataSource = self
        view.addSubview(collectionView)
    }
    
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return photoAssets.count
    }
    
    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = self.collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! UICollectionViewCell
        let asset = photoAssets[indexPath.row]
    
        var imageView = cell.viewWithTag(1) as! UIImageView //<-ここでnilになってしまい落ちてしまいます。
    
        let manager: PHImageManager = PHImageManager()
        manager.requestImageForAsset(asset,
            targetSize: CGSizeMake(120, 120),
            contentMode: .AspectFill,
            options: nil) { (image, info) -> Void in
                println("image is \(image)")
    
                imageView.image = image
            }
        return cell
    }
    
    private func getAllPhotosInfo() {
        photoAssets = []
    
        var options = PHFetchOptions()
        options.sortDescriptors = [
            NSSortDescriptor(key: "creationDate", ascending: false)
        ]
        //取得するタイプを選択。今回はimage.
        var assets: PHFetchResult = PHAsset.fetchAssetsWithMediaType(.Image, options: nil)
        assets.enumerateObjectsUsingBlock { (asset, index, stop) -> Void in
            self.photoAssets.append(asset as! PHAsset)
        }
    }
    
    }
    
[/code]


CC BY-SA 3.0
2015-11-26T15:52:59.267
19344
2015-11-27T06:25:39.433
2015-11-26T16:06:32.963
7290
12800
question
ios swift uicollectionview
ストーリーボードを使用しない場合のUICollectionViewとPhotosの使用について


第一に、`UITableViewCell`と`UICollectionViewCell`の相違を意識しましょう。前者は、`textLabel`や`detailTextLabel`、`imageView`といったコンテンツビューがあらかじめ用意されていますが、後者は、ほぼ空っぽの状態です。

[code]

    var imageView = cell.viewWithTag(1) as! UIImageView
    
[/code]

「なければ`imageView`を作る」というプログラムなしに、いきなり`imageView`にアクセスしようとしても、「そりゃないよ」とコンパイラにおこられるだけです。

第二に、`UICollectionViewCell`の[クラスリファレンス](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionViewCell_class/index.html#//apple_ref/swift/cl/c:objc\(cs\)UICollectionViewCell)の冒頭をご覧ください。

> To configure the appearance of your cell, add the views needed to  
>  present the data item’s content as subviews to the view in the  
>  contentView property. Do not directly add subviews to the cell itself.  
>  The cell manages multiple layers of content, of which the content view  
>  is only one. In addition to the content view, the cell manages two  
>  background views that display the cell in its selected and unselected  
>  states.

セルにサブビューを貼り付ける場合、`contentView`に`addSubview()`しよう。直接セルに`addSubview()`してはいけない。そう書かれています。

以上から、

  1. セルに`imageView`がなければ、`contentView`に`imageView`を貼り付ける。
  2. `imageView`にアクセスするには、  
`let imageView = cell.contentView.viewWithTag(1) as! UIImageView`  
とする。

という修正をすればいいとわかります。

（個人の考えとして、`UICollectionViewCell`のサブクラスを作って、`imageView`などの主要な構成を作ったほうがいいのではないかと思います。以下、サブクラス`PhotoCell`を作ったサンプルを載せておきます。）

_Xcode 6.4 + Swift 1.2_

[code]

    // import Foundation // UIKitをimportしてあれば、Foundationのimportは不要
    import UIKit
    import Photos
    
    // 紛らわしさを排除する意味で、AlbumViewをAlbumViewControllerに改名。
    class AlbumViewController: UIViewController,UICollectionViewDelegate, UICollectionViewDataSource{
    
        var photoAssets = [PHAsset]()
        var collectionView: UICollectionView!
    
        override func viewDidLoad() {
            super.viewDidLoad() // 親クラスの呼び出しは、最初の行で行うようにしましょう。
    
            println("now I show you alubum view!")
    
            //background image
            self.view.backgroundColor = UIColor(patternImage: UIImage(named: "cotton-a.png")!)
    
            // レイアウト作成
            let flowLayout = UICollectionViewFlowLayout()
            flowLayout.scrollDirection = .Vertical
            flowLayout.minimumInteritemSpacing = 5.0
            flowLayout.minimumLineSpacing = 5.0
            flowLayout.itemSize = CGSizeMake(100, 100)
    
            getAllPhotosInfo()
    
            // コレクションビュー作成
            collectionView = UICollectionView(frame: view.frame, collectionViewLayout: flowLayout)
            // PhotoCellクラスからセルをregistする。
            // collectionView.registerClass(PhotoCell.classForCoder(), forCellWithReuseIdentifier: "cell")
            collectionView.registerClass(PhotoCell.self, forCellWithReuseIdentifier: "cell") // クラスの取得は、さっくりselfでOK
            collectionView.dataSource = self
            view.addSubview(collectionView)
        }
    
        func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return photoAssets.count
        }
    
        func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
            let cell = self.collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! PhotoCell // PhotoCellクラスのインスタンスを返す。
            let asset = photoAssets[indexPath.row]
    
            // var imageView = cell.viewWithTag(1) as! UIImageView // 不要になったので、削除。
    
            let manager: PHImageManager = PHImageManager()
            manager.requestImageForAsset(asset,
                targetSize: CGSizeMake(120, 120),
                contentMode: .AspectFill,
                options: nil) { (image, info) -> Void in
                    println("image is \(image)")
    
                    // imageView.image = image // 次の行に変更。
                    cell.imageView.image = image
            }
            return cell
        }
    
        private func getAllPhotosInfo() {
            photoAssets = []
    
            var options = PHFetchOptions()
            options.sortDescriptors = [
                NSSortDescriptor(key: "creationDate", ascending: false)
            ]
            //取得するタイプを選択。今回はimage.
            var assets: PHFetchResult = PHAsset.fetchAssetsWithMediaType(.Image, options: nil)
            assets.enumerateObjectsUsingBlock { (asset, index, stop) -> Void in
                self.photoAssets.append(asset as! PHAsset)
            }
        }
    
    }
    
    // 1クラス1Swiftファイルという制限はなく、1Swiftファイルに複数のクラスを定義してもOK。
    class PhotoCell: UICollectionViewCell {
    
        // プロパティとしてimageViewを宣言しておけば、いちいちviewWithTag()などを使わずにすみます。
        var imageView: UIImageView!
    
        override init(frame: CGRect) {
            super.init(frame: frame)
    
            // イニシアライザで、imageViewを生成、貼り付け。imageViewの大きさ、ContentModeは、適宜指定してください。
            imageView = UIImageView(frame: CGRect(x: 2.0, y: 2.0, width: frame.size.width - 4.0, height: frame.size.height - 4.0))
            imageView.contentMode = UIViewContentMode.ScaleAspectFit
            // contentViewに貼り付ける。
            self.contentView.addSubview(imageView)
        }
    
        // Storyboardを使用しなくても、このイニシアライザを定義してないと、ビルドを通らない。
        required init(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    
[/code]


CC BY-SA 3.0
2015-11-26T23:31:28.910
19348
2015-11-27T06:25:39.433
2015-11-27T06:25:39.433
7362
7362
19344
answer