How to Debug SectionKit Collections: A Step-by-Step Guide
To debug SectionKit collection views, verify the view has a non-zero frame, ensure sections are bound with valid sectionInjection instances, inspect the layout plugin pipeline, and leverage the requestPublishers system to observe lifecycle events.
SectionKit is a declarative, data-driven framework built on top of UIKit that simplifies building complex collection views. When cells go missing, layouts break, or updates trigger crashes, understanding the internal mechanism of SKCManager and its binding lifecycle is essential to resolve issues efficiently.
Understanding the SectionKit Architecture
Before debugging, recognize the three critical layers in the linhay/sectionkit repository:
SKCollectionView(Sources/SectionUI/CollectionView/SectionCollectionView/SKCollectionView.swift): TheUICollectionViewsubclass that wires the manager and plugin-based layout system.SKCManager(Sources/SectionKit/CollectionBase/SKCManager.swift): The central orchestrator handling section binding, data-source forwarding, and batch-update coordination.SKCLayoutPlugins(Sources/SectionUI/CollectionView/SectionCollectionView/FlowLayout/SKCLayoutPlugins.swift): The modular system that calculates section-specific layout modes.
Most debugging issues stem from disconnects between these layers—particularly when the collection view isn't ready or sections aren't properly bound to the manager.
Verify the Collection View Has a Non-Zero Frame
SKCManager defers all layout-related processing until the collection view has a valid size. In SKCManager.swift, the setup(request:) method explicitly checks sectionView.frame.width > 0 && sectionView.frame.height > 0 before executing layout-related requests.
If you create SKCollectionView programmatically, ensure constraints are active before calling manager methods:
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
// Wait for layout cycle before appending sections
Check Section Binding and Injection
Sections must be bound to a manager via bind(sections:start:) in SKCManager.swift. This binding injects a SKCSectionInjection object containing the section index and view reference into each section.
If a section's sectionInjection property remains nil, UI updates are silently ignored. In DEBUG builds, the security(check:) method asserts that sectionInjection != nil, catching unbound sections early.
To verify binding:
- Set a breakpoint after
manager.append(section)ormanager.bind(sections:) - Inspect
section.sectionInjection— it should be non-nil - Check the console for assertion failures when running with
-Ononeoptimization
Inspect the Layout Plugin Collection
Missing or misconfigured plugins often cause incorrect item sizes or absent headers/footers. SKCManager collects plugins per-section via collectSectionLayoutPlugins() and merges them with global plugins set via set(pluginModes:).
If layout appears wrong:
- Verify
pluginsModescontains the expectedSKCLayoutPlugins.Modeobjects - Check that custom sections implement the layout protocol methods returning correct plugin configurations
- Ensure global plugins registered via
set(pluginModes:)aren't overriding section-specific settings unintentionally
Monitor Layout Cycles with Request Publishers
SKCollectionView exposes Combine publishers that emit during critical lifecycle events. Subscribe to requestPublishers.layoutSubviews to confirm when the view completes a layout pass and has valid dimensions.
import Combine
var cancellables = Set<AnyCancellable>()
collectionView.requestPublishers.layoutSubviews
.sink { _ in
print("Layout complete - frame: \(self.collectionView.frame)")
// Safe to perform scroll-to or section updates here
}
.store(in: &cancellables)
This publisher fires after layoutSubviews() completes, indicating the manager will now process queued requests.
Analyze Batch Update Behavior
SKCManager decides between incremental updates and full reloadData based on its static Configuration. The method pick(_:completion:) wraps performBatchUpdates, while insert(_:at:), remove(_:), and reload(_:) route through the manager's update logic.
To isolate update bugs:
- Temporarily set
SKCManager.configuration.replaceInsertWithReloadData = falseto force incremental batch updates - Breakpoint inside
pick(_:completion:)to verify the completion handler fires - Check if specific sections trigger crashes when updated—often indicating a mismatch between data source counts and the manager's internal state
Enable Debug Assertions
SectionKit includes runtime checks that only fire in DEBUG builds. The security(check:) method in SKCManager.swift validates that section injections exist before sensitive operations.
Run your app with Debug configuration (-Odebug or -Onone) to surface illegal states immediately rather than silently failing. If assertions trigger, the stack trace points directly to unbound sections.
Track Deferred Scroll Requests
Scroll requests made before the collection view is ready are deferred using SKRequestID. When you call manager.scroll(to:row:animated:), if the view lacks a valid frame, the request stores in afterLayoutSubviewsRequests and executes after the next layoutSubviews cycle.
To verify deferred execution:
- Call
manager.scroll(to:row:animated:)early inviewDidLoad - Set a breakpoint in
perform(of:)inSKCManager.swift - Confirm the request executes only after
layoutSubviewscompletes
Practical Debugging Example
This comprehensive snippet demonstrates checking frame validity, monitoring layout cycles, and verifying section binding:
import SectionKit
import SectionUI
import Combine
class DebugViewController: UIViewController {
private let collectionView = SKCollectionView()
private var manager: SKCManager { collectionView.manager }
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
// Setup with constraints
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
// Monitor layout cycles
collectionView.requestPublishers.layoutSubviews
.sink { [weak self] _ in
guard let self = self else { return }
print("📐 Frame valid: \(self.collectionView.frame.size)")
// Check pending requests
print("⏳ Deferred requests: \(self.manager.afterLayoutSubviewsRequests.count)")
}
.store(in: &cancellables)
// Create and bind section
let section = MySection()
manager.append(section)
// Force binding verification
manager.reload()
// Attempt scroll (may be deferred)
DispatchQueue.main.async {
_ = self.manager.scroll(to: 0, row: 0, animated: true)
}
}
}
class MySection: SKCSectionProtocol {
// Section implementation
}
Key debugging points in this code:
- Lines 17-24: Ensure frame is valid before manager processes requests
- Line 28: Observes
layoutSubviewsto confirm size availability - Line 36: Appends section, triggering
bind(sections:start:)injection - Line 39:
reload()verifies binding through thesecurity(check:)path - Line 43: Demonstrates deferred scroll requests via
afterLayoutSubviewsRequests
Summary
- Verify frame validity:
SKCManagerrequires non-zero width/height insetup(request:)before processing layout requests. - Ensure section binding: Sections must have non-nil
sectionInjectionafterbind(sections:start:); DEBUG assertions catch failures. - Check plugins: Validate
collectSectionLayoutPlugins()returns expected modes and globalpluginModesdon't conflict. - Observe lifecycle: Use
requestPublishers.layoutSubviewsto confirm when the collection view is ready for updates. - Control batch updates: Toggle
SKCManager.configuration.replaceInsertWithReloadDatato test incremental vs full reloads. - Track deferred work: Scroll requests queue in
afterLayoutSubviewsRequestsuntillayoutSubviewscompletes.
Frequently Asked Questions
Why are my SectionKit sections not displaying any cells?
Sections fail to display when sectionInjection remains nil, typically because the collection view has a zero frame when sections are added. Verify collectionView.frame.width > 0 in viewDidLayoutSubviews before calling manager methods. According to the source in SKCManager.swift, the setup(request:) method silently returns if the frame is invalid.
How can I force incremental updates instead of full reloadData?
Set SKCManager.configuration.replaceInsertWithReloadData = false before performing insertions. This forces the manager to use performBatchUpdates via pick(_:completion:) rather than falling back to reloadData. Use this temporarily when debugging to isolate whether specific sections cause crashes during animated updates.
Where are scroll requests stored if the collection view isn't ready?
Deferred scroll requests accumulate in SKCManager.afterLayoutSubviewsRequests, which is an internal array holding SKRequestID objects. These execute automatically after the next layoutSubviews cycle completes, as observed via requestPublishers.layoutSubviews. Breakpoint in perform(of:) to confirm execution timing.
What triggers the debug assertion failures in SectionKit?
The security(check:) method in SKCManager.swift asserts that sectionInjection != nil for each section during sensitive operations. This fires when sections receive UI updates (like reloads) before being properly bound to the manager via bind(sections:start:). Always append sections through manager methods rather than instantiating them in isolation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →