Search code examples
swiftswiftuiuicollectionviewuiviewrepresentable

UIViewRepresentable UICollectionView How do you scroll to a certain point when view appears?


I have the following code, but I am not sure where to place it in my UIViewRepresntable. Any suggestions?

    let scrollTo = IndexPath(row: 25, section: 0)
    view.scrollToItem(at: scrollTo, at: .top, animated: false)


struct CollectionViewRepresentable: UIViewRepresentable {
    
    @StateObject var vm = CollectionViewModel()

    let view = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewCompositionalLayout.list(using: UICollectionLayoutListConfiguration(appearance: .plain)))
    
    func makeUIView(context: Context) -> UICollectionView {

        view.backgroundColor = UIColor.clear
        view.dataSource = context.coordinator
        view.delegate = context.coordinator
        view.register(ListCell.self, forCellWithReuseIdentifier: "listCell")
        return view
    }

    func updateUIView(_ uiView: UICollectionView, context: Context) {
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, UICollectionViewDelegate, UICollectionViewDataSource {
        private var parent: CollectionViewRepresentable

        init(_ collectionViewRepresentable: CollectionViewRepresentable) {
            self.parent = collectionViewRepresentable
        }

        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return parent.vm.items.count
        }

        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "listCell", for: indexPath) as! ListCell
            let item = parent.vm.items[indexPath.row]
            cell.item = item
            return cell
        }
    }
}

I need to scroll to that position when the view appears.


Solution

  • You could use ViewControllerRepresentable and take advantage of the fact that view controllers can override the viewWillAppear/viewDidAppear methods where you can write the code that scrolls.

    For this, subclass UICollectionViewController, and move all the collection view related logic there:

    class MyCollectionViewController: UICollectionViewController {
        var vm = CollectionViewModel()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            collectionView.backgroundColor = UIColor.clear
            collectionView.register(ListCell.self, forCellWithReuseIdentifier: "listCell")
        }
        
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            
            let scrollTo = IndexPath(row: 25, section: 0)
            collectionView.scrollToItem(at: scrollTo, at: .top, animated: false)
        }
        
        override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return vm.items.count
        }
        
        override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "listCell", for: indexPath) as! ListCell
            let item = vm.items[indexPath.row]
            cell.item = item
            return cell
        }
    }
    

    With the above in mind, the SwiftUI code simplifies to something like this:

    struct CollectionViewRepresentable: UIViewControllerRepresentable {
        
        func makeUIViewController(context: Context) -> MyCollectionViewController {
            .init(collectionViewLayout: UICollectionViewFlowLayout())
        }
        
        func updateUIViewController(_ vc: MyCollectionViewController, context: Context) {
        }
    }