Viewing PDF Documents Stored in Local Cache

Hi, I am now having problems viewing the correct pdf documents I have stored in local cash.

I have tried to describe the code below by functionality to help understand what I am doing. As background, I have written an app to track and store aircraft inspection and maintenance history including storage of documents tied to specific inspection records. The user can view the records and the documents attached and add and delete documents. The code works fine uploading pdf documents, attaching them to records and displaying the thumbnail image in a tableview. It also works fine deleting the selected thumbnail image and re-displaying the tableview. It was also working properly displaying the deleted pdf after clicking on the thumbnail image (but now it doesn’t seem to do that properly), it only displays the last document saved (in the .cacheDirectory within the .userDomain) when the thumbnail image is clicked on.

The pdf document data is downloaded (using the URL session in the extension function) from “cloud firestore” and converted into a thumbnail image to display in a tableview. The urls downloaded are saved into the “Document.passedUrl” array.

This part of the code is as per below:


func displayDoc() {//1

    //check that there are documents to display
    
    if Document.passedUrl == [] {//2
      
     return
        
    }//2
    else {//3
        
        //check if image is in cache
        
        if let cachedImage = ImageCacheService.getImage(url: Document.urlPicked) {
            // use the cached image
            self.pdfDoc.image = cachedImage
        
        
            //image found in cache so we no longer need to download the image
            return
            } else {
        
                //image not found in cache so download the image

                guard let url = URL(string: Document.urlPicked)
        
                else {//5
                    return
                    }//5

               let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
            
                
                //get the data
                
                
                let downloadTask = urlSession.downloadTask(with: url)
        
                
                guard let document = PDFDocument(url: url) else {
                    return
                    }
                
                guard let page = document.page(at: 0) else {
                    return
                    }
                
                let pageRect = page.bounds(for: .mediaBox)
                
                let renderer = UIGraphicsImageRenderer(size: pageRect.size)
                
                let image = renderer.image { ctx in
                    
                    UIColor.white.set()
                    ctx.fill(CGRect(x: 0, y: 0, width: pageRect.width, height: pageRect.height))
                    ctx.cgContext.translateBy(x: -pageRect.origin.x, y: pageRect.size.height - pageRect.origin.y)
                    ctx.cgContext.scaleBy(x: 1.0, y: -1.0)
                    
                    page.draw(with: .mediaBox, to: ctx.cgContext)
                    }//image rendere
        
                //save image to cache using the PhotoImageCacheService
        
               ImageCacheService.saveImage(url: url.absoluteString, image: image)
                print("There are \(ImageCacheService.imageCache.count) thumbnails in cache")
                DocumentCacheService.saveDoc(url: url)
        
        //set the image view
                
            //DispatchQueue.main.async {
                    self.pdfDoc.image = image
               
                
           // }
                
              
        downloadTask.resume()
        }// else 3
        
        
    }//else no cached image
            
}//display document 1

The pdf data is saved into the “.cacheDirectoy” in the “.userDoman” inside the extension function, as per below and I believe this is where the problem is:


extension DocumentTableViewCell: URLSessionDownloadDelegate {

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
   
    Document.pdfUrlArray = []
    
    //create destination URL folder
    

    //save the data
    for url in DocumentCacheService.docCache {
        
       
       let docsPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
      let destinationPath = docsPath.appendingPathComponent(url.lastPathComponent)

  do {
            try FileManager.default.removeItem(at: destinationPath)
        } catch {
            print("The file did not get deleted")
        }
       
            do {
                try FileManager.default.copyItem(at: location, to: destinationPath)
               
                Document.pdfUrlArray.append(destinationPath)
               
                
            } catch let error {
                print("Copy Error: \(error.localizedDescription)")
            
                
            }//catchlet
     
   } //for
    
}//func

When the user clicks on a thumbnail image they are taken to another view controller that displays the pdf document as per the code below:


class DocDetailViewController: UIViewController, WKUIDelegate {

@IBOutlet weak var docDetail: WKWebView!


var urlToDisplay: URL?

let userId = Auth.auth().currentUser?.uid
let db = Firestore.firestore()


override func viewDidLoad() {
    super.viewDidLoad()

    //docDetail.uiDelegate = self
    
    if urlToDisplay != nil {
        
        
        self.docDetail.loadFileURL(urlToDisplay!, allowingReadAccessTo: urlToDisplay!)
        print("The document to display is \(urlToDisplay)")
    }//if
}//func

NOTE: The “urlToDisplay” property, in the code above, is obtained from the didselectRowAt method as per below:

___________________________________-
if let detailvc = storyboard?.instantiateViewController(withIdentifier: “DocDetail”) as? DocDetailViewController {
detailvc.urlToDisplay = Document.pdfUrlArray[indexPath.row]

                Document.docToDelete = Document.passedUrl[indexPath.row]
                
                Document.urlIndexPath = indexPath.row
               
                
                
                self.present(detailvc, animated: true, completion: nil)
               
                
            }//iflet

The app gives the user the option of deleting the document from this second view controller as per the code below:


@IBAction func deleteDoc(_ sender: Any) {

    let dialogMessage = UIAlertController(title: "Confirm", message: "Are you sure you want to delete the document as this action cannot be reversed!", preferredStyle: .alert)
     
     //Create OK button with action handler
     let ok = UIAlertAction(title: "Ok", style: .default, handler: { (action) -> Void in
        
         //delete database reference
             
        let docField = self.db.collection("Challenger").document("Engine Inspection & Maintenance Requirements").collection(self.userId!).document(PlaneInfo.plane).collection("\(EngFrReq.engFrReq[Document.num])").document(EngineHistory.passedDocId!)
        
        docField.updateData(["Documents": FieldValue.arrayRemove([Document.docToDelete])]) { (error) in
            
            if error != nil {
                self.showMessage(message: "There was a problem deleting the document.  Please try again.  If the problem re-occurs, sgutdown and restart the app.")
            } else {
            return
            }
        }//updata data
        ImageCacheService.deleteImage(url: Document.passedUrl[Document.urlIndexPath])
        DocumentCacheService.deleteDOC(url: URL(string: Document.passedUrl[Document.urlIndexPath])!)
        Document.passedUrl.remove(at: Document.urlIndexPath)
        Document.pdfUrlArray.remove(at: Document.urlIndexPath)
        

        //if Documents field is empty delete the Documents field as well
        if Document.passedUrl == [] {
            
            docField.updateData(["Documents" : FieldValue.delete()]) { (error) in
            
            if error == nil {
                return
            } else {
                self.showMessage(message: "There was a problem deleting the document.  Please try again.  If the problem re-occurs, close and restart the app.")
            }
        }//delete docfield
        }//docfield is empty
       
 
        //delete storage of document
         
         let docRef = Storage.storage()
            
         let deleteref = docRef.reference(forURL: "\(Document.docToDelete)")
             
             deleteref.delete { (error) in
             
             if error == nil {
                 return
             } else {
                self.showMessage(message: "There was a problem deleting the document.  Please try again.  If the problem re-occurs, close and restart the app.")
             }
         }//delete ref
        
        self.performSegue(withIdentifier: "goBack", sender: self)
        
     })
        
   
     let cancel = UIAlertAction(title: "Cancel", style: .cancel) { (action) -> Void in
         
         
     }
     
     //Add ok and Cncel button to dialog message
     
     dialogMessage.addAction(ok)
     dialogMessage.addAction(cancel)
     
     //present dialog message to user
     
     self.present(dialogMessage, animated: true, completion: nil)
      
}

The “goBack” segue above takes the user back to the view controller that displays the thumbnail
images in the tableview. It has a listener to repopulate the cells when there is a database change as per below:


db.collection(“Challenger”).document(“Engine Inspection & Maintenance Requirements”).collection(self.userId!).document(PlaneInfo.plane).addSnapshotListener { (documentSnapshot, error) in

        if error != nil || documentSnapshot == nil {
            self.showMessage(message: "A problem occured.  Please try again.  If the problem persists, close and restart the app.")
            
        } else {
            let document = documentSnapshot
            let source = document!.metadata.hasPendingWrites ? "Local" : "Server"
            self.photoTable.reloadData()
            self.docTableView.reloadData()
            return
            }
        }//snapshot

When a thumbnail image is selected, the document displayed doesn’t match the thumbnail image. It appears to only display the last document saved.

If anyone has any ideas on this I could really use a point in the right direction. Thanks

maybe the answer lies in your tableview, try printing the “index” or the value of the docURL when you select to see if it really matches what you have clicked

if its somehow wrong then you probably forgot to set values to “null” when the tableviewrow initialized, making it “take” the value from the previous row

Hi Francise, thanks for replying! I tried placing print commands at various parts of the code to ensure the URL was being captured correctly and it was. The URL I wanted to display was the URL being requested from the file manager.

The problem appears to be that the file manager is only retrieving the latest saved URL. I couldn’t find the problem in my code so I re-wrote it and got rid of the extension. It now works fine.

I have no idea why it wouldn’t work the way I had written the code initially.

Thanks again for your help!