feature/v1
swift 150 lines 4.71 KB
Raw
1 //
2 // SeeAllViewController.swift
3 // JomloCollectionView Example
4 //
5 // Created by Seto Elkahfi on 2026-01-27.
6 // Copyright © 2026 Seto Elkahfi. All rights reserved.
7 //
8
9 import JomloCollectionView
10 import UIKit
11
12 /// A view controller that simulates loading content from a network request,
13 /// then displays the results in a grid layout.
14 ///
15 /// Inspired by Viaplay's `LinkLoadingViewController` pattern — shows a loading
16 /// indicator while fetching page data, then transitions to a content grid.
17 @MainActor
18 final class SeeAllViewController: JomloCollectionViewController {
19
20 private let sectionTitle: String
21 private let contentItems: [PosterItem]
22
23 private lazy var loadingView: UIView = {
24 let container = UIView()
25 container.translatesAutoresizingMaskIntoConstraints = false
26
27 let spinner = UIActivityIndicatorView(style: .large)
28 spinner.startAnimating()
29 spinner.translatesAutoresizingMaskIntoConstraints = false
30
31 let label = UILabel()
32 label.text = "Loading..."
33 label.font = .preferredFont(forTextStyle: .subheadline)
34 label.textColor = .secondaryLabel
35 label.translatesAutoresizingMaskIntoConstraints = false
36
37 container.addSubview(spinner)
38 container.addSubview(label)
39
40 NSLayoutConstraint.activate([
41 spinner.centerXAnchor.constraint(equalTo: container.centerXAnchor),
42 spinner.centerYAnchor.constraint(equalTo: container.centerYAnchor, constant: -16),
43 label.centerXAnchor.constraint(equalTo: container.centerXAnchor),
44 label.topAnchor.constraint(equalTo: spinner.bottomAnchor, constant: 12),
45 ])
46
47 return container
48 }()
49
50 init(title: String, items: [PosterItem]) {
51 self.sectionTitle = title
52 self.contentItems = items
53 super.init(sections: [])
54 }
55
56 override func viewDidLoad() {
57 super.viewDidLoad()
58 self.title = sectionTitle
59 view.backgroundColor = .systemBackground
60
61 collectionView.registerJomloCell(PosterCell.self)
62 collectionView.register(
63 JomloSectionHeaderView.self,
64 forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
65 withReuseIdentifier: JomloSectionHeaderView.reuseIdentifier
66 )
67
68 showLoading()
69 simulateNetworkRequest()
70 }
71
72 private func showLoading() {
73 collectionView.isHidden = true
74 view.addSubview(loadingView)
75 NSLayoutConstraint.activate([
76 loadingView.topAnchor.constraint(equalTo: view.topAnchor),
77 loadingView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
78 loadingView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
79 loadingView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
80 ])
81 }
82
83 private func simulateNetworkRequest() {
84 // Simulate a 1.5 second network delay, then show content
85 Task { @MainActor in
86 try? await Task.sleep(for: .seconds(1.5))
87 showContent()
88 }
89 }
90
91 private func showContent() {
92 loadingView.removeFromSuperview()
93 collectionView.isHidden = false
94
95 // Display all items in a grid layout
96 let gridSection = SeeAllGridSection(
97 title: sectionTitle,
98 items: contentItems
99 )
100 setSections([gridSection], animated: true)
101 }
102 }
103
104 // MARK: - Grid Section for "See All" page
105
106 @MainActor
107 private final class SeeAllGridSection: JomloSection {
108
109 let identifier: JomloSectionID
110 var onReloadNeeded: (@MainActor @Sendable () -> Void)?
111
112 private let title: String
113 private let gridItems: [PosterItem]
114
115 init(title: String, items: [PosterItem]) {
116 self.identifier = JomloSectionID("see-all-grid")
117 self.title = title
118 self.gridItems = items
119 }
120
121 func items() -> [any JomloItem] {
122 gridItems
123 }
124
125 func layoutSection(
126 environment: NSCollectionLayoutEnvironment,
127 sectionIndex: Int
128 ) -> NSCollectionLayoutSection {
129 let columns = environment.container.contentSize.width > 500 ? 4 : 3
130 return JomloLayoutFactory.grid(
131 columns: columns,
132 itemHeight: .absolute(220),
133 interItemSpacing: 12,
134 headerHeight: nil
135 )
136 }
137
138 func cell(
139 for item: any JomloItem,
140 in collectionView: UICollectionView,
141 at indexPath: IndexPath
142 ) -> UICollectionViewCell {
143 guard let posterItem = item as? PosterItem else { return UICollectionViewCell() }
144 return collectionView.dequeueJomloCell(PosterCell.self, for: indexPath, item: posterItem)
145 }
146
147 var displayAnimation: JomloAnimator.Animation? {
148 JomloAnimator.moveUpWithFade(offset: 20, duration: 0.4, delayFactor: 0.03)
149 }
150 }