Swift 4.0 - Unable to create empty text file in iOS - ios

Below is my code -
I have tried to get the document directory path and with standard FileManager singleton tried to create a file, but I am not able to create the file, as the error -
Unable to store data: Error Domain=NSCocoaErrorDomain Code=4 "The file “CrashLog.txt” doesn’t exist."
UserInfo={NSFilePath=file:///Users/ABC/Library/Developer/CoreSimulator/Devices/87317777-63E7-422B-A55F-878E3267AFB8/data/Containers/Data/Application/4B41AA87-E4B9-4EE4-A67F-AC3B018913CC/Documents/CrashLog,
NSUnderlyingError=0x600000244ec0 {Error Domain=NSPOSIXErrorDomain
Code=2 "No such file or directory"}}
Code in development -
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
if (paths.count > 0) {
let documentsDirectory = paths[0]
let logFilePath = URL(fileURLWithPath: documentsDirectory).appendingPathComponent("CrashLog.txt").absoluteString
let _string = "Hello"
//Create file at given path
let data = _string.data(using: .utf8)
//let attributes = FileManager.default.attributesOfItem(atPath: logFilePath)
let fileExists : Bool = FileManager.default.fileExists(atPath: logFilePath)
print(fileExists)
let isFileCreated = FileManager.default.createFile(atPath: logFilePath, contents: data, attributes: nil)
print("ifFileCreated", isFileCreated)
}

Here's my take on what you've done. Adopt the URL-based means of working with files. The best way to write data (for this example, at least), is to use Data's ability (not FileManager) to write to a file, again, using a URL. In most cases, you don't need to worry whether the file exists or not; just do it, and handle any error that arises.
if var url = try? FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false) {
url = url.appendingPathComponent("CrashLog").appendingPathExtension("txt")
let _string = "Hello"
if let data = _string.data(using: .utf8) {
do {
try data.write(to: url)
print("successful")
} catch {
print("unsuccessful")
}
}
}

The appendingPathComponent method if the receiver (e.g. parameter) does not end with a trailing slash, then it may read file metadata to determine whether the resulting path is a directory. That means it may produce the error you are seeing, so better use the appendingPathComponent(_:isDirectory:) instead.
For example:
let logFilePath = URL(fileURLWithPath: documentsDirectory).appendingPathComponent("CrashLog.txt", isDirectory: false).absoluteString

The API absoluteString is wrong. The correct API is path
absoluteString returns the entire URL string representation including the scheme file://. On the other hand the path API of FileManager expects file system paths, the string without the scheme.
You are encouraged to use the URL related API anyway and you can write Data directly to disk without explicitly creating a file.
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let logFileURL = documentsURL.appendingPathComponent("CrashLog.txt")
let string = "Hello"
let data = Data(string.utf8)
let fileExists = FileManager.default.fileExists(atPath: logFileURL.path)
print(fileExists)
do {
try data.write(to: logFileURL)
print("data written")
} catch { print(error) }

Related

How to save a POST request response as a PDF file persistently such that user may open it from outside the app later in iOS app?

I'm working on an iOS application. In the app, I am using Alamofire to create a POST request that returns a raw PDF file in response. Right now, I am able to save the file and open it with UIDocumentInteractionController. But, I want the file to stay in User's documents folder.
Here's how I create the destination path:
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent("Dividend Summary Report.pdf")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
Someone please tell me what's wrong with my logic and what I can do to correct it.
Well you need to check the file status if it exists then read from documentDirectory else download the file.
Create function like this:
func checkIfFileExists(urlString: String) {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
let fileName = urlString
let fileManager = FileManager.default
let filePath = url.appendingPathComponent("\(fileName).pdf")?.path
print("filePath : \(String(describing: filePath))")
if fileManager.fileExists(atPath: filePath!) {
print("File exists")
} else {
print("File doesn't exists")
// set your download function here
}
}

Getting the name of a file in a directory in swift 5

This portion of the code is supposed to download a txt file from a website. By running this code it is able to successfully download the file from the website and place it into the app's Documents directory. I am able to see where the file is stored because in the last couple of lines in the code it prints out the location of the file. However, I am not able to get the file name that was recently downloaded. The goal is for me to try to get the name of the file so that I can be able to open it and read from it. What are my options in approaching this? What am I missing in this block of code that is preventing me from getting the name of the file that was recently downloaded?
guard let url1 = URL(string: website) else { return }
//This portion of the code focuses on creating a download task with a completion handler
//Completion handler moves the downloaded file to the app's directory
let downloadTask = URLSession.shared.downloadTask(with: url1) {
urlOrNil, responseOrNil, errorOrNil in
// check for and handle errors:
// * errorOrNil should be nil
// * responseOrNil should be an HTTPURLResponse with statusCode in 200..<299
guard let fileURL = urlOrNil else { return }
do {
let documentsURL = try
FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
let savedURL = documentsURL.appendingPathComponent(
fileURL.lastPathComponent)
try FileManager.default.moveItem(at: fileURL, to: savedURL)
} catch {
print ("file error: \(error)")
}
}
downloadTask.resume()
//If you want to receive progress updates as the download proceeds, you must use a delegate.
var urlSession = URLSession(configuration: .default, delegate: self as? URLSessionDelegate, delegateQueue: nil)
func startDownload(url1: URL) -> String? {
let downloadTask = urlSession.downloadTask(with: url1)
let fname = downloadTask.response?.suggestedFilename
downloadTask.resume()
return fname
//self.downloadTask = downloadTask
}
let name = startDownload(url1: url1)
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as NSArray
let documentsDir = paths.firstObject as! String
print("Path to the Documents directory\n\(documentsDir)")
You can get the name of the file at a given path by initialising a URL object from the path:
let name = URL(fileURLWithPath: yourPath).lastPathComponent
This returns an optional string. The name will be automatically unescaped, so it will be human-readable (no percent encoding).
The filename should be available to you from your fileURL variable, as this is where you got the filename in order to save it:
let name = fileURL.lastPathComponent

Access file downloaded with Alamofire to downloadsdirectory with unknown filename

I have an app for iOS that uses metal to render obj files. I am trying to add functionality for users to insert the url of an obj file online and render that. I am using alamofire and am not sure how I will access the file once downloaded, since I won't know the file name.
let destination = DownloadRequest.suggestedDownloadDestination(for: .downloadsDirectory)
let modelUrl = URL(string: "https://drive.google.com/file/d/110KRnku3N_K_EIN-ZLYXK128zjMqxGLM/view?usp=sharing")
Alamofire.download(
modelUrl!,
method: .get,
parameters: Parameters.init(),
encoding: JSONEncoding.default,
headers: nil,
to: destination).downloadProgress(closure: { (progress) in
//progress closure
}).response(completionHandler: { (DefaultDownloadResponse) in
//here you able to access the DefaultDownloadResponse
//result closure
})
let file = try? String(contentsOf: URL(string: (NSSearchPathForDirectoriesInDomains(.downloadsDirectory, .userDomainMask, true)[0]))!)
I am also fairly certain my method for retrieving the file will not work, but i'm not sure how to search the documents directory for a specific file.
The files I have working are in the project as .obj files in xcode, and I simply use this.
let assetURL = Bundle.main.url(forResource: modelName, withExtension: "obj")
Bundle.main will not return files in document directory, it is use for the files you put in your main bundle (inside Xcode while development usually). You need to use FileManager to access files in document directory. You can use this function to search files in your document directory.
func getFilePathInDocuments(fileName:String) -> String {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = URL(fileURLWithPath: path)
let fileManager = FileManager.default
let filePath = url.appendingPathComponent(fileName).path
if (fileManager.fileExists(atPath: filePath)) {
return filePath
}else{
return ""
}
}
This is how you call it:
let foundPath = getFilePathInDocuments(fileName: "fileName.obj")
Update:
You can give a fileName to Almofire and you will receive the downloaded URL too from it.
let destinationPath: DownloadRequest.DownloadFileDestination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0];
let fileURL = documentsURL.appendingPathComponent("fileName")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(url, to: destinationPath)
.downloadProgress { progress in
}
.responseData { response in
}
To get downloaded document directory URL use response.destinationURL.

How to save text file as variable in IOS

I am sending some images via socket and I would like to create text file with information about the images to send over the network as well. I right now I can send the images no problem by creating a variable for the image data like so
let imageData = UIImageJPEGRepresentation(someUIImage, 1.0)
How do create a variable with the data of the text file?
let textData = someTextFileAsData.....
Is this what you want?
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
// Get the URL to the file. Below is an example
let fileURL = documentsDirectory.appendingPathComponent("test").appendingPathExtension("txt") // Replace "test" with your fileName and "txt" with your fileExtension
var text = ""
do {
text = try String(contentsOf: fileURL)
} catch {
fatalError("error: \(error.localizedDescription)")
}
Expanding a bit on Anthonin C.'s answer:
Each file in iOS is identified by a URL (just like a webpage, but this is a URL referring to the iOS filesystem). There are a few places in the system where you can save files, most of which you can get the URLs for by reading
FileManager.default.urls(for: SearchPathDirectory, in: SearchPathDomainMask)
(FileManager class reference).
For instance, to save in the user's personal "documents" directory, you would do:
let fileName = "socketLog.txt"
if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
//build full path to file
let path = dir.appendingPathComponent(fileName)
do {
try text.write(to: path, atomically: false, encoding: String.Encoding.utf8)
}
catch {/* error handling here */}
}
where text would be the string data you want to save.
You should use write(to:​ URL, atomically:​ Bool) or write(to​File:​ String, atomically:​ Bool) of NSData method to write your data in a file.
In your case it would be :
let imageData = UIImageJPEGRepresentation(someUIImage, 1.0)
imageData.writeToFile("imageData.txt", atomically:true)
You can then restore it like that :
var imageData = NSData(contentsOfFile: "imageData.txt")
And to get back the image from data :
let image : UIImage = UIImage(data: imageData)

Rename file in DocumentDirectory

I have a PDF file in my DocumentDirectory.
I want the user to be able to rename this PDF file to something else if they choose to.
I will have a UIButton to start this process. The new name will come from a UITextField.
How do I do this? I'm new to Swift and have only found Objective-C info on this and am having a hard time converting it.
An example of the file location is:
/var/mobile/Containers/Data/Application/39E030E3-6DA1-45FF-BF93-6068B3BDCE89/Documents/Restaurant.pdf
I have this code to see check if the file exists or not:
var name = selectedItem.adjustedName
// Search path for file name specified and assign to variable
let getPDFPath = paths.stringByAppendingPathComponent("\(name).pdf")
let checkValidation = NSFileManager.defaultManager()
// If it exists, delete it, otherwise print error to log
if (checkValidation.fileExistsAtPath(getPDFPath)) {
print("FILE AVAILABLE: \(name).pdf")
} else {
print("FILE NOT AVAILABLE: \(name).pdf")
}
To rename a file you can use NSFileManager's moveItemAtURL.
Moving the file with moveItemAtURL at the same location but with two different file names is the same operation as "renaming".
Simple example:
Swift 2
do {
let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let documentDirectory = NSURL(fileURLWithPath: path)
let originPath = documentDirectory.URLByAppendingPathComponent("currentname.pdf")
let destinationPath = documentDirectory.URLByAppendingPathComponent("newname.pdf")
try NSFileManager.defaultManager().moveItemAtURL(originPath, toURL: destinationPath)
} catch let error as NSError {
print(error)
}
Swift 3
do {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let documentDirectory = URL(fileURLWithPath: path)
let originPath = documentDirectory.appendingPathComponent("currentname.pdf")
let destinationPath = documentDirectory.appendingPathComponent("newname.pdf")
try FileManager.default.moveItem(at: originPath, to: destinationPath)
} catch {
print(error)
}
The modern way is (url is the file URL of a file in your sandbox):
var rv = URLResourceValues()
rv.name = newname
try? url.setResourceValues(rv)
There is an easier way to rename item at any given NSURL.
url.setResourceValue(newName, forKey: NSURLNameKey)
Edit - Swift4
url.setTemporaryResourceValue(newName, forKey: .nameKey)

Resources