CollectionViewCellにチェックボックスをつけて選択状態を見えるようにする

タイトルのとおりです.最終的には簡単だったのですがそこにたどり着くまでにいろいろ面倒だったので書いておきます.

CollectionViewで写真アプリのようにして写真を表示,そしてそれを複数選択をするというViewを作りたかったのですがただ思いついたとおりにCellの隅にチェックボックスを置いてCellに選択されているかというプロパティをもたせてどのCellが選択されているかを判断しようとしたらCollectionViewのCellの再利用の機能のせいであるCellを選択したら画面の外の他のCellも選択状態になるということになりましてそれで悩んでいたのですが,

ViewControllerの方で配列で選択されているCellのindexPathを保持しておくといいというのを見つけてその通りに実装しようとしたらなんかいろいろ面倒でじゃあどうするか...

CollectionViewCellに元々selectedとかいうプロパティがあるではないか! CollectionViewにselectedがtrueになっているCellを保持している配列もあるらしいじゃん!

こんなに困って自分で実装しようとしたら元々あったらしく無駄足な感じがしましたが気を取り直して標準であるプロパティを使って実装してみました.

今回作ったサンプルはこんな感じです.

f:id:mashiroyuya:20160304021925p:plain

github.com

この茶色のViewをUIImageViewにして画像を入れるとアルバムっぽくなるかと思います.

実装の仕方 概要

まずチェックマークを新しいクラスを使って書いておきます(CheckBox).画像は使わずdrawRectで描画しています.

CustomCellを作ります.ここでCellに非選択状態のCheckBoxViewを貼り付けておきます.

そしてCellの選択状態に表示されるUICollectionViewCellのプロパティであるselectedbackgroundViewに選択状態(緑色)のCheckBoxViewを入れておきます.

ViewControllerにCollectionViewを入れるときにcollectionViewのプロパティにallowsMultipleSelection = trueとセットすることでCellの複数選択を許可するようにしましょう.

以外と簡単にできます.

まずCheckBoxView

import UIKit

class CheckBoxView: UIView {
    var selected = false
    init(frame: CGRect,selected: Bool) {
        super.init(frame:frame)
        self.selected = selected
        self.backgroundColor = UIColor.clearColor()
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
   
    override func drawRect(rect: CGRect) {
        let ovalColor:UIColor
        let ovalFrameColor:UIColor
        let checkColor:UIColor
        
        let RectCheck = CGRectMake(5, 5, rect.width - 10, rect.height - 10)
        
        if self.selected {
            ovalColor = UIColor(red: 85/255, green: 185/255, blue: 1/255, alpha: 1)
            ovalFrameColor = UIColor.blackColor()
            checkColor = UIColor.whiteColor()
        }else{
            ovalColor = UIColor(red: 150/255, green: 150/255, blue: 150/255, alpha: 0.2)
            ovalFrameColor = UIColor(red: 50/255, green: 50/255, blue: 50/255, alpha: 0.3)
            checkColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.5)
        }
        
        // 円 -------------------------------------
        let oval = UIBezierPath(ovalInRect: RectCheck)
        
        // 塗りつぶし色の設定
        ovalColor.setFill()
        // 内側の塗りつぶし
        oval.fill()
        //枠の色
        ovalFrameColor.setStroke()
        //枠の太さ
        oval.lineWidth = 2
        // 描画
        oval.stroke()
        
        let xx = RectCheck.origin.x
        let yy = RectCheck.origin.y
        let width = RectCheck.width
        let height = RectCheck.height
        
        // チェックマークの描画 ----------------------
        let checkmark = UIBezierPath()
        //起点
        checkmark.moveToPoint(CGPointMake(xx + width / 6, yy + height / 2))
        //帰着点
        checkmark.addLineToPoint(CGPointMake(xx + width / 3, yy + height * 7 / 10))
        checkmark.addLineToPoint(CGPointMake(xx + width * 5 / 6, yy + height * 1 / 3))
        // 色の設定
        checkColor.setStroke()
        // ライン幅
        checkmark.lineWidth = 6
        // 描画
        checkmark.stroke()

    }
}

丸い形のチェックボックスになります. このクラスのselectedがfalseなら薄い灰色,trueなら緑になるようにしています.

CustomCollectionViewCell

import UIKit

class CustomCollectionViewCell: UICollectionViewCell {
    
    var testView : UIView?
    
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        testView = UIView()
        testView!.backgroundColor = UIColor.brownColor()
        self.backgroundView = testView
        
        let boxSize = frame.width * 0.3
        
        let falseBox = CheckBoxView(frame: CGRectMake(0, 0, boxSize, boxSize), selected: false)
        self.addSubview(falseBox)
        
        let trueBox = CheckBoxView(frame: CGRectMake(0, 0, boxSize, boxSize), selected: true)
        let backView = UIView(frame: frame)
        backView.backgroundColor = UIColor.clearColor()
        backView.addSubview(trueBox)
        self.selectedBackgroundView = backView
    }
}

Cellに標準でfalseなCheckBoxを置いておきます. そしてCellのselectedBackgroundViewというプロパティにtrueなCheckBoxを入れます.

selectedBackgroundView

selectedBackgroundViewはCellが選択状態にある時にCellに表示されるViewです.デフォルトではnilですがここにViewを入れておくとそのViewが表示されます.

しかしここで問題なのですがCellのcontentViewになにかViewが入っているとこのselectedBackgroundViewが選択状態でも表示されないようです.

CellのbackgroundViewになにかCellに表示したいViewを設定するのが重要です.

最初contentViewにセットしていたのですが上にあるようにこれだとうまくいきません.

CollectionViewController

import UIKit

class CollectionViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource{

    var myCollectionView:UICollectionView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let width = self.view.bounds.width
        let cellSize = width * 0.5 - 12

        // CollectionViewのレイアウトを生成.
         let layout = UICollectionViewFlowLayout()
        layout.itemSize = CGSizeMake(cellSize, cellSize)
        layout.sectionInset = UIEdgeInsetsMake(16, 8, 32, 8)
        print(layout.minimumLineSpacing)
        layout.minimumLineSpacing = 10 //cellの縦のマージン
        layout.minimumInteritemSpacing = 8 //cellの横のマージン
        layout.headerReferenceSize = CGSizeMake(100,30)

        
        // CollectionViewを生成.
        myCollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
        myCollectionView.registerClass(CustomCollectionViewCell.self, forCellWithReuseIdentifier: "MyCell")
       
        myCollectionView.delegate = self
        myCollectionView.dataSource = self
        
        myCollectionView.backgroundColor = UIColor.whiteColor()
        
        myCollectionView.allowsMultipleSelection = true
        
        self.view.addSubview(myCollectionView)
    }
    
    //MARK:- UICollectionViewDataSorce
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 20
    }
    
    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MyCell", forIndexPath: indexPath) as! CustomCollectionViewCell
        //ここでcell.testViewなどcellのbackgroundViewにあるViewになにか必要な要素を配置する
        return cell
    }
    
    //MARK:UICollectionViewDelegate
    //選択された時に呼ばれる.
    func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MyCell", forIndexPath: indexPath) as! CustomCollectionViewCell
        print(cell.selected)

        //選択された時に選択されているCellのindexPathを表示
        let selecteditems = self.myCollectionView.indexPathsForSelectedItems()
        print(selecteditems)
    }
    //選択状態から非選択状態になった時に呼ばれる.
    func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MyCell", forIndexPath: indexPath) as! CustomCollectionViewCell
        print(cell.selected)
    }
}

allowsMultipleSelection

myCollectionView.allowsMultipleSelection = true の一行をしっかり入れましょう.

これはCellの複数選択を許可するcollectionViewのプロパティです.

これをtrueにすることでCellの複数選択が可能になります.

デフォルトではこれはfalseなので一つしか選択できない状態になります.

それ以外は普通にCollectionViewを設定してそこにCustomCellを登録して使いましょう.

選択されたCellを取得するには

self.myCollectionView.indexPathsForSelectedItems() というメソッドを使いましょう.これで選択されているCellのindexPathを配列の形で取得することができます.

今回作ったサンプルをGithubにあげておきます.

github.com