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

Time:06-11

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.

CodePudding user response:

A scenario is not completely clear, but it is possible to use external State and a Binding inside representable, changing with calls updateUIView.

So a possible solution is

@State private var scrollTo = IndexPath(row: 25, section: 0) // << in parent !! 

// ...

struct CollectionViewRepresentable: UIViewRepresentable {
    @Binding var scrollTo: IndexPath                       // << 1) !!
    @StateObject private var vm = CollectionViewModel()

    func makeUIView(context: Context) -> UICollectionView {
        let view = UICollectionView(...)    // << move creation here !!
        // ... other code
        return view
    }

    func updateUIView(_ uiView: UICollectionView, context: Context) {
       // called at start and when binding updated
       uiView.scrollToItem(at: scrollTo, at: .top, animated: false)  // << 2) !!
    }
// ... other code
}

CodePudding user response:

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) {
    }
}
  • Related