Sample CodeiOS, iPadOS, Mac CatalystReviewed 2026-07-21View on Apple Developer

Customizing collection view layouts

At a glance

Item Summary
Purpose Compares extending flow layout for responsive columns with implementing a fully custom mosaic layout.
App architecture A friends list and photo feed share model/cell conventions but plug different UICollectionViewLayout strategies into separate collection controllers.
Main patterns Layout strategy, framework template-method overrides, attribute caching, batch-update commands, reuse identity guard, and lightweight MVC.
Project style Feature folders for Model, Layouts, Cells, Controls, and two collection-view controllers.

Project structure

ImageFeed/
├── AppDelegate.swift
├── FriendsViewController.swift
├── FeedViewController.swift
├── Model/Person.swift
├── Layouts/
│   ├── ColumnFlowLayout.swift
│   └── MosaicLayout.swift
├── Cells/
│   ├── PersonCell.swift
│   └── MosaicCell.swift
├── Controls/AvatarView.swift
└── Utilities/Utilities.swift

Structure observations

  • Layout algorithms are reusable objects, separate from data source and navigation code.
  • Person / PersonUpdate describe list data and simulated remote changes.
  • Cells own presentation/reuse cleanup; controllers own model arrays and Photos integration.

Overall architecture

Reference code

ImageFeed/FeedViewController.swift:23 — the feed installs the fully custom layout as a strategy when it constructs its collection view.

override func viewDidLoad() {
    super.viewDidLoad()

    let mosaicLayout = MosaicLayout()
    collectionView = UICollectionView(
        frame: view.bounds,
        collectionViewLayout: mosaicLayout
    )
    collectionView.delegate = self
    collectionView.dataSource = self
    collectionView.register(
        MosaicCell.self,
        forCellWithReuseIdentifier: MosaicCell.identifer
    )
}

Interpretation

Controllers provide data and navigation; layout subclasses own geometry. ColumnFlowLayout reuses flow layout’s engine and changes sizing/update attributes, while MosaicLayout must calculate, cache, invalidate, size, and query every attribute itself.

Ownership and state

Ownership evidence

ImageFeed/Layouts/MosaicLayout.swift:17 — the custom layout owns cached attributes and content bounds, resetting both before deriving frames for the current collection.

class MosaicLayout: UICollectionViewLayout {
    var contentBounds = CGRect.zero
    var cachedAttributes =
        [UICollectionViewLayoutAttributes]()

    override func prepare() {
        super.prepare()
        guard let collectionView = collectionView else { return }

        cachedAttributes.removeAll()
        contentBounds = CGRect(
            origin: .zero,
            size: collectionView.bounds.size
        )
        let count = collectionView.numberOfItems(inSection: 0)
        // Derive and cache one frame per item.
    }
}
Owner Object or state Relationship Mutation authority
FriendsViewController Private people array Owns list model order Refresh and batch-update methods
FeedViewController assets, selected person, avatar view Owns feed session data Photo authorization/fetch and segue injection
Collection view Active layout and reusable cells Framework retains/queries/reuses Collection lifecycle
ColumnFlowLayout Column constants and pending insert/delete paths Owns flow customization state Layout update callbacks
MosaicLayout Content bounds and cached attributes Owns full geometry cache prepare and query overrides
MosaicCell Current asset identifier and image view Owns reuse identity guard Cell configuration and prepareForReuse

FriendsViewController also declares private flowLayout and feedViewController properties, but the inspected source does not use them after declaration; the active feed destination comes from the segue, and the list layout is storyboard-provided.

Class and protocol design

Type or protocol Responsibility Depends on or conforms to
ColumnFlowLayout Compute responsive column width and animate inserted/deleted items UICollectionViewFlowLayout
MosaicLayout Build segmented frames, expose content size, invalidate on width change, answer rect queries UICollectionViewLayout
FriendsViewController Display/mutate people and route selected person UICollectionViewController
FeedViewController Fetch Photos assets and bind them to a 50K-item stress feed UICollectionViewController, Photos
Person Store friend display/update state CustomStringConvertible
PersonUpdate Represent delete/insert/move/reload commands Enum with associated values
PersonCell / MosaicCell Present list/photo data and reset reusable state UICollectionViewCell

There is no app-defined protocol. Subclass polymorphism is the primary abstraction: UIKit calls the common layout API, and each concrete layout implements a different geometry strategy.

Access control

Symbol Access Verified effect Why it fits this design
Friends layout/feed properties and people private Only the list controller can access them. Keeps navigation/list mutation local, although two stored collaborators are unused.
ColumnFlowLayout sizing constants private let Fixed implementation policy. Prevents controllers from coupling to geometry constants.
Pending insertion/deletion paths private var Only layout transition callbacks mutate them. Protects animation lifecycle state.
Photo-denied alert helper private Only feed authorization flow can present it. Keeps error presentation local.
FeedViewController.assets / person implicit internal Other module code can inject or replace feed data. Segue needs person injection; assets could be narrowed to private.
MosaicLayout.cachedAttributes / contentBounds implicit internal var Module code could mutate cache directly. Teaching simplicity; private would better protect cache invariants.
Cell subviews/model properties mostly implicit internal Controllers configure cells directly. Simple binding across files; semantic configure methods would narrow exposure.
App types implicit internal No external API. Standalone sample target.

Reference code

ImageFeed/Layouts/ColumnFlowLayout.swift:10 — fixed geometry and transition bookkeeping are private to the layout.

class ColumnFlowLayout: UICollectionViewFlowLayout {
    private let minColumnWidth: CGFloat = 300.0
    private let cellHeight: CGFloat = 70.0

    private var deletingIndexPaths = [IndexPath]()
    private var insertingIndexPaths = [IndexPath]()
}

Logic ownership and placement

Logic Owning type or file Placement rationale
Friend list data, batch mutations, navigation FriendsViewController Must synchronize model order with collection updates and segue selection.
Responsive flow sizing/update animations ColumnFlowLayout Geometry/transition attributes are layout responsibilities.
Mosaic segmentation, cache, invalidation, rect lookup MosaicLayout A full custom layout must satisfy UIKit’s layout contract.
Photos authorization/fetch and cell image requests FeedViewController Requires presentation and data-source context.
Stale asynchronous-result guard MosaicCell.assetIdentifier + feed callback Cell reuse identity crosses request and display boundaries.
Cell presentation/reset Cell subclasses Reusable UI owns its subviews and cleanup.

Design patterns

Pattern Source evidence Purpose or tradeoff
Layout strategy ImageFeed/FeedViewController.swift:27 Swaps geometry without changing collection/data-source APIs.
Template method ImageFeed/Layouts/ColumnFlowLayout.swift:21 Overrides framework lifecycle hooks while reusing flow behavior.
Attribute cache ImageFeed/Layouts/MosaicLayout.swift:23 Precomputes geometry and serves repeated viewport queries.
Bounds invalidation ImageFeed/Layouts/MosaicLayout.swift:105 Rebuilds only when collection size changes.
Spatial binary search ImageFeed/Layouts/MosaicLayout.swift:117 Finds the first intersecting cached attribute before scanning neighbors.
Batch-update command model ImageFeed/Model/Person.swift:10 Represents remote changes independently from their UIKit application.
Reuse identity guard ImageFeed/FeedViewController.swift:81 Prevents an asynchronous image from overwriting a reused cell.
MVC-style separation ImageFeed/Model/Person.swift:17 Keeps friend values outside controller/cell types.

Naming conventions

  • Layout types name geometry: ColumnFlowLayout and MosaicLayout.
  • FriendsViewController and FeedViewController name feature screens, not generic collection mechanics.
  • PersonUpdate cases use collection-operation verbs: delete, insert, move, reload.
  • Cache/state names are direct: cachedAttributes, contentBounds, deletingIndexPaths.
  • MosaicCell.identifer preserves the source’s misspelling; identifier would be the conventional spelling.

Architecture takeaways

  • Use a flow-layout subclass when its placement model still fits; use a full layout subclass when geometry no longer does.
  • A custom layout owns cache validity, content bounds, and efficient viewport queries.
  • Keep collection model mutations synchronized with batch-update commands.
  • Guard asynchronous cell results with stable identity and clear the identity on reuse.
  • Make layout caches private in production to protect derived-state invariants.

Source map

Source file Relevant symbols
ImageFeed/Layouts/ColumnFlowLayout.swift Responsive flow sizing and update animation
ImageFeed/Layouts/MosaicLayout.swift Full geometry cache/query implementation
ImageFeed/FriendsViewController.swift Friend data, batch updates, segue
ImageFeed/FeedViewController.swift Layout composition, Photos data, reuse guard
ImageFeed/Model/Person.swift Person and PersonUpdate
ImageFeed/Cells/PersonCell.swift Friend presentation/highlight/reuse
ImageFeed/Cells/MosaicCell.swift Photo presentation and asset identity