MENU

【swift アプリ開発】UICollectionViewで画面にチェックマークをつけてみる

  • URLをコピーしました!
UICollectionViewで画面にチェックマークをつけてみる

こんにちは!Popoです。

こんな方へのお勧めの記事です。

  • UICollectionViewのカスタマイズを学習したい
  • 選択、非選択を視覚的に表現できる画面を実装したい

前回の記事でUICollectionViewの基礎を勉強できたとおもいます。

色々とカスタマイズをしてきれいなUIにしてみたいな!

そんな気持ちになっているのではないでしょうか。

そこで今回は写真一覧画面などでよく見かける「チェックマーク」のカスタマイズをやってみたいと思います。

写真一覧画面

自前でカスタマイズできると、チェックマークの形状や色などを色々変えることができるので非常に便利です。

是非参考にして覚えてほしいですね!

目次

アプリの動作環境

アプリの動作環境

今回の開発環境は下記になります。

項目バージョン
XcodeVersion 14.3.1 (14E300c)
SwiftSwift version 5.8.1
MacOSmacOS Ventura バージョン13.4(22F66)

アプリの設計

アプリの設計

コーディングの前に、アプリの設計を行い開発方針を決めましょう!

構造体を利用して、画像データのチェックON・OFFを制御!

  • 構造体を定義します。
   //画像格納構造体
    struct oneImageStruct {
        var saveUmage: UIImage
        var flag: Bool
    }
構造体item内容
saveUmage表示用画像データ
flagチェックON・OFF制御フラグ
  • この構造体の配列を作成し、写真一覧画面を表示します。
考え方

写真をクリックするとチェックが表示され、再度クリックするとチェックがきえます。

初期表示で色のない丸形状を表示するようにしています。

アプリのソースコード全体

アプリのソースコード全体

それでは、実際のコーディングを行っていきましょう!

OneViewController

OneViewControllerはメインとなるUIViewControllerです。

主な機能

  • ナビゲーションバーカスタマイズ
  • UICollectionViewDelegateFlowLayoutの設定
  • UICollectionViewの生成
  • 表示用配列作成
  • UICollectionViewDelegate、UICollectionViewDataSourceメソッド

import UIKit

class OneViewController: UIViewController {

    //画像格納構造体
    struct oneImageStruct {
        var saveUmage: UIImage
        var flag: Bool
    }
    
    //表示用配列
    fileprivate var oneImgeArray:[oneImageStruct] = []
    //UICollectionView
    fileprivate var photoListCollectionView: UICollectionView!
    
    fileprivate let flagOn:Bool = true
    fileprivate let flagOff:Bool = false
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.view.backgroundColor = .black
        
        self.navigationItem.title = "Popo"

        let attrs: [NSAttributedString.Key: Any] = [
            .foregroundColor: UIColor.white,
            .font: UIFont(name: "HiraginoSans-W6",size:17)!,
            .baselineOffset:1
        ]

        // iOS15以降の場合
        let appearance = UINavigationBarAppearance()
        appearance.backgroundColor = .brown
        appearance.titleTextAttributes = attrs
        self.navigationController?.navigationBar.scrollEdgeAppearance = appearance
        // CollectionViewのレイアウトを生成.
        let layout = UICollectionViewFlowLayout()
        // Cell間の最小サイズ
        layout.minimumInteritemSpacing = 1
        // 行間の最小サイズ
        layout.minimumLineSpacing = 1
        // セクションのヘッダーサイズ
        layout.headerReferenceSize = CGSize(width:0,height:0)
        // Cellひとつひとつの大きさ.
        let photoSize:CGFloat = (SCREEN_WIDTH - 5)/4
        layout.itemSize = CGSize(width:photoSize, height:photoSize)
        // Cellのマージン.
        layout.sectionInset = UIEdgeInsets(top: 1, left: 1, bottom: 1, right: 1)
        
        layout.itemSize = CGSize(width: photoSize, height: photoSize)

        // CollectionViewを生成.
        self.photoListCollectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: SCREEN_HEIGHT), collectionViewLayout: layout)
        
        self.photoListCollectionView.register(PhotoCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
        //複数選択許可
        self.photoListCollectionView.allowsMultipleSelection = true
        self.photoListCollectionView.delegate = self
        self.photoListCollectionView.dataSource = self
        self.view.addSubview(photoListCollectionView)
        //表示用配列生成
        for index in 0 ..< 10
        {
            let saveIndex:Int = index + 1
            let iconName:String = String(saveIndex) + ".jpg"
            let saveImage:UIImage = UIImage(named: iconName) ?? UIImage()
            //初期設定はOFF
            let saveOneItem:oneImageStruct = oneImageStruct(saveUmage: saveImage, flag: self.flagOff)
            self.oneImgeArray.append(saveOneItem)
        }
    }

}
//MARK: UICollectionView
extension OneViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
    {
        return self.oneImgeArray.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
    {
        let cell : PhotoCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! PhotoCollectionViewCell
        //二重に表示されるのを防ぐ
        for subview in cell.contentView.subviews{
            subview.removeFromSuperview()
        }

        let index = indexPath.row
        
        let checkFlag:Bool = self.oneImgeArray[index].flag
        let workImage:UIImage = self.oneImgeArray[index].saveUmage
        if checkFlag
        {
            cell.setConfigure(setImage: workImage,flag:self.flagOn)
        } else {
            cell.setConfigure(setImage: workImage,flag:self.flagOff)
        }
        
        return cell
    }
    //MARK:UICollectionViewDelegate
    //選択された時に呼ばれる.
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
    {
        guard let cell: PhotoCollectionViewCell = collectionView.cellForItem(at: indexPath) as? PhotoCollectionViewCell else { return }
        let index = indexPath.row
        //選択テーブルにonを設定
        self.oneImgeArray[index].flag = self.flagOn
        
        if self.photoListCollectionView.allowsMultipleSelection {
            cell.isMarked = self.flagOn
        }
    }
    //選択状態から非選択状態になった時に呼ばれる.
    func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
        guard let cell: PhotoCollectionViewCell = collectionView.cellForItem(at: indexPath) as? PhotoCollectionViewCell else { return }
        let index = indexPath.row
        //選択テーブルにoffを設定
        self.oneImgeArray[index].flag = self.flagOff
        if self.photoListCollectionView.allowsMultipleSelection {
            cell.isMarked = self.flagOff
        }
    }
}

PhotoCollectionViewCell

cellは別クラスで制御を行います。

主な機能

  • チェックボックス表示制御
  • 表示用UIImageView生成
  • チェックボックス用UIViewの生成
  • 画像表示メソッド

import UIKit

class PhotoCollectionViewCell: UICollectionViewCell {
    
    //var backView : UIView!
    fileprivate var checkBlueView: UIView!
    fileprivate var checkWhiteView: UIView!
    
    fileprivate var thumbnailImageView : UIImageView!
    
    var saveImage:UIImage!
    
    var isMarked: Bool = false {
        didSet {
            if isMarked {
                self.checkBlueView.isHidden = false
                self.checkWhiteView.isHidden = true
            } else {
                self.checkBlueView.isHidden = true
                self.checkWhiteView.isHidden = false
            }
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        //チェックボック生成
        let boxSize = frame.width * 0.3
        
        self.checkBlueView = UIView()
        self.checkWhiteView = UIView()
        //選択時のチェックボックス
        self.checkBlueView = PhotoCheckBlueView(frame: CGRect(x:self.frame.size.width - boxSize, y:self.frame.size.height - boxSize, width:boxSize, height:boxSize))
        //非選択時のチェックボックス
        self.checkWhiteView = PhotoCheckClearView(frame: CGRect(x:self.frame.size.width - boxSize, y:self.frame.size.height - boxSize, width:boxSize, height:boxSize))
        
        self.thumbnailImageView = UIImageView()
        
        let rect:CGRect = CGRect(x: 2.5, y: 2.5, width: self.frame.size.width - 5, height: self.frame.size.height - 5)
        self.thumbnailImageView.image = saveImage
        self.thumbnailImageView.frame = rect
        self.thumbnailImageView.contentMode = UIView.ContentMode.scaleAspectFit
        
        self.thumbnailImageView.addSubview(self.checkBlueView)
        self.thumbnailImageView.addSubview(self.checkWhiteView)
        
        self.checkBlueView.isHidden = true
        self.checkWhiteView.isHidden = false
        
        self.addSubview(self.thumbnailImageView)
        
    }
 
    // 画像を表示する
    func setConfigure(setImage: UIImage?,flag:Bool) {
   
        if setImage != nil{
            //画像正方形
            let saveImage:UIImage = setImage?.pingMakesquare() ?? UIImage()
            self.thumbnailImageView.image = saveImage
        }

        if flag {
            self.checkBlueView.isHidden = false
            self.checkWhiteView.isHidden = true
        } else {
            self.checkBlueView.isHidden = true
            self.checkWhiteView.isHidden = false
        }
    }
}

PhotoCheckBlueView

写真をチェックした際に表示されるチェックのUIViewになります。

ロジックで描画するようにします。

主な機能

  • チェックボックスの描画

import UIKit

class PhotoCheckBlueView: UIView
{
    override init(frame: CGRect) {
        super.init(frame:frame)
        self.backgroundColor = UIColor.clear
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func draw(_ rect: CGRect) {
        
        let ovalColor:UIColor
        let ovalFrameColor:UIColor
        let checkColor:UIColor
        
        let RectCheck = CGRect(x:5, y:6, width:rect.width - 12, height:rect.height - 12)
        
        ovalColor = UIColor.blue
        ovalFrameColor = UIColor.white
        checkColor = UIColor.white
        
        // 円 -------------------------------------
        let oval = UIBezierPath(ovalIn: 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.move(to: CGPoint(x: xx + width / 6, y: yy + height / 2))
        //帰着点
        checkmark.addLine(to: CGPoint(x: xx + width / 3, y: yy + height * 7 / 10))
        checkmark.addLine(to: CGPoint(x: xx + width * 5 / 6, y: yy + height * 1 / 3))
        // 色の設定
        checkColor.setStroke()
        // ライン幅
        checkmark.lineWidth = 2
        // 描画
        checkmark.stroke()
        
    }
}

PhotoCheckClearView

こちらはチェック解除時のUIViewです。

主な機能

  • 非選択マークの描画

import UIKit

class PhotoCheckClearView: UIView
{
    
    override init(frame: CGRect) {
        super.init(frame:frame)
        self.backgroundColor = UIColor.clear
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func draw(_ rect: CGRect) {
        
        let ovalColor:UIColor
        let ovalFrameColor:UIColor
        
        let RectCheck = CGRect(x:5, y:6, width:rect.width - 12, height:rect.height - 12)
        
        ovalColor = UIColor.clear
        ovalFrameColor = UIColor.white
        
        // 円 -------------------------------------
        let oval = UIBezierPath(ovalIn: RectCheck)
        // 塗りつぶし色の設定
        ovalColor.setFill()
        // 内側の塗りつぶし
        oval.fill()
        //枠の色
        ovalFrameColor.setStroke()
        //枠の太さ
        oval.lineWidth = 2
        // 描画
        oval.stroke()
        
    }

}

画面動作

こんな動作になります。

各クラスのロジック解説

各クラスのロジック解説

それでは、個々のロジックを見ていきましょう!

OneViewController

メインUIViewControllerの各箇所から解説します。

定義

構造体、表示用配列、UICollectionViewなどを定義しておきます。

    //画像格納構造体

    struct oneImageStruct {

        var saveUmage: UIImage

        var flag: Bool

    }

    //表示用配列

    fileprivate var oneImgeArray:[oneImageStruct] = []

    //UICollectionView

    fileprivate var photoListCollectionView: UICollectionView!

    fileprivate let flagOn:Bool = true

    fileprivate let flagOff:Bool = false

viewDidLoad

この箇所は、前記事同様です。

  • ナビゲーションバーのカスタマイズ、UICollectionViewFlowLayout設定、UICollectionView生成
  • 表示用配列生成

今回は構造体の配列を生成するため、1行追加します。

        //表示用配列生成

        for index in 0 ..< 10

        {

            let saveIndex:Int = index + 1

            let iconName:String = String(saveIndex) + “.jpg”

            let saveImage:UIImage = UIImage(named: iconName) ?? UIImage()

            //初期設定はOFF

            let saveOneItem:oneImageStruct = oneImageStruct(saveUmage: saveImage, flag: self.flagOff)

            self.oneImgeArray.append(saveOneItem)

        }

UICollectionViewDelegate、UICollectionViewDataSourceメソッド

UICollectionViewのdelegateメソッド箇所です。

  • セル数設定

構造体配列数を設定します。生成しました構造体の配列数を返却してやります。

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int

    {

        return self.oneImgeArray.count

    }

  • セル内容表示

「indexPath.row」を取得して、配列の表示位置を取得します。

今回は、「UICollectionViewCell」を別クラスで定義しています。

構造体からフラグと画像データを取得して「PhotoCollectionViewCell」の「setConfigure」に引き渡し、画像を表示させます。

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

    {

        let cell : PhotoCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: “cell”, for: indexPath) as! PhotoCollectionViewCell

        //二重に表示されるのを防ぐ

        for subview in cell.contentView.subviews{

            subview.removeFromSuperview()

        }

        let index = indexPath.row

        let checkFlag:Bool = self.oneImgeArray[index].flag

        let workImage:UIImage = self.oneImgeArray[index].saveUmage

        if checkFlag

        {

            cell.setConfigure(setImage: workImage,flag:self.flagOn)

        } else {

            cell.setConfigure(setImage: workImage,flag:self.flagOff)

        }

        return cell

    }

  • セル選択

「indexPath.row」を取得して、配列の選択位置を取得します。

選択位置の配列のフラグ項目を「true」に置き換えます。

「isMarked」も「true」に置き換えて、チェックボックスを表示させます。

    //選択された時に呼ばれる.

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)

    {

        guard let cell: PhotoCollectionViewCell = collectionView.cellForItem(at: indexPath) as? PhotoCollectionViewCell else { return }

        let index = indexPath.row

        //選択テーブルにonを設定

        self.oneImgeArray[index].flag = self.flagOn

        if self.photoListCollectionView.allowsMultipleSelection {

            cell.isMarked = self.flagOn

        }

    }

  • セルの選択解除

こちらも「indexPath.row」を取得して、配列の選択位置を取得します。

選択位置の配列のフラグ項目を「false」に置き換えます。

「isMarked」も「false」に置き換えて、非選択マークを表示させます。

    //選択状態から非選択状態になった時に呼ばれる.

    func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {

        guard let cell: PhotoCollectionViewCell = collectionView.cellForItem(at: indexPath) as? PhotoCollectionViewCell else { return }

        let index = indexPath.row

        //選択テーブルにoffを設定

        self.oneImgeArray[index].flag = self.flagOff

        if self.photoListCollectionView.allowsMultipleSelection {

            cell.isMarked = self.flagOff

        }

PhotoCollectionViewCell

cellのクラスの解説です。

isMarkedでチェックマークの描画制御

「isMarked」を呼び出すことでチェックボックスの表示制御を行います。

    var isMarked: Bool = false {

        didSet {

            if isMarked {

                self.checkBlueView.isHidden = false

                self.checkWhiteView.isHidden = true

            } else {

                self.checkBlueView.isHidden = true

                self.checkWhiteView.isHidden = false

            }

        }

    }

override init(frame: CGRect) {

画像データ用UIImageView、チェックボックス用UIView、非選択マーク用UIViewを生成します。

    override init(frame: CGRect) {

        super.init(frame: frame)

        //チェックボック生成

        let boxSize = frame.width * 0.3

        self.checkBlueView = UIView()

        self.checkWhiteView = UIView()

        //選択時のチェックボックス

        self.checkBlueView = PhotoCheckBlueView(frame: CGRect(x:self.frame.size.width – boxSize, y:self.frame.size.height – boxSize, width:boxSize, height:boxSize))

        //非選択時のチェックボックス

        self.checkWhiteView = PhotoCheckClearView(frame: CGRect(x:self.frame.size.width – boxSize, y:self.frame.size.height – boxSize, width:boxSize, height:boxSize))

     

        self.thumbnailImageView = UIImageView()

        let rect:CGRect = CGRect(x: 2.5, y: 2.5, width: self.frame.size.width – 5, height: self.frame.size.height – 5)

        self.thumbnailImageView.image = saveImage

        self.thumbnailImageView.frame = rect

        self.thumbnailImageView.contentMode = UIView.ContentMode.scaleAspectFit

        self.thumbnailImageView.addSubview(self.checkBlueView)

        self.thumbnailImageView.addSubview(self.checkWhiteView)

        self.checkBlueView.isHidden = true

        self.checkWhiteView.isHidden = false

        self.addSubview(self.thumbnailImageView)

    }

setConfigure

画像データと制御フラグを引き継いで、画像データ用UIImageView、チェックボックス用UIView、非選択マーク用UIViewを表示します。

このメソッドは、下記メソッドで呼び出されています。

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

    // 画像を表示する

    func setConfigure(setImage: UIImage?,flag:Bool) {

        // 取得したimageをUIImageViewなどで表示する

        if setImage != nil{

            //画像正方形

            let saveImage:UIImage = setImage?.pingMakesquare() ?? UIImage()

            self.thumbnailImageView.image = saveImage

        }

        if flag {

            self.checkBlueView.isHidden = false

            self.checkWhiteView.isHidden = true

        } else {

            self.checkBlueView.isHidden = true

            self.checkWhiteView.isHidden = false

        }

    }

PhotoCheckBlueView

チェックマークは、全てロジックで描画しています。

  • 円描画

サイズ、描画位置は微調整します。

背景色は青色にしておきます。

  let RectCheck = CGRect(x:5, y:6, width:rect.width – 12, height:rect.height – 12)

  ovalColor = UIColor.blue

        ovalFrameColor = UIColor.white

        checkColor = UIColor.white

  // 円 ————————————-

        let oval = UIBezierPath(ovalIn: 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.move(to: CGPoint(x: xx + width / 6, y: yy + height / 2))

        //帰着点

        checkmark.addLine(to: CGPoint(x: xx + width / 3, y: yy + height * 7 / 10))

        checkmark.addLine(to: CGPoint(x: xx + width * 5 / 6, y: yy + height * 1 / 3))

        // 色の設定

        checkColor.setStroke()

        // ライン幅

        checkmark.lineWidth = 2

        // 描画

        checkmark.stroke()

PhotoCheckClearView

チェックマークをクリアするUIViewです。

  • 非選択時の円描画

塗り潰し無しで線は白色の円を描画します。

        let RectCheck = CGRect(x:5, y:6, width:rect.width – 12, height:rect.height – 12)

        ovalColor = UIColor.clear

        ovalFrameColor = UIColor.white

        // 円 ————————————-

        let oval = UIBezierPath(ovalIn: RectCheck)

        // 塗りつぶし色の設定

        ovalColor.setFill()

        // 内側の塗りつぶし

        oval.fill()

        //枠の色

        ovalFrameColor.setStroke()

        //枠の太さ

        oval.lineWidth = 2

        // 描画

        oval.stroke()

まとめ

まとめ

お好みによって、下記のようなカスタマイズが可能です。

  • チェックマークの色、形状を変える
  • 線の太さを変える
  • チェックマークの位置を変える
  • 非選択のマークは表示させない

その後の処理としては、チェックされた画像を次画面に引き継ぐ対応が想定されます。

これも簡単で、構造体のflagが「true」の配列の画像データを別配列に格納して、そのまま次画面に引き継ぐだけですね!

次回は画像データをiPhoneの「写真」から取得して表示するロジックを実装してみたいと思います。

それではまた!

よかったらシェアしてね!
  • URLをコピーしました!
目次