SwiftUI包装UICollectionView并使用合成布局
我试图通过UIViewRepresentable在SwiftUI中实现CollectionView,并将这些SwiftUI视图用作可重用的单元格,但单元格根本没有出现。在SwiftUI中,似乎不可能包装集合视图并使用合成布局,或者可能我遗漏了一些东西 我知道我可以在UIKit中完全实现此CollectionView,只需包装所有视图控制器,但如果我想使用SwiftUI视图作为UICollectionView的单元格,该怎么办 使用UICollectionViewFlowLayout()时,视图显示正确SwiftUI包装UICollectionView并使用合成布局,swift,uicollectionview,swiftui,Swift,Uicollectionview,Swiftui,我试图通过UIViewRepresentable在SwiftUI中实现CollectionView,并将这些SwiftUI视图用作可重用的单元格,但单元格根本没有出现。在SwiftUI中,似乎不可能包装集合视图并使用合成布局,或者可能我遗漏了一些东西 我知道我可以在UIKit中完全实现此CollectionView,只需包装所有视图控制器,但如果我想使用SwiftUI视图作为UICollectionView的单元格,该怎么办 使用UICollectionViewFlowLayout()时,视图显
import SwiftUI
import UIKit
struct CollectionView<Section: Hashable & CaseIterable, Item: Hashable>: UIViewRepresentable {
// MARK: - Properties
let layout: UICollectionViewLayout
let sections: [Section]
let items: [Section: [Item]]
// MARK: - Actions
let snapshot: (() -> NSDiffableDataSourceSnapshot<Section, Item>)?
let content: (_ indexPath: IndexPath, _ item: Item) -> AnyView
// MARK: - Init
init(layout: UICollectionViewLayout,
sections: [Section],
items: [Section: [Item]],
snapshot: (() -> NSDiffableDataSourceSnapshot<Section, Item>)? = nil,
@ViewBuilder content: @escaping (_ indexPath: IndexPath, _ item: Item) -> AnyView) {
self.layout = layout
self.sections = sections
self.items = items
self.snapshot = snapshot
self.content = content
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func generateLayout() -> UICollectionViewLayout {
let itemHeightDimension = NSCollectionLayoutDimension.absolute(44)
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: itemHeightDimension)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: itemHeightDimension)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
func makeUIView(context: Context) -> UICollectionView {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
collectionView.backgroundColor = .clear
collectionView.delegate = context.coordinator
collectionView.register(HostingControllerCollectionViewCell<AnyView>.self, forCellWithReuseIdentifier: HostingControllerCollectionViewCell<AnyView>.reuseIdentifier)
context.coordinator.dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView, cellProvider: context.coordinator.cellProvider)
return collectionView
}
func updateUIView(_ uiView: UICollectionView, context: Context) {
context.coordinator.dataSource.apply( (snapshot ?? reloadSnaphot)() , animatingDifferences: true)
}
func reloadSnaphot() -> NSDiffableDataSourceSnapshot<Section, Item> {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections(Array(items.keys))
items.forEach { (section, items) in
snapshot.appendItems(items, toSection: section)
}
return snapshot
}
class Coordinator: NSObject, UICollectionViewDelegate {
var dataSource: UICollectionViewDiffableDataSource<Section, Item>! = nil
// MARK: - Properties
let parent: CollectionView
// MARK: - Init
init(_ parent: CollectionView) {
self.parent = parent
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Did select item at \(indexPath)")
}
// MARK: - Cell Provider
func cellProvider(collectionView: UICollectionView, indexPath: IndexPath, item: Item) -> UICollectionViewCell? {
print("Providing cell for \(indexPath)")
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HostingControllerCollectionViewCell<AnyView>.reuseIdentifier, for: indexPath) as? HostingControllerCollectionViewCell<AnyView> else {
fatalError("Coult not load cell!")
}
cell.host(parent.content(indexPath, item))
//cell.host(rootView: parent.content(indexPath, item))
return cell
}
}
}
class HostingControllerCollectionViewCell<Content: View> : UICollectionViewCell {
weak var controller: UIHostingController<Content>?
func host(_ view: Content, parent: UIViewController? = nil) {
if let controller = controller {
controller.rootView = view
controller.view.layoutIfNeeded()
} else {
let controller = UIHostingController(rootView: view)
self.controller = controller
controller.view.backgroundColor = .clear
layoutIfNeeded()
parent?.addChild(controller)
contentView.addSubview(controller.view)
controller.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
controller.view.leadingAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.leadingAnchor),
controller.view.trailingAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.trailingAnchor),
controller.view.topAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.topAnchor),
controller.view.bottomAnchor.constraint(equalTo: self.contentView.layoutMarginsGuide.bottomAnchor)
])
if let parent = parent {
controller.didMove(toParent: parent)
}
controller.view.layoutIfNeeded()
}
}
}
导入快捷界面
导入UIKit
结构集合视图:UIViewRepresentable{
//标记:-属性
let布局:UICollectionViewLayout
让我们来看看以下章节:[章节]
出租项目:[部分:[项目]]
//马克:行动
让快照:(()->NSDiffableDataSourceSnapshot)?
让内容:(\uIndeXPath:indexPath,\uItem:item)->AnyView
//MARK:-Init
初始化(布局:UICollectionViewLayout,
章节:[章节],
项目:[部分:[项目]],
快照:(()->NSDiffableDataSourceSnapshot)?=nil,
@ViewBuilder内容:@escaping(indexPath:indexPath,item:item)->AnyView){
self.layout=布局
self.sections=节
self.items=项目
self.snapshot=快照
self.content=内容
}
func makeCoordinator()->Coordinator{
协调员(自我)
}
func generateLayout()->UICollectionViewLayout{
设itemHeightDimension=NSCollectionLayoutDimension.absolute(44)
让itemSize=NSCollectionLayoutSize(宽度维度:。分数宽度(1.0),高度维度:itemHeightDimension)
let item=NSCollectionLayoutItem(layoutSize:itemSize)
让groupSize=NSCollectionLayoutSize(宽度维度:.SectilWidth(1.0),高度维度:itemHeightDimension)
let group=NSCollectionLayoutGroup.horizontal(layoutSize:groupSize,子项:项,计数:1)
let section=NSCollectionLayoutSection(组:组)
section.contentInsets=NSDirectionalEdgeInsets(顶部:0,前导:0,底部:0,尾随:0)
let layout=UICollectionViewCompositionLayout(截面:截面)
返回布局
}
func makeUIView(上下文:context)->UICollectionView{
让collectionView=UICollectionView(帧:.0,collectionViewLayout:UICollectionViewFlowLayout())
collectionView.autoresizingMask=[.flexibleHeight、.flexibleWidth]
collectionView.backgroundColor=.clear
collectionView.delegate=context.coordinator
collectionView.register(HostingControllerCollectionViewCell.self,forCellWithReuseIdentifier:HostingControllerCollectionViewCell.reuseIdentifier)
context.coordinator.dataSource=UICollectionViewDiffableDataSource(collectionView:collectionView,cellProvider:context.coordinator.cellProvider)
返回集合视图
}
func updateUIView(uiView:UICollectionView,context:context){
context.coordinator.dataSource.apply((快照??重新加载snapshot)(),animatingDifferences:true)
}
func reloadSnaphot()->NSDiffableDataSourceSnapshot{
var snapshot=NSDiffableDataSourceSnapshot()
快照.appendSections(数组(items.key))
items.forEach{(节,项)在
snapshot.appendItems(items,toSection:section)
}
返回快照
}
类协调器:NSObject,UICollectionViewDelegate{
变量数据源:UICollectionViewDiffableDataSource!=nil
//标记:-属性
让父对象:CollectionView
//MARK:-Init
init(uu父:CollectionView){
self.parent=parent
}
func collectionView(collectionView:UICollectionView,didSelectItemAt indexPath:indexPath){
打印(“在\(indexPath)处选择了项目”)
}
//标记:-单元提供程序
func cellProvider(collectionView:UICollectionView,indexPath:indexPath,item:item)->UICollectionViewCell{
打印(“为\(indexPath)提供单元格”)
guard let cell=collectionView.dequeueReusableCell(带reuseIdentifier:HostingControllerCollectionViewCell.reuseIdentifier,for:indexPath)作为?HostingControllerCollectionViewCell else{
fatalError(“不能使用称重传感器!”)
}
cell.host(parent.content(indexPath,item))
//host(rootView:parent.content(indexPath,item))
返回单元
}
}
}
类HostingControllerCollectionViewCell:UICollectionViewCell{
弱var控制器:UIHostingController?
func主机(u视图:内容,父级:UIViewController?=nil){
如果让控制器=控制器{
controller.rootView=view
controller.view.layoutIfNeeded()
}否则{
let controller=UIHostingController(rootView:view)
self.controller=控制器
controller.view.backgroundColor=.clear
layoutIfNeeded()
父级?.addChild(控制器)
contentView.addSubview(controller.view)
controller.view.translatesAutoResizezingMaskintoConstraints=false
NSLayoutConstraint.activate([
controller.view.leadingAnchor.constraint(等式:self.contentView.layoutMarginsGuide.leadingAnchor),
controller.view.trailingAnchor.constraint(等式:self.contentView.layoutMarginsGuide.trailingAnchor),
controller.view.topAnchor.constraint(等式:self.contentView.layoutMarginsGuide.topAnchor),
controller.view.bottomAnchor.constraint(等式:self.contentView.layoutMarginsGuide.bottomAnchor)
])
如果let parent=parent{
controller.didMove(toParent:parent)
}
controller.view.layoutIfNeeded()
}
}
}
我已将其重写到UIViewControllerRepresentab中
struct CollectionView<Section: Hashable & CaseIterable, Item: Hashable>: UIViewControllerRepresentable {
// MARK: - Properties
let layout: UICollectionViewLayout
let sections: [Section]
let items: [Section: [Item]]
// MARK: - Actions
let snapshot: (() -> NSDiffableDataSourceSnapshot<Section, Item>)?
let content: (_ indexPath: IndexPath, _ item: Item) -> AnyView
// MARK: - Init
init(layout: UICollectionViewLayout,
sections: [Section],
items: [Section: [Item]],
snapshot: (() -> NSDiffableDataSourceSnapshot<Section, Item>)? = nil,
@ViewBuilder content: @escaping (_ indexPath: IndexPath, _ item: Item) -> AnyView) {
self.layout = layout
self.sections = sections
self.items = items
self.snapshot = snapshot
self.content = content
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> CollectionViewController<Section, Item> {
let controller = CollectionViewController<Section, Item>()
controller.layout = self.layout
controller.snapshotForCurrentState = {
if let snapshot = self.snapshot {
return snapshot()
}
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections(self.sections)
self.sections.forEach { section in
snapshot.appendItems(self.items[section]!, toSection: section)
}
return snapshot
}
controller.content = content
controller.collectionView.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: CollectionViewController<Section, Item>, context: Context) {
uiViewController.updateUI()
}
class Coordinator: NSObject, UICollectionViewDelegate {
// MARK: - Properties
let parent: CollectionView
// MARK: - Init
init(_ parent: CollectionView) {
self.parent = parent
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Did select item at \(indexPath)")
}
}
}
struct CollectionView_Previews: PreviewProvider {
enum Section: CaseIterable {
case features
case categories
}
enum Item: Hashable {
case feature(feature: Feature)
case category(category: Category)
}
class Feature: Hashable{
let id: String
let title: String
init(id: String, title: String) {
self.id = id
self.title = title
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
}
static func ==(lhs: Feature, rhs: Feature) -> Bool {
lhs.id == rhs.id
}
}
class Category: Hashable {
let id: String
let title: String
init(id: String, title: String) {
self.id = id
self.title = title
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
}
static func ==(lhs: Category, rhs: Category) -> Bool {
lhs.id == rhs.id
}
}
static let items: [Section: [Item]] = {
return [
.features : [
.feature(feature: Feature(id: "1", title: "Feature 1")),
.feature(feature: Feature(id: "2", title: "Feature 2")),
.feature(feature: Feature(id: "3", title: "Feature 3"))
],
.categories : [
.category(category: Category(id: "1", title: "Category 1")),
.category(category: Category(id: "2", title: "Category 2")),
.category(category: Category(id: "3", title: "Category 3"))
]
]
}()
static var previews: some View {
func generateLayout() -> UICollectionViewLayout {
let itemHeightDimension = NSCollectionLayoutDimension.absolute(44)
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: itemHeightDimension)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: itemHeightDimension)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 2)
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
return CollectionView(layout: generateLayout(), sections: [.features], items: items, snapshot: {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections(Section.allCases)
items.forEach { (section, items) in
snapshot.appendItems(items, toSection: section)
}
return snapshot
}) { (indexPath, item) -> AnyView in
switch item {
case .feature(let item):
return AnyView(Text("Feature \(item.title)"))
case .category(let item):
return AnyView(Text("Category \(item.title)"))
}
}
}
}
class CollectionViewController<Section, Item>: UIViewController
where Section : Hashable & CaseIterable, Item : Hashable {
var layout: UICollectionViewLayout! = nil
var snapshotForCurrentState: (() -> NSDiffableDataSourceSnapshot<Section, Item>)! = nil
var content: ((_ indexPath: IndexPath, _ item: Item) -> AnyView)! = nil
lazy var dataSource: UICollectionViewDiffableDataSource<Section, Item> = {
let dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView, cellProvider: cellProvider)
return dataSource
}()
lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
collectionView.backgroundColor = .clear
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
configureCollectionView()
configureDataSource()
}
}
extension CollectionViewController {
private func configureCollectionView() {
view.addSubview(collectionView)
collectionView.register(HostingControllerCollectionViewCell<AnyView>.self, forCellWithReuseIdentifier: HostingControllerCollectionViewCell<AnyView>.reuseIdentifier)
}
private func configureDataSource() {
// load initial data
let snapshot : NSDiffableDataSourceSnapshot<Section, Item> = snapshotForCurrentState()
dataSource.apply(snapshot, animatingDifferences: false)
}
private func cellProvider(collectionView: UICollectionView, indexPath: IndexPath, item: Item) -> UICollectionViewCell? {
print("Providing cell for \(indexPath)")
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HostingControllerCollectionViewCell<AnyView>.reuseIdentifier, for: indexPath) as? HostingControllerCollectionViewCell<AnyView> else {
fatalError("Coult not load cell!")
}
cell.host(content(indexPath, item))
return cell
}
}
extension CollectionViewController {
func updateUI() {
let snapshot : NSDiffableDataSourceSnapshot<Section, Item> = snapshotForCurrentState()
dataSource.apply(snapshot, animatingDifferences: true)
}
}