web-dev-qa-db-ja.com

SwiftUIビューをセルとして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),
        ])
    }
}

最初の画面は良いです。

enter image description here

しかし、目に見える画面の最後を超えてスクロールすると、次のようになります

enter image description here

セルの自動サイズ変更で問題が発生していますか?それとも、これはより多くのSwiftUIバグですか?

[編集]SwiftUIの回答を受け入れましたが、この質問で尋ねられたように、UIKitを使用するための修正を提供できる人がいれば、受け入れます。

10
swift nub

SwiftUIだけを使用して、必要なテキストビューをリフローするビューをまとめることができます。 コードはGitHubにあります ですが、非常に長いため、ここには投稿しません。何が起こっているかについての私の説明は この答え にあります。ここにデモがあります:

enter image description here

0
John M.

私はいくつかの変更を加えましたが、動作しますが、これはベストプラクティスではないと思います。

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