UICollectionView reloadData only works one time in two with PDFViews in the cells

I have a UIViewController that presents a UIDocumentPicker to pick PDF files, and contains a UICollectionView that displays them (each cell contains a PDFView to do so).

Here is the code:

import MobileCoreServices; import PDFKit; import UIKit

class ViewController: UIViewController {
    var urls: [URL] = []
    @IBOutlet weak var collectionView: UICollectionView!

    @IBAction func pickFile() {
        DispatchQueue.main.async {
            let documentPicker = UIDocumentPickerViewController(documentTypes: [kUTTypePDF as String], in: .import)
            documentPicker.delegate = self
            documentPicker.modalPresentationStyle = .formSheet
            self.present(documentPicker, animated: true, completion: nil)
        }
    }
    
    override func viewDidLoad() {
        collectionView.register(UINib(nibName: PDFCollectionViewCell.identifier, bundle: .main),
                                forCellWithReuseIdentifier: PDFCollectionViewCell.identifier)
    }
    
    init() { super.init(nibName: "ViewController", bundle: .main) }
    required init?(coder: NSCoder) { fatalError() }
}

extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PDFCollectionViewCell.identifier, for: indexPath) as! PDFCollectionViewCell
        cell.pdfView.document = PDFDocument(url: urls[indexPath.row])
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return urls.count
    }

    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        sizeForItemAt indexPath: IndexPath) -> CGSize {
        CGSize(width: 150, height: 150)
    }
}

extension ViewController: UIDocumentPickerDelegate {
    // MARK: PDF Picker Delegate
    func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
        controller.dismiss(animated: true, completion: nil)
        
    }
    
    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        controller.dismiss(animated: true, completion: {
            DispatchQueue.main.async {
                self.urls.append(contentsOf: urls)
                self.collectionView.reloadData()
            }
        })
    }
}

class PDFCollectionViewCell: UICollectionViewCell {
    static let identifier = "PDFCollectionViewCell"
    
    @IBOutlet weak var pdfView: PDFView! { didSet { setPdfViewUI() } }
    
    func setPdfViewUI() {
        pdfView.displayMode = .singlePage
        pdfView.autoScales = true
        pdfView.displayDirection = .vertical
        pdfView.isUserInteractionEnabled = false
    }
}

Now, for some reason, the collectionView.reloadData() actually only works one time in two. It works the first time, then the second time nothing happens, then the third time the collection view is updated again with the three expected elements...

I realized that even if I'm calling reloadData(), the dataSource and delegate methods (numberOfItems/cellForItem) are not getting called when this happens.

Any idea of what is happening? Am I doing something wrong?

Thank you for your help!

Replies

Hello, if anyone is interested in this topic, I have also asked this on StackOverflow and we came to the same conclusion that there is definitely something wrong with using PDFKit in UICollectionView (but also UITableView, i.e.), see https://stackoverflow.com/questions/67988863/uicollectionview-reloaddata-only-works-one-time-in-two

Two solutions were proposed to me and actually worked:

  • Calling collectionView.insertItems(at: indexPaths) instead of collectionView.reloadData()
  • Or use QLThumbnailGenerator from QuickLookThumbnailing.