【问题标题】:How to set a SwiftUI view as a cell to a CollectionView如何将 SwiftUI 视图设置为 CollectionView 的单元格
【发布时间】:2020-10-12 21:40:49
【问题描述】:

我发现 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 的修复程序,我会接受。

【问题讨论】:

  • 你可以使用列表中的列表来代替 UIcollectionview 来获得相同的效果
  • @yawnobleix 怎么样?
  • 今晚在家的时候我会尽量贴出来
  • 添加了答案
  • 您能否告诉我们您是如何使用UICollectionViewUICollectionViewCell 完成此任务的?这将有助于指出具体问题 - 这可能是“目前”您无法在 SwiftUI 中执行此操作。例如,这是与任一组件中的自动布局约束相关的问题吗?这是否与尝试在不存在的 SwiftUI 中创建集合视图的“惰性”或“可重用单元”版本有关?或者这是一个动态类型问题?换句话说(请原谅我玩文字游戏)-您将如何在仅限 UIKit 的应用程序中处理此问题?

标签: swift uicollectionview uicollectionviewcell swiftui


【解决方案1】:

这是一个纯粹的 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: $0) }
        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 视图设置为 CollectionView 的单元格”问题的答案
  • 张贴者需要它来处理collectionview,因为它被截断了。我只是表明不需要集合视图。或许请原发帖人换个问题?
【解决方案2】:

我做了一些修改,它可以工作,但我认为这不是最佳做法。

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")
    }
}

【讨论】:

    【解决方案3】:

    这是一个仅使用 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)
                        }
                    }
                }
            }
        }
    }
    

    这将给出如下所示的结果

    【讨论】:

    • 这里的问题是结构是刚性的。在这种情况下,数据是数字。在实际代码中,数据是 Words。需要的不是严格的 10x10 行/高。需要的是,当一个单词到达末尾时,它应该换行并转到下一行。我还没有找到一种方法来做到这一点。 (虽然我确实给了你 +1)
    • 所以您想在屏幕上的一个框中显示多个不同长度的单词?如果您在问题中包含更多详细信息,将来会有所帮助
    • 当问题明确指出要在 SwiftUI 框架内使用 UIKit 组件集合视图时,为什么每个人都使用 SwithUi 滚动视图发布答案?正确答案应该显示使用 UIViewRepresentable 在 SwiftUi 中使用集合视图。
    【解决方案4】:

    仅使用 SwiftUI,您可以组合一个视图,该视图可以像您想要的那样重排 Text 视图。 The code is on GitHub,但是很长,这里就不贴了。您可以在 this answer 中找到我对正在发生的事情的解释。这是一个演示:

    【讨论】:

    • 如果把那个 GitHub 整理成一个项目就好了。
    • 当问题明确指出要在 SwiftUI 框架内使用 UIKit 组件集合视图时,为什么每个人都使用 SwithUi 滚动视图发布答案?正确答案应该显示使用 UIViewRepresentable 在 SwiftUi 中使用集合视图。即使我也想达到同样的效果 - 使用 COLLECTION VIEW CUMPOLSORY...
    • 只用SwiftUi就可以达到发帖人想要的效果,谁说这种做法不对?
    • @yawnobleix 请阅读问题和标题。它说关于使用 UICollectionView
    • @Tejas 你是对的。我仍在寻找 UIKit 修复程序。如果有人提供,我会接受。我还编辑了上面的问题以反映我刚才所说的内容。
    猜你喜欢
    • 2018-04-05
    • 2022-06-29
    • 1970-01-01
    • 1970-01-01
    • 2019-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多