Skip to content

CollectionView Closure Based Delegate

Taichiro Kimura edited this page Apr 19, 2026 · 1 revision

SJUICollectionView クロージャベース Delegate 設計

概要

SJUICollectionViewにクロージャベースのデリゲート設定機能を追加し、チェーン形式で直感的にイベントハンドラを設定できるようにする。また、カスタムレイアウトにも対応できる拡張性のある設計とする。

構成ファイル

ファイル 役割
SJUICollectionViewDelegateProxy.swift UICollectionViewDelegate/DataSource/FlowLayoutDelegateを実装するプロキシクラス
SJUICollectionView+Closures.swift チェーン可能なExtensionメソッド群
SJUILayoutDelegateAdapter.swift カスタムレイアウト対応のプロトコルと基底クラス

使用例

1. 基本的なFlowLayout(標準)

collectionView
    // UICollectionViewDataSource
    .onNumberOfSections { _ in 2 }
    .onNumberOfItemsInSection { _, section in items[section].count }
    .onCellForItem { collectionView, indexPath in
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        return cell
    }
    // UICollectionViewDelegate
    .onDidSelectItem { _, indexPath in
        handleSelection(indexPath)
    }
    .onWillDisplayCell { _, cell, indexPath in
        // セル表示前の処理
    }
    // UICollectionViewDelegateFlowLayout
    .onSizeForItem { _, layout, indexPath in
        CGSize(width: 100, height: 100)
    }
    .onInsetForSection { _, layout, section in
        UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    }
    .onMinimumLineSpacing { _, layout, section in 10 }
    .onMinimumInteritemSpacing { _, layout, section in 5 }

2. カスタムレイアウト(例: WaterfallLayout)

Step 1: カスタムレイアウトとデリゲートプロトコルの定義(アプリ側)

// WaterfallLayout.swift
class WaterfallLayout: UICollectionViewLayout {
    weak var delegate: WaterfallLayoutDelegate?

    var numberOfColumns: Int = 2
    var cellPadding: CGFloat = 8

    // ...レイアウト実装
}

protocol WaterfallLayoutDelegate: AnyObject {
    func collectionView(_ collectionView: UICollectionView,
                        heightForItemAt indexPath: IndexPath) -> CGFloat
    func numberOfColumns(in collectionView: UICollectionView) -> Int
}

Step 2: Adapterの定義

// WaterfallLayoutAdapter.swift
class WaterfallLayoutAdapter: SJUILayoutDelegateAdapter, WaterfallLayoutDelegate {

    // クロージャプロパティ
    var heightForItem: ((UICollectionView, IndexPath) -> CGFloat)?
    var numberOfColumns: ((UICollectionView) -> Int)?

    // SJUILayoutDelegateAdapter
    override func attachToLayout(_ layout: UICollectionViewLayout) {
        guard let waterfallLayout = layout as? WaterfallLayout else { return }
        waterfallLayout.delegate = self
    }

    // WaterfallLayoutDelegate
    func collectionView(_ collectionView: UICollectionView,
                        heightForItemAt indexPath: IndexPath) -> CGFloat {
        heightForItem?(collectionView, indexPath) ?? 100
    }

    func numberOfColumns(in collectionView: UICollectionView) -> Int {
        numberOfColumns?(collectionView) ?? 2
    }
}

Step 3: Extensionの追加

// SJUICollectionView+WaterfallLayout.swift
extension SJUICollectionView {

    /// WaterfallLayoutアダプターを登録(アプリ起動時に1回呼び出し)
    static func registerWaterfallLayoutSupport() {
        SJUICollectionViewDelegateProxy.registerLayoutAdapter(
            forLayoutType: WaterfallLayout.self,
            adapterType: WaterfallLayoutAdapter.self
        )
    }

    @discardableResult
    func onWaterfallHeightForItem(_ handler: @escaping (UICollectionView, IndexPath) -> CGFloat) -> Self {
        if let adapter = delegateProxy.layoutAdapter as? WaterfallLayoutAdapter {
            adapter.heightForItem = handler
        }
        return self
    }

    @discardableResult
    func onWaterfallNumberOfColumns(_ handler: @escaping (UICollectionView) -> Int) -> Self {
        if let adapter = delegateProxy.layoutAdapter as? WaterfallLayoutAdapter {
            adapter.numberOfColumns = handler
        }
        return self
    }
}

Step 4: 使用側コード

// AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions...) {
    // カスタムレイアウトサポートを登録
    SJUICollectionView.registerWaterfallLayoutSupport()
}

// ViewController.swift
collectionView
    .onNumberOfItemsInSection { _, _ in photos.count }
    .onCellForItem { cv, indexPath in
        let cell = cv.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as! PhotoCell
        cell.configure(with: photos[indexPath.item])
        return cell
    }
    .onDidSelectItem { _, indexPath in
        showPhotoDetail(photos[indexPath.item])
    }
    // Waterfall専用メソッド
    .onWaterfallHeightForItem { _, indexPath in
        photos[indexPath.item].calculatedHeight
    }
    .onWaterfallNumberOfColumns { _ in
        UIDevice.current.orientation.isLandscape ? 3 : 2
    }

3. JSONでのレイアウト指定

{
  "type": "Collection",
  "id": "photo_collection",
  "layout": "Waterfall",
  "layoutConfig": {
    "columns": 2,
    "cellPadding": 8
  },
  "cellClasses": [
    { "className": "PhotoCell" }
  ],
  "setTargetAsDelegate": true,
  "setTargetAsDataSource": true
}

対応するデリゲートメソッド

UICollectionViewDataSource

メソッド クロージャ
numberOfSections(in:) onNumberOfSections
collectionView(_:numberOfItemsInSection:) onNumberOfItemsInSection
collectionView(_:cellForItemAt:) onCellForItem
collectionView(_:viewForSupplementaryElementOfKind:at:) onSupplementaryView

UICollectionViewDelegate

メソッド クロージャ
collectionView(_:didSelectItemAt:) onDidSelectItem
collectionView(_:didDeselectItemAt:) onDidDeselectItem
collectionView(_:willDisplay:forItemAt:) onWillDisplayCell
collectionView(_:didEndDisplaying:forItemAt:) onDidEndDisplayingCell
collectionView(_:shouldSelectItemAt:) onShouldSelectItem
collectionView(_:shouldDeselectItemAt:) onShouldDeselectItem
collectionView(_:shouldHighlightItemAt:) onShouldHighlightItem
collectionView(_:didHighlightItemAt:) onDidHighlightItem
collectionView(_:didUnhighlightItemAt:) onDidUnhighlightItem

UICollectionViewDelegateFlowLayout

メソッド クロージャ
collectionView(_:layout:sizeForItemAt:) onSizeForItem
collectionView(_:layout:insetForSectionAt:) onInsetForSection
collectionView(_:layout:minimumLineSpacingForSectionAt:) onMinimumLineSpacing
collectionView(_:layout:minimumInteritemSpacingForSectionAt:) onMinimumInteritemSpacing
collectionView(_:layout:referenceSizeForHeaderInSection:) onHeaderReferenceSize
collectionView(_:layout:referenceSizeForFooterInSection:) onFooterReferenceSize

UIScrollViewDelegate

メソッド クロージャ
scrollViewDidScroll(_:) onDidScroll
scrollViewWillBeginDragging(_:) onWillBeginDragging
scrollViewDidEndDragging(_:willDecelerate:) onDidEndDragging
scrollViewDidEndDecelerating(_:) onDidEndDecelerating

設計の特徴

特徴 説明
型安全 カスタムレイアウトごとに専用メソッドを定義し、型安全を保証
拡張性 アプリ側で新しいレイアウトタイプを自由に追加可能
一貫性 標準FlowLayoutと同じチェーン形式で使用可能
分離 レイアウト実装とデリゲート設定が明確に分離
後方互換 従来のsetTargetAsDelegate方式も引き続き利用可能

注意事項

  1. プロキシとの併用: クロージャベースのデリゲートを使用する場合、従来のsetTargetAsDelegateは使用しない
  2. メモリ管理: クロージャ内でのself参照は[weak self]を使用すること
  3. レイアウト登録: カスタムレイアウトを使用する場合は、アプリ起動時にアダプターを登録する必要がある

Language / 言語


English Documentation

Getting Started

Advanced Guides

Development Tools

Release Notes

UI Components

Layout Components

Controls

Special Views


日本語ドキュメント

はじめに

上級ガイド

開発ツール

リリースノート

UIコンポーネント

レイアウトコンポーネント

コントロール

特殊ビュー


🔗 Related Projects

Clone this wiki locally