PDFKIT , Swift How to remove pdf (PDFDocument) from uitableview - ios

i have this code for download PDF file :
var documents = [PDFDocument]()
DispatchQueue.global(qos: .default).async(execute: {
//All stuff here
print("Download PDF");
let url=NSURL(string: urlString);
let urlData=NSData(contentsOf: url! as URL);
if((urlData) != nil)
{
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let fileName = urlString as NSString;
let filePath="\(documentsPath)/\(fileName.lastPathComponent)";
let fileExists = FileManager().fileExists(atPath: filePath)
if(fileExists){
// File is already downloaded
print("PDF Already Downloaded");
}
else{
//download
DispatchQueue.main.async(execute: { () -> Void in
print(filePath)
urlData?.write(toFile: filePath, atomically: true);
print("PDF Saved");
self.refreshData()
})
}
}
})
Now I want to remove this file from uitableview in table and in documentdirecotry how to use index path row and how to find file name for removing
i know i will remove the file here but i don't know how to exactly remove the PDF in documentDirectory and Table
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == UITableViewCellEditingStyle.delete) {
// handle delete (by removing the data from your array and updating the tableview)
}
}
here is my table view cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! BookshelfCell
let document = documents[indexPath.row]
if let documentAttributes = document.documentAttributes {
if let title = documentAttributes["Title"] as? String {
cell.title = title
}
if let author = documentAttributes["Author"] as? String {
cell.author = author
}
here is my refresh data part
let fileManager = FileManager.default
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let contents = try! fileManager.contentsOfDirectory(at: documentDirectory, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
documents = contents.flatMap { PDFDocument(url: $0) }

You will need to complete three steps to properly delete the document and update your table view:
Use FileManager's removeItem(at: URL) or removeItem(atPath: String) to delete the file from disk. (note that both of these methods throw so you need to use a do-catch block along with try and only proceed if the method didn't throw an error.) Update: If you look at the documentation for PDFDocument you will find that in addition to the documentAttributes that you are already using there is another optional property, documentURL that should give you exactly what you need to remove it.
Remove the document from documents (you could just refresh the whole array using your existing code but removing a single item is faster). documents.remove(at: indexPath.row)
Finally, you need to tell the table view to remove the row in question (you could of course just reload the whole table view but removing the single cell is cleaner) tableView.deleteRows(at: [indexPath], with .fade)
In case you are unfamiliar with do-catch blocks here is a bit of code from Apple's book on Swift (see below for link) simplified a bit:
do {
try makeASandwich()
eatASandwich() // This only gets called if the line above worked
} catch {
dealWithTheError() // This only gets called if makeASandwich() throws an error
}
Side Note
Apple has a fantastic guide on the Swift Language if you haven't done so yet I suggest reading, at least, The Basics. This will give you a base understanding of the language. If you are also new to programming I would suggest going through Apple's Learn to Code series that is free on iPads in the Swift Playgrounds app. The series will guide you through all the basics of programming giving you the tools to search through the documentation that Apple provides and find answers to your questions.
We all started at the beginning at some point, and we all had to crawl before we could walk and well before we could run.

Related

Swift: How to increase loading speed of images from documents?

My app saved photo to the local documents folder and I used the UICollectionView to display all the image from that folder. But whenever I try to open the CollectionView it often took several seconds to open. I'm thinking that maybe the image files are too big, each photo is around 10MB. I also tried using thumbnails to display in collectionview but it still too slow. Any idea how to speed that up?
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! SPCell
// Configure the cell
cell.imageView.image = loadImage(fileName: self.fileURLs[indexPath.row])
return cell
}
func loadImagesFromDocuments(){
let fileManager = FileManager.default
let documentsURL = NSHomeDirectory() + "/Documents/Secure/"
do {
fileURLs = try fileManager.contentsOfDirectory(at: URL(string: documentsURL)!, includingPropertiesForKeys: nil)
} catch {
print("Error while enumerating files : \(error.localizedDescription)")
}
}
func loadImage(fileName: URL) -> UIImage? {
do {
let imageData = try Data(contentsOf: fileName)
return UIImage(data: imageData)
} catch {
print("Error loading image : \(error)")
}
return nil
}
Current problem is that you load the image every time the cell appears so Instead of
var fileURLs = [URL]()
Make it
var fileImages = [UIImage]()
Then inside viewDidLoad
fileImages = fileURLs.compactMap { self.loadImage(fileName: $0) }
you are synchronously loading images while returning every cell at indexPath.
cell.imageView.image = loadImage(fileName: self.fileURLs[indexPath.row])
Instead, you can create a custom UICollectionViewCell implementation with a variable in global scope say imageURL.
after cell initialization, do this:
cell.imageURL = self.fileURLs[indexPath.row]
and, in ViewDidLoad() of your custom class, add this line:
self.imageView.image = loadImage(fileName: self.imageURL)
doing so, will lead to images being loaded in each custom implementation of cell unblocking the thread which is invoking the DataSource of your CollectionView.

How to delete files in the app's sandbox?

I have a UITableView set up and its data source is the app's sandbox, so all the rows are filled with the files that were imported using UIDocumentPicker, but I also need to be able to delete those files.
The delete function works, I am able to slide and delete a row and everything, but the file stays in the app's sandbox, so every time I import a new file, the rows are refilled (the TableView reloads every time something is imported) with the previously "deleted" stuff.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCell.EditingStyle.delete {
self.deleteFile()
importedfiles.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
}
}
Plus everything that is in the code already, I need the function to delete the file from the app's sandbox (importedfiles).
Here's what I've got so far, I am able to delete files, but only the entire directory, which is not what I want. Code:
func deleteFile() {
let dirPaths = FM.urls(for: .documentDirectory, in: .userDomainMask)
let docsDir = dirPaths[0].path
if FM.fileExists(atPath: docsDir) {
do {
try FM.removeItem(atPath: docsDir)
} catch {
print("Could not delete file: \(error)")
}
}
}
Edit: "importedfiles" are files that were imported in the app's directory (documents) using UIDocumentPickerViewController. And the TableView uses this data to create cells.
Change your delete method to
func deleteFile(_ url: URL) {
let fm = FileManager.default
do {
try fm.removeItem(at: url)
} catch {
print(error)
}
}
and call it
if editingStyle == UITableViewCell.EditingStyle.delete {
self.deleteFile(importedfiles[indexPath.row])
importedfiles.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
}
This should work but there is no error handling and if you want that you can define your method to return Result
func deleteFile(_ url: URL) -> Result<Void,Error> {
let fm = FileManager.default
do {
try fm.removeItem(at: url)
return .success(())
} catch {
return .failure(error)
}
}
and handle the result in a switch
let result = deleteFile(importedfiles[indexPath.row])
switch result {
case .success:
//Maybe update array and table view here
case .failure(let error):
//dislplay error?
}

Error loading image from Firebase to my Table View

I want to load my images from Firebase to my Table View but I get the error:
Cannot convert value of type 'String' to expected argument type 'URL'
When I print the object on its own it is definitely a URL.
This is what my code looks like:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FeedItem", for: indexPath) as! FeedItem
//TODO: Guard...
let postImage = postArray [indexPath.row]
let postImageURL = postImage.postImageURL
let data = Data(contentsOf: postImageURL) // Line with Error
cell.postImage.image = UIImage (data: data)
return cell
}
To display the image in your cell, you need to convert the URL string into an actual URL object, which you can do via:
let postImage = postArray[indexPath.row]
if let postImageURL = URL(string: postImage.postImageURL)
{
do {
let data = try Data(contentsOf: postImageURL)
cell.postImage.image = UIImage (data: data)
} catch {
print("error with fetching from \(postImageURL.absoluteString) - \(error)")
}
}
And as rmaddy implies, your performance is not going to be very good (because depending on how far away the remote server is or how slow the internet is), the synchronous "Data(contentsOf:" call might take an unacceptably long time to succeed. I'm just providing this answer so you will be able to see something in your own testing, but I wouldn't use this in production code.
Try to replace the Data fetch with an asynchronous URLSession task, and you can find much more information in this very related question.

iOS: Delete file in .DocumentDirectory using Swift2

I have a project I'm working on that saves data to a PDF. The code for this is:
// Save PDF Data
let recipeItemName = nameTextField.text
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
pdfData.writeToFile("\(documentsPath)/\(recipeFileName).pdf", atomically: true)
I'm able to view the files in a separate UITableView I have in another ViewController. When the user swipes the UITableViewCell I want it to also delete the item from the .DocumentDirectory. My code for the UITableView delete is:
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
savedPDFFiles.removeAtIndex(indexPath.row)
// Delete actual row
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
// Deletion code for deleting from .DocumentDirectory here???
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
I've tried finding the answer online but can't find anything for Swift 2. Can someone please help?
I've tried working with this but with no luck:
var fileManager:NSFileManager = NSFileManager.defaultManager()
var error:NSErrorPointer = NSErrorPointer()
fileManager.removeItemAtPath(filePath, error: error)
I just want to remove the particular item swiped and not all data in the DocumentDirectory.
removeItemAtPath:error: is the Objective-C version. For swift, you want removeItemAtPath, like this:
do {
try NSFileManager.defaultManager().removeItemAtPath(path)
} catch {}
In swift, this is a pretty common pattern when working with methods that will throw - prefix the call with try and enclose in do-catch. You will be doing less with error pointers then you would in objective-c. Instead, the errors need to be caught or, as in the snippet above, ignored. To catch and handle the error, you could do your delete like this:
do {
let fileManager = NSFileManager.defaultManager()
let documentDirectoryURLs = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
if let filePath = documentDirectoryURLs.first?.URLByAppendingPathComponent("myFile.pdf").path {
try fileManager.removeItemAtPath(filePath)
}
} catch let error as NSError {
print("ERROR: \(error)")
}
What you want to do is to retrieve the recipeFileName from the edited cell to reconstruct the file path.
It is unclear as to how you are populating your UITableViewCell data, so I will cover the most common scenario.
Assume you have an array of files that you use to populate the dataSource.
let recipeFiles = [RecipeFile]()
with the RecipeFile struct
struct RecipeFile {
var name: String
}
In tableView(_:cellForRowAtIndexPath:), you probably set the recipeFile like so :
cell.recipeFile = recipeFiles[indexPath.row]
so in tableView(_:commitEditingStyle:forRowAtIndexPath:), you can retrieve the file name like this:
let recipeFile = recipeFiles[indexPath.row]
and delete your file
var fileManager:NSFileManager = NSFileManager.defaultManager()
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let filePath = "\(documentsPath)/\(recipeFile.name).pdf"
do {
fileManager.removeItemAtPath(filePath, error: error)
} catch _ {
//catch any errors
}

Swift - tableview.reloadData() crashes the app

I am fetching some data from a plist into a UITableview.
I am trying to delete the data selected however, when I try to reload the data to show just the remaining cells the app crashes.
I think the problem is when I use tableview.reloadData() but I am not sure how to fix this problem. If I don't use reloadData the cell will be deleted when I reopen the view controller.
Any Advice?
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete {
let row = indexPath.row
let plistPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let DocumentsDirectory = plistPath[0] as! String
let path = DocumentsDirectory.stringByAppendingPathComponent("notes.plist")
let fileManager = NSFileManager.defaultManager()
if (!fileManager.fileExistsAtPath(path)) {
if let bundlePath = NSBundle.mainBundle().pathForResource("notes", ofType: "plist") {
let resultDictionary = NSMutableDictionary(contentsOfFile: bundlePath)
println("Bundle notes.plist file is --> \(resultDictionary?.description)")
fileManager.copyItemAtPath(bundlePath, toPath: path, error: nil)
println("copy")
} else {
println("notes.plist not found")
}
} else {
println("note.plist already exists")
//fileManager.removeItemAtPath(path, error: nil)
}
let resultDictionary = NSMutableDictionary(contentsOfFile: path)
//resultDictionary?.removeAllObjects()
resultDictionary?.removeObjectForKey(allKeys[row])
resultDictionary!.writeToFile(path, atomically: false)
println("Loaded notes.plist file is --> \(resultDictionary?.description)")
tableView.reloadData()
}
}
About calling reloadData(), the documentation says: "It should not be called in the methods that insert or delete rows, especially within an animation block implemented with calls to beginUpdates and endUpdates." so its better to just reload the section where you made the changes and call begin and end if animation is involved
tableView.beginUpdates()
tableView.reloadRowsAtIndexPaths(path, withRowAnimation: UITableViewRowAnimation.Automatic)
tableView.endUpdates()
and also its preferable to use your own instance of nsfilemanager since the default one works only in the main thread. and also you're unsafely unwrapping resultDictionary when writing to file, that could cause crash
ps,
let path = DocumentDirectory.stringByAppendingPathComponent("notes.plist")
is replaced by stringByAppendingString n swift 2, just fyi

Resources