YCollectionView is a elegant swiftUI CollectionView built off UICollectionView. It was designed to solve performance issues associated with SwiftUI stack and scroll view nesting by achieving elegant scrolling and cell resuablity that UICollectionVew provides. It allows cells and supplementary views to be built in SwiftUI and provides built in basic paganation logic, pull to refresh and on cell tap functionally. YCollectionView was built off https://github.com/defagos/SwiftUICollection sample project for his article for Building a Collection For SwiftUI
- Pagination/Infinite Scrolling vertically and horizontally
- Cells and Supplementary views are built with swiftUI Views
- Fully customizable sections using compositional layouts
- Uses diffable data source as collectionViews data source
- Built in pull to refresh added to view
- iOS 14.0+
- Xcode 12.0
YCollectionview is available via Swift Package Manger
Using Xcode 12, go to File -> Swift Packages -> Add Package Dependecy and enter
and enter https://github.com/stshelton/YCollectionView
Heres the most basic implentation of YCollectionView. If you'd like to see a more complex implentation that shows pagination refer to the Sample Project
• Create Cell and Section Data that conform to hashable protocol
struct BasicDemoData: Hashable{
var id: UUID = UUID()
var title: String
}
struct BasicSectionData: Hashable{
var id: UUID = UUID()
var extraSectionInfo: String = ""
}
• Create CollectionViewSection, which contains an array of hashable objects for cells(BasicDemoData) and a hashable object for section(BasicSectionData)
//section
typealias Section = CollectionViewSection<AnyHashable, AnyHashable>
//basic Section
var basicSection = Section(section: BasicSectionData(), items: [BasicDemoData(title: "Test 1"),
BasicDemoData(title: "Test 2"), BasicDemoData(title: "Test 3"), BasicDemoData(title: "Test 4")])
• Create Basic NSCollectionLayoutSection for compositional layout
private func CreateBasicLayoutSection()-> NSCollectionLayoutSection{
//1
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
//2
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(0.3))
//3
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize,subitems: [item])
let section = NSCollectionLayoutSection(group: group)
return section
}
• Full Example
import SwiftUI
import YCollectionView
struct BasicYCollectionViewImplementation: View {
//section
typealias Section = CollectionViewSection<AnyHashable, AnyHashable>
//basic Section
var basicSection = Section(section: BasicSectionData(), items: [BasicDemoData(title: "Test 1"), BasicDemoData(title: "Test 2"),
BasicDemoData(title: "Test 3"), BasicDemoData(title: "Test 4")])
struct BasicDemoData: Hashable{
var id: UUID = UUID()
var title: String
}
struct BasicSectionData: Hashable{
var id: UUID = UUID()
var extraSectionInfo: String = ""
}
var body: some View {
CollectionView(rows: [basicSection], collectionViewBackgroundColor: UIColor.white, addPullToRefresh: true) { sectionIndex, layoutEnviroment in
return CreateBasicLayoutSection()
} cell: { indexPath, cellData in
switch cellData{
case let basicCellData as BasicDemoData:
VStack{
Text(basicCellData.title)
}
default:
EmptyView()
}
} supplementaryView: { kind, indexPath, sectionData in
//header and footer views
} loadMoreCells: { loadingSection in
//logic to load more cells within section
} loadMoreSections: {
//logic to load more sections
} clickedDetailPage: { item in
//current cell clicked and data associated with cell
} pullToRefresh: { refreshControl in
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
refreshControl.endRefreshing()
}
}
}
private func CreateBasicLayoutSection()-> NSCollectionLayoutSection{
//1
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
//2
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),heightDimension: .fractionalHeight(0.3))
//3
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize,subitems: [item])
let section = NSCollectionLayoutSection(group: group)
return section
}
}
YCollectionView uses willDisplay
method of UICollectionViewDelegate to check if last cell within data source is or inherits from LoadMoreCellsData
object. If you'd like to add more data to the LoadMoreCellsData
create a new object that inherits from LoadMoreCellsData
and add aditional data needed within that class.
class OverrideLoadingCell: LoadMoreCellsData{
var extraData: String
init(extraData: String){
self.extraData = extraData
super.init()
}
}
Accessing the data
cell: { indexPath, cellData in
switch cellData {
case let demoCellData as DemoCellData:
BasicCellView(demoCellData: demoCellData)
case let loadingMoreData as OverrideLoadingCell:
LoadingCellView(loadingData: loadingMoreData)
default:
EmptyView()
}
}
The same is true with loading more sections. YCollectionView uses willDisplay
method of UICollectionViewDelegate to check if last cell in section is or inherits from LoadMoreSectionsCellData
. With a section intented to load more sections I would only pass in one cell which is or inherits from LoadMoreSectionsCellData
.
class OverrideSectionLoadingCell: LoadMoreSectionsCellData{
var extraData: String = ""
init(extraData: String){
self.extraData = extraData
}
}
Accessing the data
cell: { indexPath, cellData in
switch cellData {
case let demoCellData as DemoCellData:
BasicCellView(demoCellData: demoCellData)
case let loadingMoreData as OverrideLoadingCell:
LoadingCellView(loadingData: loadingMoreData)
case let loadingMoreSections as OverrideSectionLoadingCell:
LoadingNewSectionCellView(loadingSectionData: loadingMoreSections)
default:
EmptyView()
}
}
We would love you for the contribution to YCollectionView, check the LICENSE
file for more info.
Spencer Shelton - Stshelton1993@example.com
Distributed under the MIT license. See LICENSE
for more information.