Last active
November 13, 2023 14:39
-
-
Save satishVekariya/eb72196e4289eb7b20a32d2b68afa7ee to your computer and use it in GitHub Desktop.
A SwiftUI wrapper of UICollectionView that manages an ordered collection of data items and presents them using customizable layouts.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CollectionViewRepresentable.swift | |
// | |
// | |
// Created by Satish Vekariya on 12/11/2023. | |
// | |
import SwiftUI | |
import UIKit | |
/// A SwiftUI wrapper of UICollectionView that manages an ordered collection of data items and presents them using customizable layouts. | |
public struct CollectionViewRepresentable<Item, Content: View>: UIViewRepresentable { | |
@Binding var items: [Item] | |
let layout: LayoutKind | |
let alwaysBounceVertical: Bool | |
/// SwiftUI view provider | |
let contentProvider: (Item, IndexPath) -> Content | |
// Helper Callbacks | |
let shouldHighlightItemAt: ((UICollectionViewCell?, Item, IndexPath) -> Bool)? | |
let didHighlightItemAt: ((UICollectionViewCell?, Item, IndexPath) -> ())? | |
let didUnhighlightItemAt: ((UICollectionViewCell?, Item, IndexPath) -> ())? | |
let shouldSelectItemAt: ((UICollectionViewCell?, Item, IndexPath) -> Bool)? | |
let didSelectItemAt: ((UICollectionViewCell?, Item, IndexPath) -> ())? | |
public init( | |
_ items: Binding<[Item]>, | |
layout: LayoutKind, | |
alwaysBounceVertical: Bool = false, | |
@ViewBuilder contentProvider: @escaping (Item, IndexPath) -> Content, | |
shouldHighlightItemAt: ((UICollectionViewCell?, Item, IndexPath) -> Bool)? = nil, | |
didHighlightItemAt: ((UICollectionViewCell?, Item, IndexPath) -> ())? = nil, | |
didUnhighlightItemAt: ((UICollectionViewCell?, Item, IndexPath) -> ())? = nil, | |
shouldSelectItemAt: ((UICollectionViewCell?, Item, IndexPath) -> Bool)? = nil, | |
didSelectItemAt: ((UICollectionViewCell?, Item, IndexPath) -> ())? = nil | |
) { | |
self._items = items | |
self.layout = layout | |
self.alwaysBounceVertical = alwaysBounceVertical | |
self.contentProvider = contentProvider | |
self.shouldHighlightItemAt = shouldHighlightItemAt | |
self.didHighlightItemAt = didHighlightItemAt | |
self.didUnhighlightItemAt = didUnhighlightItemAt | |
self.shouldSelectItemAt = shouldSelectItemAt | |
self.didSelectItemAt = didSelectItemAt | |
} | |
public func makeUIView(context: Context) -> UICollectionView { | |
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout.create()) | |
collectionView.dataSource = context.coordinator | |
collectionView.delegate = context.coordinator | |
collectionView.alwaysBounceVertical = alwaysBounceVertical | |
collectionView.backgroundColor = nil | |
return collectionView | |
} | |
public func updateUIView(_ uiView: UICollectionView, context: Context) { | |
uiView.reloadData() | |
} | |
// Create a coordinator to handle the data source and delegate | |
public func makeCoordinator() -> Coordinator { | |
return Coordinator(self) | |
} | |
public class Coordinator: NSObject, UICollectionViewDataSource, UICollectionViewDelegate { | |
var parent: CollectionViewRepresentable | |
private var userInteractionCellRegistration: UICollectionView.CellRegistration<UICollectionViewListCell, Item>! | |
init(_ parent: CollectionViewRepresentable) { | |
self.parent = parent | |
super.init() | |
userInteractionCellRegistration = .init { [unowned self] cell, indexPath, item in | |
cell.contentConfiguration = UIHostingConfiguration { | |
self.parent.contentProvider(item, indexPath) | |
} | |
.margins(.all, 0) | |
cell.backgroundConfiguration = .clear() | |
} | |
} | |
// UICollectionViewDataSource methods | |
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { | |
return parent.items.count | |
} | |
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { | |
let item = parent.items[indexPath.row] | |
let cell = collectionView.dequeueConfiguredReusableCell( | |
using: userInteractionCellRegistration, | |
for: indexPath, | |
item: item | |
) | |
return cell | |
} | |
public func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool { | |
let cell = collectionView.cellForItem(at: indexPath) | |
let item = parent.items[indexPath.row] | |
return parent.shouldHighlightItemAt?(cell, item, indexPath) ?? false | |
} | |
public func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) { | |
let cell = collectionView.cellForItem(at: indexPath) | |
let item = parent.items[indexPath.row] | |
parent.didHighlightItemAt?(cell, item, indexPath) | |
} | |
public func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) { | |
let cell = collectionView.cellForItem(at: indexPath) | |
let item = parent.items[indexPath.row] | |
parent.didUnhighlightItemAt?(cell, item, indexPath) | |
} | |
public func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { | |
let cell = collectionView.cellForItem(at: indexPath) | |
let item = parent.items[indexPath.row] | |
return parent.shouldHighlightItemAt?(cell, item, indexPath) ?? true | |
} | |
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { | |
let cell = collectionView.cellForItem(at: indexPath) | |
let item = parent.items[indexPath.row] | |
parent.didSelectItemAt?(cell, item, indexPath) | |
} | |
} | |
} | |
extension CollectionViewRepresentable { | |
public enum LayoutKind { | |
case list(appearance: UICollectionLayoutListConfiguration.Appearance) | |
case carousel | |
func create() -> UICollectionViewLayout { | |
switch self { | |
case .list(let appearance): | |
return UICollectionViewCompositionalLayout.list(using: .init(appearance: appearance)) | |
case .carousel: | |
return CarouselLayoutGenerator.create() | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment