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
flowchart LR
Storyboard["Storyboard"] --> Friends["FriendsViewController"]
Friends --> Column["ColumnFlowLayout"]
Friends --> People["[Person]"]
Friends -->|"selected Person"| Feed["FeedViewController"]
Feed --> Mosaic["MosaicLayout"]
Feed --> Photos["Photos / PHAsset"]
Column -->|"responsive itemSize + animations"| FriendsCV["Friends collection"]
Mosaic -->|"cached layout attributes"| FeedCV["Feed collection"]
People --> PersonCell["PersonCell"]
Photos --> MosaicCell["MosaicCell"]
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
classDiagram
FriendsViewController *-- PersonArray : private people
FriendsViewController --> FeedViewController : segue injects person
FeedViewController *-- PHAssetArray : assets
FeedViewController *-- AvatarView : title view
UICollectionView *-- UICollectionViewLayout : active strategy
MosaicLayout *-- LayoutAttributesArray : cachedAttributes
MosaicLayout *-- CGRect : contentBounds
ColumnFlowLayout *-- IndexPathArray : insert/delete transition state
UICollectionView *-- PersonCell : reuse lifecycle
UICollectionView *-- MosaicCell : reuse lifecycle
MosaicCell *-- String : assetIdentifier guard
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 |