NSKeyedArchiver.archiveRootObject fails to save - swift

I have a group of custom objects that I'm converting to NSMutableDictionary's, and then creating an array out of them (this part is currently working as expected).
I'm then attempting to save that array of data as a file using NSKeyedArchiver. However, the result of NSKeyedArchiver.archiveRootObject always returns false.
Below is my logic for saving - am I missing something obvious, or perhaps is this the wrong approach? Thank you!
var groupsArray = [Any?]()
for group in file!.groups{
for obj in group.children {
let objDict = obj.convertToDictionary()
groupsArray.append(objDict)
}
}
let documents: String = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let filePath: String = URL(fileURLWithPath: documents).appendingPathComponent("file.archive").absoluteString
let save: Bool = NSKeyedArchiver.archiveRootObject(groupsArray, toFile: filePath)
EDIT: This also fails if trying to save to the .desktop or the .caches directories.

The issue here is .absoultestring. If the URL object contains a file URL, then we should use .path for working with FileManager or PathUtilities etc. So here replacing .absoultestring with .path will solve the issue
for more details about their difference please refer this answer

try this method to save
1.Method returns filepath.
func filePath(key:String) -> String {
let manager = FileManager.default
let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first
return (url!.appendingPathComponent(key).path)
}
2.Code to save to a file using NSKeyedArchiver.
NSKeyedArchiver.archiveRootObject(groupsArray, toFile: filePath(key: "file.archive"))

Related

How to sort the filemanager array by creation date

I have written code that retrieves CSV paths in the documents directory and loads them into a tableview. I am trying to sort the files by creation date so that they list from newest to oldest in the tableview. Anyone got any advice on how to accomplish this?
I've not tried anything yet, because I am a little stuck
override func viewDidLoad() {
super.viewDidLoad()
csvFiles = listCsvs()
tblViewDataCSV.dataSource = self
tblViewDataCSV.delegate = self
}
func listCsvs() -> [URL] {
let fileManager = FileManager.default
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let files = try? fileManager.contentsOfDirectory(
at: documentDirectory,
includingPropertiesForKeys: nil,
options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles]
).filter {
$0.lastPathComponent.hasSuffix(".csv")
}
print(files as Any)
return files ?? []
}
I need the array sorted by .creationdate and not alphanumerically. Many thanks for your help.
You need to declare a struct that has a URL (or filename String) and a Date. Populate an array of this struct from the files (and their creation dates) you query from FileManager.
Use that array of struct as your data model for the table view. You can sort the array on filename or date (or any other attributes you might add in the future).
You can get the creation date of each file by first adding [.creationDateKey] to the includingPropertiesForKeys parameter of contentsOfDirectory. Then access the creation date using resourceValues on each URL. See How can I get the file creation date using URL resourceValues method in Swift 3? for more details on getting the creation date.
It may help to use the enumerator method of FileManager instead of contentsOfDirectory. This will make it easier to get the needs URL attributes and populate the array of struct.
You can build a struct like the below:
struct yourStruct {
var path:URL
var filedate:Date
}
For each file in folder, append your struct into array.
let s = yourStruct(
path: csvUrl,
filedate: csvFileDate)
myArray.append(s)
At this point, you have an array with your files and filedates.
And finally sort the array with:
let newArr = myArray.sorted { $0.filedate < $1.filedate }

NSArrayURL , userDefault filePath and URL

I have one pdf source code and I want to add the Url in Array and use UserDefault
let defaults = UserDefaults.standard
struct Constants {
static let myKeyURL = "myKeyUrl"
}
I download the Pdf Like This
let documentsPath =NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let fileName = urlString as NSString;
let filePath="\(documentsPath)/\(fileName.lastPathComponent)";
After I save the Path like This
var arrayUrl = [String]()
arrayUrl.append(filePath)
self.defaults.set(arrayUrl, forKey: Constants.myKeyURL)
Now I want to Read
var arrayUrl = [String]()
defaults.stringArray(forKey: Constants.myKeyURL)
arrayUrl = defaults.stringArray(forKey: Constants.myKeyURL)!
I need to Read in This Model
documents = arrayUrl.flatMap { PDFDocument(url: $0 ) }
But I received
Cannot convert value of type 'String' to expected argument type 'URL'
I need this URL (arrayUrl) File in this format file:///private/var/mobile/Containers/Data/Application/----/Documents/Sample.pdf
The error is clear:
PDFDocument(url: expects URL, you pass String which is a classic type mismatch.
You have to create URL instances from the strings
documents = arrayUrl.flatMap { PDFDocument(url: URL(fileURLWithPath: $0) ) }
However you are discouraged from saving the full path because the path to the Documents folder changes. Save only the file name or relative path and get the actual path to the Documents folder on each application launch.

Read all files from a directory?

I am trying to create a folder in my assets, then get a list of files inside.
Sounds simple but there is no clean answer on how to do exactly this.
Even to get the list from the main directory, most people can't do on Swift 3, reading here : Getting list of files in documents folder
using :
let fileMngr = FileManager.default;
// Full path to documents directory
let docs = fileMngr.urls(for: .documentDirectory, in: .userDomainMask)[0].path
// List all contents of directory and return as [String] OR nil if failed
return try? fileMngr.contentsOfDirectory(atPath:docs)
Not working.
Reading from a specific folder, I couldn't understand how to get it's path for swift.
Any example that really work that reads from a folder ?
If you want to get all files in a personal directory, here is the simple answer
do {
let documentURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let Path = documentURL.appendingPathComponent("yourDirectoyName").absoluteURL
let directoryContents = try FileManager.default.contentsOfDirectory(at: Path, includingPropertiesForKeys: nil, options: [])
}
catch {
print(error.localizedDescription)
}
And then if you want for example to read all files with special extension, you can do it that way
static func listAllFileNamesExtension(nameDirectory: String, extensionWanted: String) -> (names : [String], paths : [URL]) {
let documentURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let Path = documentURL.appendingPathComponent(nameDirectory).absoluteURL
do {
try FileManager.default.createDirectory(atPath: Path.relativePath, withIntermediateDirectories: true)
// Get the directory contents urls (including subfolders urls)
let directoryContents = try FileManager.default.contentsOfDirectory(at: Path, includingPropertiesForKeys: nil, options: [])
// if you want to filter the directory contents you can do like this:
let FilesPath = directoryContents.filter{ $0.pathExtension == extensionWanted }
let FileNames = FilesPath.map{ $0.deletingPathExtension().lastPathComponent }
return (names : FileNames, paths : FilesPath);
} catch {
print(error.localizedDescription)
}
return (names : [], paths : [])
}
So if you want to have all your json files in your personal directory
let allJsonNamePath = listAllFileNamesExtension(nameDirectory:"yourDirectoryName", extensionWanted: "json")
Swift 4/5
let documentDirectoryPath:String = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let myFilesPath = "\(documentDirectoryPath)/myfolder"
let filemanager = FileManager.default
let files = filemanager.enumerator(atPath: myFilesPath)
while let file = files?.nextObject() {
print(file)
}
Actually, amazingly, for 2 days no one could tell me the real issue here.
Creating a group, is completely different from dragging a folder into the project.
For some reason, with Apple, its always complicated with files. I have to figure out the NOT so intuitive approach that a group that looks like a folder, is nothing but a nice way to look at something, and will not create a real folder accessible by the file manager.
This strange approach is maybe intutitive to a very pro programmer, but really not to any simple person.
Simply put, create a blue folder outside Xcode and drag it in.

FileManager.contentsEqual returns false when comparing copied files

I need to preload SQLite files from my bundle's resources into the application support directory. I want to make sure the correct files are there vs. the empty files that Core Data puts there by default. To do this, I'm using FileManager.default.contentsEqual; however, this always returns false.
I tried testing with a playground, but the copy there is creating alias files, still resulting in a false comparison.
In the app, the files do copy over with the same name and size. The dates are different: the copies have the current date/time rather than the original's timestamps. Using contentsEqual, though, I wouldn't think that matters.
Update: diff at the command line shows the files are the same...
What am I missing?
Here's the code from the playground, which is virtually the same as my app code:
// get the URL for the application support directory
let appSupportDir: URL = try!
FileManager.default.url(for: FileManager.SearchPathDirectory.applicationSupportDirectory,
in: FileManager.SearchPathDomainMask.userDomainMask,
appropriateFor: nil, create: true)
// get the source URLs for the preload files
let sqliteFileBundleURL: URL = Bundle.main.url(forResource: "My_DB", withExtension: "sqlite")!
let sqliteShmFileBundleURL: URL = Bundle.main.url(forResource: "My_DB", withExtension: "sqlite-shm")!
let sqliteWalFileBundleURL: URL = Bundle.main.url(forResource: "My_DB", withExtension: "sqlite-wal")!
// create target URLs for copy to application support directory
let sqliteFileAppSptURL: URL = appSupportDir.appendingPathComponent("My_DB.sqlite")
let sqliteShmFileAppSptURL: URL = appSupportDir.appendingPathComponent("My_DB.sqlite-shm")
let sqliteWalFileAppSptURL: URL = appSupportDir.appendingPathComponent("My_DB.sqlite-wal")
// remove the files if they already exist at the target (for test - app doesn't do this)
do {
let filesFound: [URL] = try FileManager.default.contentsOfDirectory(at: appSupportDir,
includingPropertiesForKeys: nil,
options: .skipsHiddenFiles)
if !filesFound.isEmpty {
for fileURL in filesFound {
try FileManager.default.removeItem(at: fileURL)
}
print("Removed \(filesFound.count) files without error.")
}
}
catch {
print("Error:\n\(error)")
}
// copy the files to the application support directory
do {
try FileManager.default.copyItem(at: sqliteFileBundleURL, to: sqliteFileAppSptURL)
try FileManager.default.copyItem(at: sqliteShmFileBundleURL, to: sqliteShmFileAppSptURL)
try FileManager.default.copyItem(at: sqliteWalFileBundleURL, to: sqliteWalFileAppSptURL)
}
catch {
print("Error: \(error)")
}
// compare the copied target files to their source using contentsEqual
let sqliteFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteFileBundleURL.absoluteString, andPath: sqliteFileAppSptURL.absoluteString)
let sqliteShmFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteShmFileBundleURL.absoluteString, andPath: sqliteShmFileAppSptURL.absoluteString)
let sqliteWalFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteWalFileBundleURL.absoluteString, andPath: sqliteWalFileAppSptURL.absoluteString)
Aha! When using FileManager, one should be using path rather than absoluteString to convert a URL to a String:
// compare the copied target files to their source using contentsEqual
let sqliteFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteFileBundleURL.path, andPath: sqliteFileAppSptURL.path)
let sqliteShmFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteShmFileBundleURL.path, andPath: sqliteShmFileAppSptURL.path)
let sqliteWalFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteWalFileBundleURL.path, andPath: sqliteWalFileAppSptURL.path)
The difference between the two is that path generates a file system-type path:
/var/folders/kb/y2d_vrl133d1b04_5kc3kw880000gn/T/com.apple.dt.Xcode.pg/resources/238FF955-236A-42FC-B6EA-9A74FC52F235/My_DB.sqlite
whereas absoluteString generates a browser-friendly path:
file:///var/folders/kb/y2d_vrl133d1b04_5kc3kw880000gn/T/com.apple.dt.Xcode.pg/resources/238FF955-236A-42FC-B6EA-9A74FC52F235/My_DB.sqlite
Note: path also works in the playground with the alias files.

Cannot delete element in directory in Swift [duplicate]

How is it possible?
let exists = NSFileManager.defaultManager().fileExistsAtPath(path.absoluteString)
print("exists: \(exists)") //false
This is path.absoluteString
//file:///Users/kuna/Library/Developer/CoreSimulator/Devices/92BD140D-5C14-43C4-80D6-904BB9594ED6/data/Containers/Data/Application/5B818832-BB19-4047-A7F8-1487F36868D6/Documents/wishlists/68/147/128/IMG_0006.PNG
And you can see it is there where it should be:
What is going on?
(The code in this answer has been updated for Swift 3 and later.)
Apparently your path variable is a NSURL (describing a file path). To get the path as
a string, use the path property, not absoluteString:
let exists = FileManager.default.fileExists(atPath: path.path)
absoluteString returns the URL in a string format, including
the file: scheme etc.
Example:
let url = URL(fileURLWithPath: "/path/to/foo.txt")
// This is what you did:
print(url.absoluteString)
// Output: file:///path/to/foo.txt
// This is what you want:
print(url.path)
// Output: /path/to/foo.txt
If you want to check if a path exist,you should check path
let url = NSURL(string: "balabala")
let path = url?.path
//Check path