Introduction
Creating a horizontally scrolling UICollectionView requires setting scrollDirection = .horizontal on the UICollectionViewFlowLayout. By default, UICollectionView scrolls vertically and lays out items left-to-right, wrapping to the next row. Changing to horizontal scroll makes items flow top-to-bottom and wrap to the next column. For a single horizontal row (carousel), set the item height equal to the collection view height. This article covers basic horizontal layout, paging, and compositional layout approaches.
Basic Horizontal Layout
1import UIKit
2
3class HorizontalCollectionVC: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
4
5 var collectionView: UICollectionView!
6 let items = Array(1...20)
7
8 override func viewDidLoad() {
9 super.viewDidLoad()
10
11 let layout = UICollectionViewFlowLayout()
12 layout.scrollDirection = .horizontal
13 layout.minimumLineSpacing = 10
14 layout.minimumInteritemSpacing = 10
15 layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
16
17 collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
18 collectionView.dataSource = self
19 collectionView.delegate = self
20 collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
21 collectionView.backgroundColor = .systemBackground
22
23 view.addSubview(collectionView)
24 }
25
26 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
27 return items.count
28 }
29
30 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
31 let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
32 cell.backgroundColor = .systemBlue
33 return cell
34 }
35
36 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
37 return CGSize(width: 120, height: 120)
38 }
39}
Setting scrollDirection = .horizontal on the flow layout is the key configuration. Items fill vertically first, then scroll horizontally to show more columns.
Single-Row Carousel
1class CarouselVC: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
2
3 override func viewDidLoad() {
4 super.viewDidLoad()
5
6 let layout = UICollectionViewFlowLayout()
7 layout.scrollDirection = .horizontal
8 layout.minimumLineSpacing = 16
9
10 let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
11 collectionView.translatesAutoresizingMaskIntoConstraints = false
12 collectionView.showsHorizontalScrollIndicator = false
13 collectionView.dataSource = self
14 collectionView.delegate = self
15 collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
16
17 view.addSubview(collectionView)
18 NSLayoutConstraint.activate([
19 collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
20 collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
21 collectionView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
22 collectionView.heightAnchor.constraint(equalToConstant: 200),
23 ])
24 }
25
26 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
27 // Height matches collection view height minus insets for single row
28 return CGSize(width: 150, height: 180)
29 }
30
31 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 10 }
32
33 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
34 let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
35 cell.backgroundColor = .systemIndigo
36 cell.layer.cornerRadius = 12
37 return cell
38 }
39}
For a single horizontal row, set the item height to fill the collection view's height (minus section insets). This prevents vertical wrapping.
Paging Behavior
1let layout = UICollectionViewFlowLayout()
2layout.scrollDirection = .horizontal
3layout.minimumLineSpacing = 0
4
5let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
6collectionView.isPagingEnabled = true
7collectionView.showsHorizontalScrollIndicator = false
8
9// Each item fills the entire visible area for full-page swiping
10func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
11 return collectionView.bounds.size
12}
isPagingEnabled = true snaps scrolling to full-page boundaries. Set minimumLineSpacing = 0 and item size equal to collection view size for clean page transitions.
Compositional Layout (iOS 13+)
1func createHorizontalLayout() -> UICollectionViewCompositionalLayout {
2 let itemSize = NSCollectionLayoutSize(
3 widthDimension: .fractionalWidth(1.0),
4 heightDimension: .fractionalHeight(1.0)
5 )
6 let item = NSCollectionLayoutItem(layoutSize: itemSize)
7 item.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
8
9 let groupSize = NSCollectionLayoutSize(
10 widthDimension: .absolute(150),
11 heightDimension: .absolute(200)
12 )
13 let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
14
15 let section = NSCollectionLayoutSection(group: group)
16 section.orthogonalScrollingBehavior = .continuous // Horizontal scroll
17
18 return UICollectionViewCompositionalLayout(section: section)
19}
20
21// Usage
22let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createHorizontalLayout())
UICollectionViewCompositionalLayout with .orthogonalScrollingBehavior creates horizontally scrolling sections within a vertically scrolling collection view — ideal for app-store-style layouts.
In Storyboard
1// If using Interface Builder:
2// 1. Drag a UICollectionView onto the storyboard
3// 2. Select the Collection View
4// 3. In the Attributes Inspector, set "Scroll Direction" to "Horizontal"
5// 4. Adjust "Min Spacing" for line and item spacing
6
7// Or configure in code after outlet connection
8@IBOutlet weak var collectionView: UICollectionView!
9
10override func viewDidLoad() {
11 super.viewDidLoad()
12 if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
13 layout.scrollDirection = .horizontal
14 }
15}
Common Pitfalls
Items stacking vertically instead of scrolling: If items appear in a vertical column that scrolls horizontally, the item height is smaller than the collection view height. For a single row, make item height fill the available height.
minimumLineSpacing vs minimumInteritemSpacing: In horizontal scroll, minimumLineSpacing controls the horizontal gap between columns, and minimumInteritemSpacing controls the vertical gap between rows. The names are counterintuitive for horizontal layouts.
Cell size exceeding collection view bounds: If the item width exceeds the collection view width, cells overlap or disappear. Ensure item size fits within the visible area minus section insets.
isPagingEnabled with non-zero spacing: Paging assumes each page is exactly the collection view width. Non-zero minimumLineSpacing causes drift over multiple pages. Set spacing to 0 or use custom snapping with targetContentOffset.
Not hiding the scroll indicator: Horizontal carousels typically hide the scroll indicator with showsHorizontalScrollIndicator = false. The default shows a thin bar at the bottom that looks out of place in carousel UIs.
Summary
Set layout.scrollDirection = .horizontal on UICollectionViewFlowLayout for horizontal scrolling
For a single-row carousel, make item height match the collection view height
Enable isPagingEnabled with zero line spacing for full-page swipe behavior
Use UICollectionViewCompositionalLayout with .orthogonalScrollingBehavior for modern iOS 13+ layouts
minimumLineSpacing controls horizontal gaps in horizontal scroll direction