How to add edge insets to UICollectionView's contentView when using UICollectionViewCompositionalLayout of type .list

129 views Asked by At

I have the following problem: I want to add horizontal insets to a collectionView's contentView, but when I set the contentInset to UIEdgeInsets(top: .zero, left: 64, bottom: .zero, right: 64) for example, it scrolls the contentView rather than reducing its width. I'm using a UICollectionViewCompositionalLayout of type .list.

I can set the edge insets when using a UICollectionViewCompositionalLayout that uses a custom NSCollectionLayoutSection (I apply the inset to the section's group), but when using Apple's default list designs I don't seem to be able to.

How can I use Apple's default designs for the various states in lists (editing, reordering) and add edge insets in order to reduce the contentView's width?

Here the viewController I'm working with:

import SwiftUI
import UIKit

final class AppearancePlainViewController: UIViewController {

    struct Contact: Identifiable, Hashable {
        let id: UUID = UUID()
        let name: String = "Arthur"
        let surname: String = "Dent"
        let image: UIImage = UIImage(systemName: "person")!
    }
    
    private enum ListSection: Int {
        case main
    }
    
    private enum ListRow: Hashable {
        enum ListContentConfiguration: Hashable {
            case cell
        }
        
        case example(ListContentConfiguration, Contact)
        case description(String)
    }
    
    private typealias Section = ListSection
    private typealias Row = ListRow
    private typealias DataSource = UICollectionViewDiffableDataSource<Section, Row>
    private typealias Snapshot = NSDiffableDataSourceSnapshot<Section, Row>
    
    private let rows: [ListRow] = [
        .example(.cell, Contact()),
        .description(".cell"),
        .example(.cell, Contact()),
        .description(".cell1"),
        .example(.cell, Contact()),
        .description(".cell2"),
        .example(.cell, Contact()),
        .description(".cell3"),
        .example(.cell, Contact()),
        .description(".cell4"),
        .example(.cell, Contact()),
        .description(".cell5"),
        .example(.cell, Contact()),
        .description(".cell6"),
        .example(.cell, Contact()),
        .description(".cell7"),
        .example(.cell, Contact()),
        .description(".cell8"),
    ]
    
    private weak var collectionView: UICollectionView!
    private var datasource: DataSource!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Appearance.plain"
        view.backgroundColor = .orange
        configureViewHierarchy()
        configureDataSource()
        loadData()
        
        collectionView.isEditing = true
    }

    private func configureViewHierarchy() {
        let layout = createLayout()
        
        let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.delegate = self
        collectionView.contentInset = UIEdgeInsets(top: .zero, left: 64, bottom: .zero, right: 64)
        view.addSubview(collectionView)
        self.collectionView = collectionView
        
        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
        ])
    }
    
    private func createLayout() -> UICollectionViewLayout {
        var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
        let layout = UICollectionViewCompositionalLayout.list(using: configuration)
        return layout
    }
    
    private func configureDataSource() {
        let descriptionRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { cell, _, item in
            cell.contentConfiguration = UIHostingConfiguration {
                ZStack {
                    Text(item)
                        .font(.caption2)
                        .padding(.horizontal, 2.0)
                }
                .foregroundColor(Color(UIColor.systemBackground))
                .background(Color(UIColor.label))
                .cornerRadius(2.0)
                .padding(.bottom, 2.0)
            }
            var backgroundConfig = UIBackgroundConfiguration.listPlainCell()
            backgroundConfig.backgroundColor = .blue
            cell.backgroundConfiguration = backgroundConfig
        }
        
        let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Contact> { cell, _, item in
            var configuration = UIListContentConfiguration.cell()
            configuration.text = item.name
            configuration.secondaryText = item.surname
            configuration.image = item.image
            cell.contentConfiguration = configuration
            cell.backgroundConfiguration = .listPlainCell()
        }
        
        datasource = DataSource(collectionView: collectionView) { collectionView, indexPath, identifier -> UICollectionViewCell in
            
            switch identifier {
            case .example(let configType, let contact):
                switch configType {
                case .cell:
                    return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: contact)
                }
            case .description(let string):
                return collectionView.dequeueConfiguredReusableCell(using: descriptionRegistration, for: indexPath, item: string)
            }
        }
    }
    
    private func loadData() {
        var snapshot = Snapshot()
        snapshot.appendSections([.main])
        snapshot.appendItems(rows, toSection: .main)
        datasource.applySnapshotUsingReloadData(snapshot)
    }
}

extension AppearancePlainViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, canEditItemAt indexPath: IndexPath) -> Bool {
        return true
    }
    
    func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt currentIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath {
        return proposedIndexPath
    }
}
1

There are 1 answers

0
Sweeper On

I don't think the compositional layout API cares about UICollectionView.contentInsets at all.

You can create a NSCollectionLayoutSection with your own custom contentInsets:

private func createLayout() -> UICollectionViewLayout {
    UICollectionViewCompositionalLayout { _, env in
        let section = NSCollectionLayoutSection.list(using: .init(appearance: .insetGrouped), layoutEnvironment: env)
        section.contentInsets = .init(top: 0, leading: 64, bottom: 0, trailing: 64)
        return section
    }
}