-
-
Save KazaiMazai/ca9e18f76e02ff9d17c99846ab8cea1c to your computer and use it in GitHub Desktop.
import UIKit | |
import SwiftUI | |
public protocol SectionProtocol: Hashable { | |
associatedtype Item: Hashable | |
var items: [Item] { get } | |
} | |
extension CollectionView { | |
public typealias DataSource = UICollectionViewDiffableDataSource<Section, Item> | |
public typealias SnapShot = NSDiffableDataSourceSnapshot<Section, Item> | |
public typealias CellProvider = DataSource.CellProvider | |
public typealias SupplementaryViewProvider = | |
(SnapShot, UICollectionView, String, IndexPath) -> UICollectionReusableView? | |
public typealias CollectionViewProvider = () -> UICollectionView | |
public typealias CollectionViewUpdateCompleteHandler = (UICollectionView) -> Void | |
} | |
public struct CollectionView<Section, Item> | |
where | |
Section: SectionProtocol, | |
Section.Item == Item { | |
private let collectionViewProvider: CollectionViewProvider | |
private let cellProvider: CellProvider | |
private let supplementaryViewProvider: SupplementaryViewProvider? | |
private let updateCompleteHandler: CollectionViewUpdateCompleteHandler? | |
private var animateCollectionUpdates: Bool = true | |
private let sections: [Section] | |
public init(sections: [Section], | |
collectionViewProvider: @escaping CollectionViewProvider, | |
cellProvider: @escaping CellProvider, | |
supplementaryViewProvider: SupplementaryViewProvider? = nil, | |
updateCompleteHandler: CollectionViewUpdateCompleteHandler? = nil) { | |
self.collectionViewProvider = collectionViewProvider | |
self.cellProvider = cellProvider | |
self.sections = sections | |
self.supplementaryViewProvider = supplementaryViewProvider | |
self.updateCompleteHandler = updateCompleteHandler | |
} | |
} | |
extension CollectionView: UIViewRepresentable { | |
public func makeCoordinator() -> Coordinator { | |
Coordinator() | |
} | |
public func makeUIView(context: UIViewRepresentableContext<CollectionView>) -> UICollectionView { | |
let collectionView = collectionViewProvider() | |
let datasource = DataSource(collectionView: collectionView, | |
cellProvider: cellProvider) | |
datasource.setSupplementaryViewProvider(with: supplementaryViewProvider) | |
context.coordinator.datasource = datasource | |
return collectionView | |
} | |
public func updateUIView(_ uiView: UICollectionView, | |
context: UIViewRepresentableContext<CollectionView>) { | |
context.coordinator.applySnapshotInBackground(sections: sections, | |
animated: animateCollectionUpdates) { | |
updateCompleteHandler?(uiView) | |
} | |
} | |
} | |
extension UICollectionViewDiffableDataSource where SectionIdentifierType: SectionProtocol, | |
SectionIdentifierType.Item == ItemIdentifierType { | |
func setSupplementaryViewProvider( | |
with provider: CollectionView<SectionIdentifierType, ItemIdentifierType>.SupplementaryViewProvider?) { | |
guard let provider = provider else { | |
supplementaryViewProvider = nil | |
return | |
} | |
supplementaryViewProvider = { [weak self] (collecion, kind, idx) in | |
guard let self = self else { | |
return nil | |
} | |
return provider(self.snapshot(), collecion, kind, idx) | |
} | |
} | |
} | |
public extension CollectionView { | |
func animatingDifferences(_ animated: Bool) -> Self { | |
var selfCopy = self | |
selfCopy.animateCollectionUpdates = animated | |
return selfCopy | |
} | |
} |
import UIKit | |
import SwiftUI | |
extension CollectionView { | |
public class Coordinator: NSObject { | |
var datasource: DataSource? | |
private let updateQueue = DispatchQueue( | |
label: "CollectionView.Coordinator.Update.Queue", | |
qos: .userInteractive) | |
func applySnapshotInBackground(sections: [Section], | |
animated: Bool, | |
complete: @escaping () -> Void) { | |
updateQueue.async { [weak self] in | |
guard let self = self else { | |
DispatchQueue.main.async { | |
complete() | |
} | |
return | |
} | |
self.applySnapshot(sections: sections, animated: animated) { | |
DispatchQueue.main.async { | |
complete() | |
} | |
} | |
} | |
} | |
} | |
} | |
extension CollectionView.Coordinator { | |
private func applySnapshot(sections: [Section], | |
animated: Bool, | |
complete: @escaping () -> Void) { | |
guard let datasource = self.datasource else { | |
complete() | |
return | |
} | |
var snapshot = CollectionView.SnapShot() | |
snapshot.appendSections(sections) | |
sections.forEach { | |
snapshot.appendItems($0.items, toSection: $0) | |
} | |
datasource.apply(snapshot, animatingDifferences: animated) { | |
complete() | |
} | |
} | |
} |
public protocol ReusableView { | |
} | |
public extension ReusableView { | |
static var reuseIdentifier: String { | |
"\(self)" | |
} | |
} | |
public class HostingCollectionViewCell<Content: View>: UICollectionViewCell | |
where Content: ReusableView { | |
private var hostingController: UIHostingController<Content>? | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
} | |
required init?(coder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
public static var reuseIdentifier: String { | |
String(describing: Content.reuseIdentifier) | |
} | |
public func set(rootView: Content) { | |
guard let host = hostingController else { | |
let host = UIHostingController(rootView: rootView) | |
let parentController = resolveParentViewController() | |
parentController?.addChild(host) | |
addSubview(host.view) | |
host.view.translatesAutoresizingMaskIntoConstraints = false | |
NSLayoutConstraint.activate([ | |
leadingAnchor.constraint(equalTo: host.view.leadingAnchor), | |
trailingAnchor.constraint(equalTo: host.view.trailingAnchor), | |
topAnchor.constraint(equalTo: host.view.topAnchor), | |
bottomAnchor.constraint(equalTo: host.view.bottomAnchor) | |
]) | |
parentController.map { host.didMove(toParent: $0) } | |
return | |
} | |
host.rootView = rootView | |
host.view.invalidateIntrinsicContentSize() | |
} | |
deinit { | |
hostingController?.willMove(toParent: nil) | |
hostingController?.view.removeFromSuperview() | |
hostingController?.removeFromParent() | |
hostingController = nil | |
} | |
} | |
fileprivate extension UIView { | |
func resolveParentViewController() -> UIViewController? { | |
var parentResponder: UIResponder? = self | |
while parentResponder != nil { | |
parentResponder = parentResponder?.next | |
if let viewController = parentResponder as? UIViewController { | |
return viewController | |
} | |
} | |
return nil | |
} | |
} |
Could you please provide a more detailed example of how to use the Collection View?
Could you please provide a more detailed example of how to use the Collection View?
I think I can prepare something but I'm quite busy so it won't be very fast
Can you give me an example of your use of this Collection view?
Here is an updated version:
https://gist.github.com/KazaiMazai/d9458293c0ef2006bb39958dff624f08
As well as the updated post with some comments:
@KazaiMazai - I have been using your updated implementation of CollectionView and I am running into some issues. 1) The functions CollectionView.animateDifferences(...), .onUpdate(...), and .collectionViewDelegate(...) need to return selfCopy rather than self. 2) The UICollectionViewDelegate is held as a weak reference and is therefore nil upon returning from CollectionView.makeUIView(...). I have worked around these issues by modifying your code specifically for my use (not a general solution). If you would like to resolve the issues, I would be happy to give you feedback on the fix. Thanks for your efforts.
@KazaiMazai - I have been using your updated implementation of CollectionView and I am running into some issues. 1) The functions CollectionView.animateDifferences(...), .onUpdate(...), and .collectionViewDelegate(...) need to return selfCopy rather than self. 2) The UICollectionViewDelegate is held as a weak reference and is therefore nil upon returning from CollectionView.makeUIView(...). I have worked around these issues by modifying your code specifically for my use (not a general solution). If you would like to resolve the issues, I would be happy to give you feedback on the fix. Thanks for your efforts.
@jafienberg
Hey! Just noticed you reply that I missed a year ago. Thanks for pointing it out. Fixed the issues in the latest revision.
Howdy! You can consider something like this:
BTW, I also recommend to have a look at the post with more details of the above here
Ping me if you need some more help, I will provide a more detailed example