検証環境:
Xcode 12.4
Swift 5.3.2
UICollectionView を使ってタグクラウド風にセルが並ぶレイアウトを実現したい。
UICollectionView のデフォルトのレイアウトである UICollectionViewFlowLayout をそのまま使うと以下のようにセル間にスペースが入ってしまう。
これをスペースを開けずに左寄せに配置されるカスタムレイアウトを作成する。
コードサンプル
StoryBoard 側で UICollectionView を配置して collectionView と flowLayout を IBOutlet で接続しておく。
import UIKit class ViewController: UIViewController { var items: [String] = [ "ビアガーデン", "うなぎ", "韓国料理", "焼肉", "焼き鳥", "ステーキ", "そば", "バイキング・ビュッフェ", "しゃぶしゃぶ", "鉄板焼き", "ピザ", "食堂・定食", "パン・サンドイッチ", "カフェ", "弁当", ] override func viewDidLoad() { super.viewDidLoad() } @IBOutlet weak var collectionView: UICollectionView! { didSet { collectionView.dataSource = self collectionView.delegate = self collectionView.alwaysBounceVertical = true collectionView.allowsMultipleSelection = false } } @IBOutlet weak var flowLayout: UICollectionViewFlowLayout! { didSet { flowLayout.minimumLineSpacing = 8 flowLayout.minimumInteritemSpacing = 8 flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize flowLayout.scrollDirection = .vertical flowLayout.sectionInset = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) } } } extension ViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return items.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell cell.textLabel.text = items[indexPath.item] return cell } } extension ViewController: UICollectionViewDelegate { } extension ViewController: UICollectionViewDelegateFlowLayout { } class CollectionViewCell: UICollectionViewCell { override func awakeFromNib() { super.awakeFromNib() textLabel.textColor = UIColor.systemBlue textLabel.font = .systemFont(ofSize: 14) layer.backgroundColor = UIColor.white.cgColor layer.borderWidth = 2 layer.borderColor = UIColor.systemBlue.cgColor layer.cornerRadius = 16 layer.masksToBounds = true // iOS12のみ以下制約をつけないとAutoLayoutが効かない contentView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ contentView.leftAnchor.constraint(equalTo: leftAnchor), contentView.rightAnchor.constraint(equalTo: rightAnchor), contentView.topAnchor.constraint(equalTo: topAnchor), contentView.bottomAnchor.constraint(equalTo: bottomAnchor) ]) } // セル選択で色を反転させる override var isSelected: Bool { didSet { if isSelected { backgroundColor = UIColor.systemBlue textLabel.textColor = .white } else { backgroundColor = .white textLabel.textColor = UIColor.systemBlue } } } @IBOutlet weak var textLabel: UILabel! } // セルを左寄せにするカスタムレイアウトクラス class CollectionViewLeftAlignedLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil } var currentRowY: CGFloat = -1.0 var currentRowX: CGFloat = 0 for attribute in attributes where attribute.representedElementCategory == .cell { if currentRowY != attribute.frame.origin.y { currentRowY = attribute.frame.origin.y currentRowX = sectionInset.left } attribute.frame.origin.x = currentRowX currentRowX += attribute.frame.width + minimumInteritemSpacing } return attributes } }
セルのサイズ
今回はセル内に1行指定の UILabel があり、テキストの長さによってセルの横幅は可変長となる。
セルのサイズは固定値を指定するのではなく、AutoLayout の制約に従って動的に計算される Self Sizing 機能を利用する。
セルの Self Sizing 機能を有効にするには、UICollectionViewFlowLayout.estimatedItemSize
プロパティを 0 以外にすればよい。
estimatedItemSize
プロパティにはデフォルトでUICollectionViewFlowLayout.automaticSize
が設定されているので既に有効になっている。
なのでUICollectionViewFlowLayout.itemSize
プロパティやcollectionView(layout:sizeForItemAt:)
デリゲートメソッドによるサイズ指定はしないようにする。
StoryBoard で UICollectionViewCell に UILabel を配置し、ContentView の四辺のエッジに合うように制約を追加する。
レイアウトクラス
UICollectionViewFlowLayout を継承したサブクラスCollectionViewLeftAlignedLayout
を作成する。
そしてlayoutAttributesForElements(in:)
メソッドをオーバーライドし、各セルのframe.origin.x
を上書きして左寄せになるように調整する。