如何将 SwiftUI 视图设置为 CollectionView 的单元格
How to set a SwiftUI view as a cell to a CollectionView
我发现 SwiftUI Text
视图非常容易创建带有自定义设计的标签。所以我想将它用作常规 UIKit UICollectionViewCell 的视图。
到目前为止,这是我的代码(您可以在 Xcode 11 中复制和粘贴)。
import SwiftUI
import UIKit
struct ContentView: View {
var body: some View {
CollectionComponent()
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
struct CollectionComponent : UIViewRepresentable {
func makeCoordinator() -> CollectionComponent.Coordinator {
Coordinator(data: [])
}
class Coordinator: NSObject, UICollectionViewDataSource, UICollectionViewDelegate {
var data: [String] = []
init(data: [String]) {
for index in (0...1000) {
self.data.append("\(index)")
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! GenericCell
cell.customView.rootView = AnyView(
Text(data[indexPath.item]).font(Font.title).border(Color.red)
)
return cell
}
}
func makeUIView(context: Context) -> UICollectionView {
let layout = UICollectionViewFlowLayout()
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
layout.scrollDirection = .vertical
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.dataSource = context.coordinator
cv.delegate = context.coordinator
cv.register(GenericCell.self, forCellWithReuseIdentifier: "cell")
cv.backgroundColor = .white
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
return cv
}
func updateUIView(_ uiView: UICollectionView, context: Context) {
}
}
open class GenericCell: UICollectionViewCell {
public var customView = UIHostingController(rootView: AnyView(Text("")))
public override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
private func configure() {
contentView.addSubview(customView.view)
customView.view.preservesSuperviewLayoutMargins = false
customView.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
customView.view.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
customView.view.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor),
customView.view.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
customView.view.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor),
])
}
}
首屏不错
但是当我滚动到可见屏幕的末尾时,它看起来像
我在自动调整单元格大小方面做错了什么吗?或者这只是更多的 SwiftUI 错误?
[编辑]
我已经接受了 SwiftUI 的回答,但是如果有人可以按照这个问题的要求为我提供使用 UIKit 的修复程序,我会接受。
这是一个仅使用 swifUI
的解决方案,请注意,这没有 CollectionView
带来的好处(即控制屏幕外的元素)
struct doubleList: View {
var body: some View {
VStack{
ForEach(1 ..< 10) {
index in
HStack{
ForEach(1 ..< 10) {
index2 in
Text(String(index) + String(index2))
.frame(width: 35.0)
}
}
}
}
}
}
这将给出如下所示的结果
仅使用 SwiftUI,您就可以组合一个视图,按照您的意愿重排文本视图。 The code is on GitHub, but is pretty long, so I won't post it here. You can find my explanation of what is going on in 。这是一个演示:
我做了一些修改并且它有效,但我认为这不是最佳做法。
import SwiftUI
import UIKit
struct ContentView: View {
var body: some View {
CollectionComponent()
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
struct CollectionComponent : UIViewRepresentable {
func makeCoordinator() -> CollectionComponent.Coordinator {
Coordinator(data: [])
}
class Coordinator: NSObject, UICollectionViewDataSource, UICollectionViewDelegate {
var data: [String] = []
init(data: [String]) {
for index in (0...1000) {
self.data.append("\(index)")
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
data.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width/2.5, height: collectionView.frame.width/2)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! GenericCell
cell.customView?.rootView = Text(data[indexPath.item])
return cell
}
}
func makeUIView(context: Context) -> UICollectionView {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
let cvs = UICollectionView(frame: .zero, collectionViewLayout: layout)
cvs.dataSource = context.coordinator
cvs.delegate = context.coordinator
cvs.register(GenericCell.self, forCellWithReuseIdentifier: "cell")
cvs.backgroundColor = .white
return cvs
}
func updateUIView(_ uiView: UICollectionView, context: Context) {
}
}
public class GenericCell: UICollectionViewCell {
public var textView = Text("")
public var customView: UIHostingController<Text>?
public override init(frame: CGRect) {
super.init(frame: .zero)
customView = UIHostingController(rootView: textView)
customView!.view.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(customView!.view)
customView!.view.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
customView!.view.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
customView!.view.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
customView!.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
这是一个纯粹的 SwiftUI 解决方案。
它会包裹你给它的任何视图,并给你你想要的效果。
让我知道它是否适合你。
struct WrappedGridView: View {
let views: [WrappedGridViewHolder]
var showsIndicators = false
var completion:(Int)->Void = {x in}
var body: some View {
GeometryReader { geometry in
ScrollView(showsIndicators: showsIndicators) {
self.generateContent(in: geometry)
}
}
}
init(views: [AnyView], showsIndicators: Bool = false, completion: @escaping (Int)->Void = {val in}) {
self.showsIndicators = showsIndicators
self.views = views.map { WrappedGridViewHolder(view: [=10=]) }
self.completion = completion
}
private func generateContent(in g: GeometryProxy) -> some View {
var width = CGFloat.zero
var height = CGFloat.zero
return
ZStack(alignment: .topLeading) {
ForEach(views) { item in
item
.padding(4)
.alignmentGuide(.leading) { d in
if (abs(width - d.width) > g.size.width) {
width = 0
height -= d.height
}
let result = width
if item == self.views.last {
width = 0
} else {
width -= d.width
}
return result
}
.alignmentGuide(.top) { d in
let result = height
if item == self.views.last {
height = 0
}
return result
}
.onTapGesture {
tapped(value: item)
}
}
}
.background(
GeometryReader { r in
Color
.clear
.preference(key: SizePreferenceKey.self, value: r.size)
}
)
}
func tapped(value: WrappedGridViewHolder) {
guard let index = views.firstIndex(of: value) else { assert(false, "This should never happen"); return }
completion(index)
}
}
struct SizePreferenceKey: PreferenceKey {
typealias Value = CGSize
static var defaultValue: Value = .zero
static func reduce(value: inout Value, nextValue: () -> Value) {
}
}
extension WrappedGridView {
struct WrappedGridViewHolder: View, Identifiable, Equatable {
let id = UUID().uuidString
let view: AnyView
var body: some View {
view
}
static func == (lhs: WrappedGridViewHolder, rhs: WrappedGridViewHolder) -> Bool { lhs.id == rhs.id }
}
}
我发现 SwiftUI Text
视图非常容易创建带有自定义设计的标签。所以我想将它用作常规 UIKit UICollectionViewCell 的视图。
到目前为止,这是我的代码(您可以在 Xcode 11 中复制和粘贴)。
import SwiftUI
import UIKit
struct ContentView: View {
var body: some View {
CollectionComponent()
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
struct CollectionComponent : UIViewRepresentable {
func makeCoordinator() -> CollectionComponent.Coordinator {
Coordinator(data: [])
}
class Coordinator: NSObject, UICollectionViewDataSource, UICollectionViewDelegate {
var data: [String] = []
init(data: [String]) {
for index in (0...1000) {
self.data.append("\(index)")
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! GenericCell
cell.customView.rootView = AnyView(
Text(data[indexPath.item]).font(Font.title).border(Color.red)
)
return cell
}
}
func makeUIView(context: Context) -> UICollectionView {
let layout = UICollectionViewFlowLayout()
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
layout.scrollDirection = .vertical
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.dataSource = context.coordinator
cv.delegate = context.coordinator
cv.register(GenericCell.self, forCellWithReuseIdentifier: "cell")
cv.backgroundColor = .white
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
return cv
}
func updateUIView(_ uiView: UICollectionView, context: Context) {
}
}
open class GenericCell: UICollectionViewCell {
public var customView = UIHostingController(rootView: AnyView(Text("")))
public override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
private func configure() {
contentView.addSubview(customView.view)
customView.view.preservesSuperviewLayoutMargins = false
customView.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
customView.view.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
customView.view.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor),
customView.view.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
customView.view.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor),
])
}
}
首屏不错
但是当我滚动到可见屏幕的末尾时,它看起来像
我在自动调整单元格大小方面做错了什么吗?或者这只是更多的 SwiftUI 错误?
[编辑] 我已经接受了 SwiftUI 的回答,但是如果有人可以按照这个问题的要求为我提供使用 UIKit 的修复程序,我会接受。
这是一个仅使用 swifUI
的解决方案,请注意,这没有 CollectionView
带来的好处(即控制屏幕外的元素)
struct doubleList: View {
var body: some View {
VStack{
ForEach(1 ..< 10) {
index in
HStack{
ForEach(1 ..< 10) {
index2 in
Text(String(index) + String(index2))
.frame(width: 35.0)
}
}
}
}
}
}
这将给出如下所示的结果
仅使用 SwiftUI,您就可以组合一个视图,按照您的意愿重排文本视图。 The code is on GitHub, but is pretty long, so I won't post it here. You can find my explanation of what is going on in
我做了一些修改并且它有效,但我认为这不是最佳做法。
import SwiftUI
import UIKit
struct ContentView: View {
var body: some View {
CollectionComponent()
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
struct CollectionComponent : UIViewRepresentable {
func makeCoordinator() -> CollectionComponent.Coordinator {
Coordinator(data: [])
}
class Coordinator: NSObject, UICollectionViewDataSource, UICollectionViewDelegate {
var data: [String] = []
init(data: [String]) {
for index in (0...1000) {
self.data.append("\(index)")
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
data.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width/2.5, height: collectionView.frame.width/2)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! GenericCell
cell.customView?.rootView = Text(data[indexPath.item])
return cell
}
}
func makeUIView(context: Context) -> UICollectionView {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
let cvs = UICollectionView(frame: .zero, collectionViewLayout: layout)
cvs.dataSource = context.coordinator
cvs.delegate = context.coordinator
cvs.register(GenericCell.self, forCellWithReuseIdentifier: "cell")
cvs.backgroundColor = .white
return cvs
}
func updateUIView(_ uiView: UICollectionView, context: Context) {
}
}
public class GenericCell: UICollectionViewCell {
public var textView = Text("")
public var customView: UIHostingController<Text>?
public override init(frame: CGRect) {
super.init(frame: .zero)
customView = UIHostingController(rootView: textView)
customView!.view.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(customView!.view)
customView!.view.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
customView!.view.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
customView!.view.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
customView!.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
这是一个纯粹的 SwiftUI 解决方案。 它会包裹你给它的任何视图,并给你你想要的效果。 让我知道它是否适合你。
struct WrappedGridView: View {
let views: [WrappedGridViewHolder]
var showsIndicators = false
var completion:(Int)->Void = {x in}
var body: some View {
GeometryReader { geometry in
ScrollView(showsIndicators: showsIndicators) {
self.generateContent(in: geometry)
}
}
}
init(views: [AnyView], showsIndicators: Bool = false, completion: @escaping (Int)->Void = {val in}) {
self.showsIndicators = showsIndicators
self.views = views.map { WrappedGridViewHolder(view: [=10=]) }
self.completion = completion
}
private func generateContent(in g: GeometryProxy) -> some View {
var width = CGFloat.zero
var height = CGFloat.zero
return
ZStack(alignment: .topLeading) {
ForEach(views) { item in
item
.padding(4)
.alignmentGuide(.leading) { d in
if (abs(width - d.width) > g.size.width) {
width = 0
height -= d.height
}
let result = width
if item == self.views.last {
width = 0
} else {
width -= d.width
}
return result
}
.alignmentGuide(.top) { d in
let result = height
if item == self.views.last {
height = 0
}
return result
}
.onTapGesture {
tapped(value: item)
}
}
}
.background(
GeometryReader { r in
Color
.clear
.preference(key: SizePreferenceKey.self, value: r.size)
}
)
}
func tapped(value: WrappedGridViewHolder) {
guard let index = views.firstIndex(of: value) else { assert(false, "This should never happen"); return }
completion(index)
}
}
struct SizePreferenceKey: PreferenceKey {
typealias Value = CGSize
static var defaultValue: Value = .zero
static func reduce(value: inout Value, nextValue: () -> Value) {
}
}
extension WrappedGridView {
struct WrappedGridViewHolder: View, Identifiable, Equatable {
let id = UUID().uuidString
let view: AnyView
var body: some View {
view
}
static func == (lhs: WrappedGridViewHolder, rhs: WrappedGridViewHolder) -> Bool { lhs.id == rhs.id }
}
}