How to write Dictionary to a file? - swift3

I have a FileHelper class where I've implemented 3 methods whose job is to write a Dictionary contents to a file. Those methods are:
func storeDictionary(_ dictionary: Dictionary<String, String>, inFile fileName: String, atDirectory directory: String) -> Bool {
let ext = "txt"
let filePath = createFile(fileName, withExtension: ext, atDirectory: directory)
/**** //If I use this method, file is created and dictionary is saved
guard (dictionary as NSDictionary).write(to: filePath!, atomically: true) else {
return false
}
*/
guard NSKeyedArchiver.archiveRootObject(dictionary, toFile: (filePath?.absoluteString)!) else {
return false
}
return true
}
func createFile(_ file: String, withExtension ext: String, atDirectory directory: String) -> URL? {
let directoryPath = createDirectory(directory)
let filePath = directoryPath?.appendingPathComponent(file).appendingPathExtension(ext)
if !FileManager.default.fileExists(atPath: (filePath?.absoluteString)!) {
let success = FileManager.default.createFile(atPath: (filePath?.absoluteString)!, contents: nil, attributes: nil)
print("\(success)") //** here is the issue I investigated. Always prints false.
}
return filePath
}
func createDirectory(_ directory: String) -> URL? {
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let directoryPath = documentsDirectory.appendingPathComponent(directory)
do {
try FileManager.default.createDirectory(at: directoryPath, withIntermediateDirectories: true, attributes: nil)
} catch let error as NSError {
fatalError("Error creating directory: \(error.localizedDescription)")
}
return directoryPath
}
When I call FileHelper().storeDictionary(aValidDictionary, inFile: "abc", atDirectory: "XYZ") to write the dictionary, it fails with this procedure. But if I use
guard (dictionary as NSDictionary).write(to: filePath!, atomically: true) else {
return false
}
it works.
What's wrong with NSKeyedArchiver.archiveRootObject(_:toFile:) method??
And why FileManager.default.createFile(atPath: (filePath?.absoluteString)!, contents: nil, attributes: nil) always returns false?

First of all filePath?.absoluteString returns the entire – even percent escaped – string including the file:// scheme and the method expects a path without the scheme (filePath?.path - the naming is a bit confusing ;-) ).
I recommend to save a [String:String] dictionary as property list file. It's not necessary to create the file explicitly.
I changed the signatures of the methods slightly in the Swift-3-way. Further there is no need to use any optional type.
func store(dictionary: Dictionary<String, String>, in fileName: String, at directory: String) -> Bool {
let fileExtension = "plist"
let directoryURL = create(directory:directory)
do {
let data = try PropertyListSerialization.data(fromPropertyList: dictionary, format: .xml, options: 0)
try data.write(to: directoryURL.appendingPathComponent(fileName).appendingPathExtension(fileExtension))
return true
} catch {
print(error)
return false
}
}
func create(directory: String) -> URL {
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let directoryURL = documentsDirectory.appendingPathComponent(directory)
do {
try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
} catch let error as NSError {
fatalError("Error creating directory: \(error.localizedDescription)")
}
return directoryURL
}
PS: Instead of returning a Bool you could make the store method can throw and handle the error in the calling method:
func store(dictionary: Dictionary<String, String>, in fileName: String, at directory: String) throws {
let fileExtension = "plist"
let directoryURL = create(directory:directory)
let data = try PropertyListSerialization.data(fromPropertyList: dictionary, format: .xml, options: 0)
try data.write(to: directoryURL.appendingPathComponent(fileName).appendingPathExtension(fileExtension))
}

Here's a swift 5 extension that should save any Dictionary where the Key and Value are Codable
extension Dictionary where Key: Codable, Value: Codable {
static func load(fromFileName fileName: String, using fileManager: FileManager = .default) -> [Key: Value]? {
let fileURL = Self.getDocumentsURL(on: .cachesDirectory, withName: fileName, using: fileManager)
guard let data = fileManager.contents(atPath: fileURL.path) else { return nil }
do {
return try JSONDecoder().decode([Key: Value].self, from: data)
} catch(let error) {
print(error)
return nil
}
}
func saveToDisk(on directory: FileManager.SearchPathDirectory,
withName name: String,
using fileManager: FileManager = .default) throws {
let fileURL = Self.getDocumentsURL(on: .cachesDirectory, withName: name, using: fileManager)
let data = try JSONEncoder().encode(self)
try data.write(to: fileURL)
}
private static func getDocumentsURL(on directory: FileManager.SearchPathDirectory,
withName name: String,
using fileManager: FileManager) -> URL {
let folderURLs = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)
let fileURL = folderURLs[0].appendingPathComponent(name)
return fileURL
}
}
Usage:
let myDict = [MyKey: MyValue].load(from: diskDirectory, andFileName: diskFileName) // load
try myDict.saveToDisk(on: diskDirectory, withName: diskFileName) // save

Related

How to Get the name of the Created Directory in Swift 3

I am new to iOS , I have created this method to create a directory :
static func fileHandler () {
let Mainpath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let myPath = Mainpath + "/Images Folder "
print (myPath)
var Myobjbool :ObjCBool = true
let isExist = FileManager.default.fileExists(atPath: myPath, isDirectory: &Myobjbool)
if isExist == false{
do {
try FileManager.default.createDirectory(atPath: myPath, withIntermediateDirectories: true, attributes: nil)
print (FileManager.default.currentDirectoryPath)
}catch {
print (error)
}
}
}
so my question is that , How can i return the path created by this function ?

How to pass foldername to save file into the folder?

I was doing download system with the code from https://stackoverflow.com/a/32322851/7789222. It was a great and complete code but I can find a way to pass foldername from view controller to download file to specific folder. Can anyone help me with it please. I am using swift 3 xcode 8.
If I hard code the custom directory in func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) , every file will be downloaded to same folder. I want to pass the foldername from view controller so I can download files to different folder. I cant hardcode because I retrieve file name and foldername from server
The destination URL in the example is given by
let destinationURL = try manager.url(
for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false
).appendingPathComponent(url.lastPathComponent)
(Line 17)
You can just pass a destination folder URL to the initializer of DownloadOperation which replaces the destination URL in the example:
let destinationURL = yourDestinationFolder.appendingPathComponent(url.lastPathComponent)
Your modified DownloadOperation would look something like this:
class DownloadOperation : AsynchronousOperation {
var task: URLSessionTask!
let destinationFolder: URL
init(session: URLSession, url: URL, destinationFolder: URL) {
super.init()
self.destinationFolder = destinationFolder
task = session.downloadTask(with: url) { temporaryURL, response, error in
defer { self.completeOperation() }
guard error == nil && temporaryURL != nil else {
print("\(error)")
return
}
do {
let manager = FileManager.default
let destinationURL = destinationFolder.appendingPathComponent(url.lastPathComponent)
_ = try? manager.removeItem(at: destinationURL) // remove the old one, if any
try manager.moveItem(at: temporaryURL!, to: destinationURL) // move new one there
} catch let moveError {
print("\(moveError)")
}
}
}
...
}
The code for adding operations is then
queue.addOperation(DownloadOperation(session: session, url: url, destinationFolder: destinationFolder))
If you want to use the DownloadManager:
class DownloadManager {
#discardableResult
func addDownload(_ url: URL, to destinationFolder: URL) -> DownloadOperation {
let operation = DownloadOperation(session: session, url: url, destinationFolder: destinationFolder)
operations[operation.task.taskIdentifier] = operation
queue.addOperation(operation)
return operation
}
...
}
The extension:
extension DownloadOperation: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
do {
let manager = FileManager.default
let destinationURL = destinationFolder.appendingPathComponent(downloadTask.originalRequest!.url!.lastPathComponent)
if manager.fileExists(atPath: destinationURL.path) {
try manager.removeItem(at: destinationURL)
}
try manager.moveItem(at: location, to: destinationURL)
} catch {
print("\(error)")
}
}
...
}
Then you can add downloads with
downloadManager.addDownload(url, to: destinationFolder)

Setting Alamofire custom destination file name instead of using suggestedDownloadDestination in Swift 3.0

How to write the following snippet in swift 3.0 ? The following syntax is in swift 2
Alamofire.download(.POST, invoice.url,parameters:params, destination: { (url, response) -> NSURL in
let pathComponent = response.suggestedFilename
let fileManager = NSFileManager.defaultManager()
let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
let fileUrl = directoryURL.URLByAppendingPathComponent(pathComponent)
return fileUrl
})
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
print(totalBytesRead)
dispatch_async(dispatch_get_main_queue()) {
let progress = Double(totalBytesRead) / Double(totalBytesExpectedToRead)
completionHandler(progress, nil)
}
}
.responseString { response in
print(response.result.error)
completionHandler(nil, response.result.error)
}
In Swift 3 it is something like this.
let parameters: Parameters = ["foo": "bar"]
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let pathComponent = "yourfileName"
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendPathComponent(pathComponent)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(urlString, method: .get, parameters: parameters, encoding: JSONEncoding.default, to: destination)
.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
print("Progress: \(progress.fractionCompleted)")
}
.validate { request, response, temporaryURL, destinationURL in
// Custom evaluation closure now includes file URLs (allows you to parse out error messages if necessary)
return .success
}
.responseJSON { response in
debugPrint(response)
print(response.temporaryURL)
print(response.destinationURL)
}
Check Alamofire Documentation or Alamofire 4.0 Migration Guide for more details.
Use func appendingPathComponent(_ pathComponent: String) -> URL instead of appendPathComponent.
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let pathComponent = "yourfileName"
let directoryURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let folderPath: URL = directoryURL.appendingPathComponent("Downloads", isDirectory: true)
let fileURL: URL = folderPath.appendingPathComponent(pathComponent)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
and it is also possible to use response.
let destination: DownloadRequest.DownloadFileDestination = { _, response in
let pathComponent = response.suggestedFilename!
let directoryURL: URL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
let folderPath: URL = directoryURL.appendingPathComponent("Downloads", isDirectory: true)
let fileURL: URL = folderPath.appendingPathComponent(pathComponent)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}

Issue Getting NSData Request To Work In Swift 2.0

I'm hoping someone may be able to help me figure out a snafu I'm having with an app I am trying to write (or learn to write) in Swift 2.0. This previously worked in Swift 1.2, but after the necessary conversions, I am continunally facing the error;
Cannot invoke initializer of type 'NSData' with an argument list of type '(contenOfURL: NSURL, options: NSDataReadingOptions, error:nil)'
Here is my code, slightly truncated, that I am using;
...
class func fetchMinionData() -> [Minion] {
let myURL = "https://myurl/test.json"
let dataURL = NSURL(string: myURL)
let data = NSData(contentsOfURL: dataURL!, options: NSDataReadingOptions.DataReadingMappedIfSafe, error: nil)
//THIS IS THE LINE THAT THROWS THE ERROR
let minionJSON = JSON(data)
var minions = [Minion]()
for (_ , minionDictionary) in minionJSON {
minions.append(Minion(minionDetails: minionDictionary))
}
return minions
}
...
Note that I plan to use the SwiftyJSON library to further parse the data once it is downloaded. I am searching endlessly online, but I just can't seem to figure this out! Thank you!
If you are working with Swift 2, you should not pass the last argument "error". Instead put a try around the NSData initialization. If data needs to be accessed outside take the init result in a var and convert to let Modified code
var optData:NSData? = nil
do {
optData = try NSData(contentsOfURL: dataURL!, options: NSDataReadingOptions.DataReadingMappedIfSafe)
}
catch {
print("Handle \(error) here")
}
if let data = optData {
// Convert data to JSON here
}
Example code for Dictionary :) Swift 2.0
https://github.com/DaRkD0G/LoadExtension/edit/master/LoadExtensionDictionary.swift
enum EHError: ErrorType {
case Nil(String)
case NSData(String)
case JSON(String)
}
extension Dictionary {
/**
Loads a JSON file from the app bundle into a new dictionary
- parameter filename: File name
- throws: PathForResource / NSData / JSON
- returns: Dictionary<String, AnyObject>
*/
static func loadJSONFromBundle(filename: String) throws -> Dictionary<String, AnyObject> {
guard let path = NSBundle.mainBundle().pathForResource(filename, ofType: "json") else {
throw EHError.Nil("[EasyHelper][loadJSONFromBundle][->pathForResource] The file could not be located\nFile : '\(filename).json'")
}
guard let data = try? NSData(contentsOfFile: path, options:NSDataReadingOptions()) else {
throw EHError.NSData("[EasyHelper][loadJSONFromBundle][->NSData] The absolute path of the file not find\nFile : '\(filename)'")
}
guard let jsonDict = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as? Dictionary<String, AnyObject> else {
throw EHError.JSON("[EasyHelper][loadJSONFromBundle][->NSJSONSerialization]Error.InvalidJSON Level file '\(filename)' is not valid JSON")
}
return jsonDict
}
}
If I do not do a mistake, for you is that
/**
Loads a JSON file from the app bundle into a new dictionary
- parameter filename: File name
- throws: EHError : PathForResource / NSData / JSON
- returns: [String : AnyObject]
*/
static func loadJSONFromBundle(filename: String, nameJson:String) throws -> [String : AnyObject] {
guard let path = NSBundle.mainBundle().pathForResource(filename, ofType: "json") else {
throw EHError.Nil("[EasyHelper][loadJSONFromBundle][->pathForResource] The file could not be located\nFile : '\(filename).json'")
}
guard let data = try? NSData(contentsOfFile: path, options:NSDataReadingOptions()) else {
throw EHError.NSData("[EasyHelper][loadJSONFromBundle][->NSData] The absolute path of the file not find\nFile : '\(filename)'")
}
guard let jsonDict = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as? [String : AnyObject] else {
throw EHError.JSON("[EasyHelper][loadJSONFromBundle][->NSJSONSerialization] Invalid JSON\n nameJson '\(nameJson)'\nFile '\(filename)'")
}
return jsonDict
}

Handling Facebook Graph API result in iOS SDK with Swift

I just want to request data from Facebook's Graph API, e.g. get the current user's basic info.
The Objective-C doc is: https://developers.facebook.com/docs/ios/graph#userinfo
[FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error) {
/* My question: How do I read the contents of "result" in Swift? */
// Success! Include your code to handle the results here
NSLog(#"user info: %#", result);
} else {
// An error occurred, we need to handle the error
// See: https://developers.facebook.com/docs/ios/errors
}
}];
There's no Swift doc yet, and I'm confused about the "result" parameter whose type is "id".
It looks like result contains a dictionary, but it may be nil. In Swift, its type will map to AnyObject?.
So, in Swift, you could do something like:
// Cast result to optional dictionary type
let resultdict = result as? NSDictionary
if resultdict != nil {
// Extract a value from the dictionary
let idval = resultdict!["id"] as? String
if idval != nil {
println("the id is \(idval!)")
}
}
This can be simplified a bit:
let resultdict = result as? NSDictionary
if let idvalue = resultdict?["id"] as? String {
println("the id value is \(idvalue)")
}
Just remember it is not a dictionary all the way down, it is combinations of dictionaries and arrays.
FBRequestConnection.startWithGraphPath("me?fields=feed", completionHandler: { (connection, result, error) -> Void in
if( error == nil){
let fbGraphObject = result as FBGraphObject
let feed = fbGraphObject.objectForKey("feed") as NSMutableDictionary
let data = feed.objectForKey("data") as NSMutableArray
let postDescription = data[0].objectForKey("description") as String
//println( post )
self.fbu.initialUserFeed = feed
self.performSegueWithIdentifier("SelectStreams", sender: self)
}else
{
//TODO Allert to user that something went wrong
println(error)
}
})
I got confused about this in the beginning
This is a simpler way:
let params: [NSObject : AnyObject] = ["redirect": false, "height": 800, "width": 800, "type": "large"]
let pictureRequest = FBSDKGraphRequest(graphPath: "me/picture", parameters: params, HTTPMethod: "GET")
pictureRequest.startWithCompletionHandler({
(connection, result, error: NSError!) -> Void in
if error == nil {
print("\(result)")
let dictionary = result as? NSDictionary
let data = dictionary?.objectForKey("data")
let urlPic = (data?.objectForKey("url"))! as! String
print(urlPic)
} else {
print("\(error)")
}
})
}