Skip to content

Instantly share code, notes, and snippets.

@prasadpamidi
Last active October 30, 2022 11:30
Show Gist options
  • Save prasadpamidi/829e636d4697fda025bb0795ee81e355 to your computer and use it in GitHub Desktop.
Save prasadpamidi/829e636d4697fda025bb0795ee81e355 to your computer and use it in GitHub Desktop.
Issue with large navigation bar and collection view
import UIKit
class SegmentedControlSupplementaryView: UICollectionReusableView {
let segmentControl = UISegmentedControl(items: ["Item 1", "Items 2", "Items 3"])
static let reuseIdentifier = "segmented-supplementary-reuse-identifier"
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .white
configure()
}
required init?(coder: NSCoder) {
fatalError()
}
}
extension SegmentedControlSupplementaryView {
func configure() {
addSubview(segmentControl)
segmentControl.translatesAutoresizingMaskIntoConstraints = false
let inset = CGFloat(10)
NSLayoutConstraint.activate([
segmentControl.leadingAnchor.constraint(equalTo: leadingAnchor, constant: inset),
segmentControl.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -inset),
segmentControl.topAnchor.constraint(equalTo: topAnchor, constant: inset),
segmentControl.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -inset)
])
self.setNeedsLayout()
}
}
let sectionHeaderElementKind = "sticky-header-element-kind"
let itemsPerPage = 15
let section = "Sticky Header"
let sectionHeader = "Section Header"
class StickyHeaderViewController: UIViewController {
var dataSource: UICollectionViewDiffableDataSource<String, Int>? = nil
var collectionView: UICollectionView?
// initial data
var items: [Int] = []
var currentOffset: Int = 0
fileprivate lazy var refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(performRefresh(_:)), for: UIControl.Event.valueChanged)
return refreshControl
}()
/// A background serial queue is used to calculate diffing and apply snapshot to the registered collection view
fileprivate let serialQueue = DispatchQueue(label: "assetlist.update.serial.queue")
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationItem.title = "Sticky Section Headers"
self.edgesForExtendedLayout = .all
self.extendedLayoutIncludesOpaqueBars = true
self.navigationItem.searchController = UISearchController()
setUpNavBarAppearance()
setUpCollectionView()
configureDataSource()
}
func setUpNavBarAppearance() {
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .white
appearance.shadowColor = .clear
self.navigationController?.navigationBar.standardAppearance = appearance
self.navigationController?.navigationBar.scrollEdgeAppearance = appearance
}
func layout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(44))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = 5
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(44)),
elementKind: sectionHeaderElementKind,
alignment: .top)
sectionHeader.pinToVisibleBounds = true
sectionHeader.zIndex = 2
section.boundarySupplementaryItems = [sectionHeader]
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
func setUpCollectionView() {
let collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout())
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.backgroundColor = .systemBackground
collectionView.delegate = self
collectionView.register(ListCell.self, forCellWithReuseIdentifier: ListCell.reuseIdentifier)
collectionView.register(SegmentedControlSupplementaryView.self,
forSupplementaryViewOfKind: sectionHeaderElementKind,
withReuseIdentifier: SegmentedControlSupplementaryView.reuseIdentifier)
self.view.addSubview(collectionView)
collectionView.refreshControl = refreshControl
self.collectionView = collectionView
}
func configureDataSource() {
guard let collectionView = self.collectionView else {
return
}
dataSource = UICollectionViewDiffableDataSource<String, Int>(collectionView: collectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, identifier: Int) -> UICollectionViewCell? in
// Get a cell of the desired kind.
guard let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: ListCell.reuseIdentifier,
for: indexPath) as? ListCell else { fatalError("Cannot create new cell") }
// Populate the cell with our item description.
cell.label.text = "\(indexPath.section),\(indexPath.item)"
return cell
}
dataSource?.supplementaryViewProvider = { [weak self]
(collectionView: UICollectionView, kind: String, indexPath: IndexPath) -> UICollectionReusableView? in
// Get a supplementary view of the desired kind.
guard let header = collectionView.dequeueReusableSupplementaryView(
ofKind: kind,
withReuseIdentifier: SegmentedControlSupplementaryView.reuseIdentifier,
for: indexPath) as? SegmentedControlSupplementaryView else { fatalError("Cannot create new header") }
header.segmentControl.addTarget(self, action: #selector(self?.segmentChanged), for: .valueChanged)
// Return the view.
return header
}
var snapshot = NSDiffableDataSourceSnapshot<String, Int>()
snapshot.appendSections([section])
items = Array(currentOffset..<currentOffset + 2*itemsPerPage)
currentOffset += 2*itemsPerPage
snapshot.appendItems(items)
serialQueue.async { [weak self] in
self?.dataSource?.apply(snapshot, animatingDifferences: true)
}
}
@objc func segmentChanged() {
self.collectionView?.setContentOffset(.zero, animated: true)
}
@objc
func performRefresh(_ refreshControl: UIRefreshControl?) {
getNextPage()
// if not nil make sure to call end refreshing.
DispatchQueue.main.async {
refreshControl?.endRefreshing()
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.navigationController?.pushViewController(StickyHeaderViewController(), animated: true)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment