こんにちは!Popoです。
こんな方へのお勧めの記事です。
- UICollectionViewのカスタマイズを学習したい
- 選択、非選択を視覚的に表現できる画面を実装したい
前回の記事でUICollectionViewの基礎を勉強できたとおもいます。
色々とカスタマイズをしてきれいなUIにしてみたいな!
そんな気持ちになっているのではないでしょうか。
そこで今回は写真一覧画面などでよく見かける「チェックマーク」のカスタマイズをやってみたいと思います。
自前でカスタマイズできると、チェックマークの形状や色などを色々変えることができるので非常に便利です。
是非参考にして覚えてほしいですね!
アプリの動作環境
今回の開発環境は下記になります。
項目 | バージョン |
Xcode | Version 14.3.1 (14E300c) |
Swift | Swift version 5.8.1 |
MacOS | macOS 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の「写真」から取得して表示するロジックを実装してみたいと思います。
それではまた!