Swift - tableview.reloadData() crashes the app - ios

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

Related

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?
}

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

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.

How can I add an image to my table cell in swift?

I'm following this tutorial and I'm creating cells for each json entry. Everything works fine without photos:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("CELL")
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Value1, reuseIdentifier: "CELL")
}
let user:JSON = JSON(self.items[indexPath.row])
// print(user)
let picURL = "/Users/K/Desktop/aproject/apps/address/addr/Images.xcassets/TabMap.imageset/up_dark.png"//just for tests - some random png
let url = NSURL(string: picURL)
let data = NSData(contentsOfURL: url!)
cell!.textLabel?.text = user["description"].string
// cell?.imageView?.image = UIImage(data: data!)
return cell!
}
, but when I add photos (uncomment this line):
cell?.imageView?.image = UIImage(data: data!)
then I'm getting error:
fatal error: unexpectedly found nil while unwrapping an Optional value
My plan is to pass there an url instead of hardcoded photo (so using user["photo"] instead of the link), but for tests I pasted the url to my custom graphic.
How can I add it next to text?
For the local image:
cell!.imageView?.image = UIImage(named:"ImageName")
A couple things:
Why does the line before have "cell!" and the commented out line is "cell?". Also, is this a table view subclass? Are you sure your imageView outlet is connected?
More importantly, you should do an error check when loading your image data.
That is:
if let data = NSData(contentsOfURL: url!)
{
cell!.imageView?.image = UIImage(data: data)
}

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
}

iOS Sound not playing in Swift

I'm building a tableview in which each cell represents a sound that is played when the user taps that particular cell.
In objective-C it worked fine, but now that Apple released Swift I decided to move over instantly, but for some reason the sound does not play. My code when the user taps the cell:
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
var currentItem = self.items[indexPath.row]
var audioPath = NSString(string: NSBundle.mainBundle().pathForResource(currentItem.soundID, ofType: "mp3"))
println(audioPath)
var audioPlayer = AVAudioPlayer(contentsOfURL: NSURL(string: audioPath), error: nil)
audioPlayer.play()
}
currentItem is the object in the array that has to be called. Each sound I can play is put in a custom object, together with a title and an image. that object is put in an instance of currentItem.
This is what the printNL outputs when I tapp one of my cells:
/private/var/mobile/Containers/Bundle/Application/ADF0CAFC-4C9E-475E-B3F0-CD85A1873CA5/Juichen.app/StupidQuestion.mp3
it does not give an error. I already tried moving the sound file to other folders, but that does not solve the problem either. therefore, I assume that this problem occurs because I am calling the audioPlayer incorrect?
Any help would be highly appreciated!
Let say you have a class myTable:
class myTable : UITableViewController
{
var audioPlayer:AVAudioPlayer? = nil
...
}
And to initialize audioPlayer:
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!)
{
var currentItem = self.items[indexPath.row]
var audioPath = NSString(string: NSBundle.mainBundle().pathForResource(currentItem.soundID, ofType: "mp3"))
println(audioPath)
var error : NSError? = nil
self.audioPlayer = AVAudioPlayer(contentsOfURL: NSURL(string: audioPath), error: &error)
if (self.audioPlayer == nil)
{
if let playerError = error as? NSError
{
let des : String? = playerError.localizedDescription
println("Error: \(des)")
}
}
else
{
self.audioPlayer.play()
}
}
Hope this helps.

Resources