I am getting an error when writing an image file to a directory in Xcode. The function data.writeToFile is returning an error. Here is what I am trying to do:
Get The File Path:
func getPath(fileName: String) -> String {
let documentURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
let folder = "sampleDirectory"
return documentURL.URLByAppendingPathComponent(folder).URLByAppendingPathComponent(fileName).path!
}
Save the Image
func saveImage(image: UIImage, path: String) -> Bool {
let pngImageData = UIImagePNGRepresentation(image)
do {
let success = try pngImageData?.writeToFile(path, options: NSDataWritingOptions.init(rawValue: 0))
} catch {
print(error)
}
return false
}
However, there is an error saying:
NSPOSIXErrorDomain - code : 2
Does anyone know what the problem could be?
EDIT
Where I call the code:
let fileName = "first_image"
let imagePath = self.getPath(fileName)
let result = self.saveImage(processedImage, path: imagePath)
processedImage is of type UIImage!
Try creating the directory "sampleDirectory" if it does not exists or don't use a subdirectory.
You can check if the directory exists with:
if !NSFileManager.defaultManager().fileExistsAtPath(path) {
// create missing directories
try! NSFileManager.defaultManager().createDirectoryAtPath(foo, withIntermediateDirectories: true, attributes: nil)
}
You also might want to use the option NSDataWritingOptions.DataWritingAtomic which first write to an auxiliary file first and then exchange the files if there were no errors
Related
I have an issues in changing the file path at every launch of the app.
I have a file("AppConstant.json") in application bundle, and this file I need to copy into application document directory. I am successfully saving "AppConstant.json" file inside the created user folder "MyFolder" on Document directory.
But the problem is when I relaunch the application second time, it's not showing the same path. Also I am using relativepath, but still it not getting.
here is the code
// calling the directory
let stringAppConstant = copyFileFromBundleToDocumentDirectory(resourceFile: "AppConstant", resourceExtension: "json")
// saving or get exit file path
func copyFileFromBundleToDocumentDirectory(resourceFile: String, resourceExtension: String) -> String
{
var stringURLPath = "Error_URLPath"
let fileManager = FileManager.default
let docURL = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let destFolderPath = URL(string:docURL)?.appendingPathComponent("MyFolder")
let fileName = "\(resourceFile).\(resourceExtension)"
guard let newDestPath = destFolderPath, let sourcePath = Bundle.main.path(forResource: resourceFile, ofType: ".\(resourceExtension)"), let fullDestPath = NSURL(fileURLWithPath: newDestPath.absoluteString).appendingPathComponent(fileName) else {
return stringURLPath
}
if !fileManager.fileExists(atPath: newDestPath.path) {
do {
try fileManager.createDirectory(atPath: newDestPath.path,withIntermediateDirectories: true, attributes: nil)
print("Created folder successfully in :::", newDestPath.path)
} catch {
print("Error in creating folder :::",error.localizedDescription);
}
}
else {
print("Folder is already exist!")
}
if fileManager.fileExists(atPath: fullDestPath.path) {
print("File is exist in ::: \(fullDestPath.path)")
stringURLPath = fullDestPath.path
}
else {
do {
try fileManager.copyItem(atPath: sourcePath, toPath: fullDestPath.path)
print("Saved file successfully in :::", fullDestPath.path)
stringURLPath = fullDestPath.path
} catch {
print("Error in creating file ::: \(error.localizedDescription)")
}
}
return stringURLPath
}
Please help me, where I need to save the path in Sandbox. Is this right way what I implemented.
I am running in device and simulator, both path are different while relaunch
this is the path for first time launch:
/var/mobile/Containers/Data/Application/81B568A7-0932-4C3E-91EB-9DD62416DFE8/Documents/MyFolder/AppConstant.json
relaunch the application I am getting new path:
/var/mobile/Containers/Data/Application/3DAABAC3-0DF5-415B-82A5-72B204311904/Documents/MyFolder/AppConstant.json
NOTE: I create a sample project and I use this same code and it's working. But in existing project it's not working. I am using the same bundle id and profile only for both sample and project. Checked the file added reference, settings, version all are same.
Any idea?
The behavior that the container path changes periodically is normal.
These lines
let destFolderPath = URL(string:docURL)?.appendingPathComponent("MyFolder")
let fileName = "\(resourceFile).\(resourceExtension)"
guard let newDestPath = destFolderPath, let sourcePath = Bundle.main.path(forResource: resourceFile, ofType: ".\(resourceExtension)"), let fullDestPath = NSURL(fileURLWithPath: newDestPath.absoluteString).appendingPathComponent(fileName) else {
return stringURLPath
}
contain a lot of mistakes
URL(string is the wrong API for file paths, it's URL(fileURLWithPath).
The second parameter of path(forResource:ofType:) must not have a leading dot.
The API absoluteString is wrong as parameter of URL(fileURLWithPath
Not a real mistake but don't use NSURL in Swift.
It's highly recommended to use always the URL related API to concatenate paths and get the documents folder from FileManager. Further it's good practice to make the method throw the real error rather than returning a meaningless literal string. And NSSearchPathForDirectoriesInDomains is outdated and should not be used in Swift.
func copyFileFromBundleToDocumentDirectory(resourceFile: String, resourceExtension: String) throws -> URL
{
let sourceURL = Bundle.main.url(forResource: resourceFile, withExtension: resourceExtension)!
let fileManager = FileManager.default
let destFolderURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("MyFolder")
let fullDestURL = destFolderURL.appendingPathComponent(resourceFile).appendingPathExtension(resourceExtension)
if !fileManager.fileExists(atPath: destFolderURL.path) {
try fileManager.createDirectory(at: destFolderURL, withIntermediateDirectories: true, attributes: nil)
print("Created folder successfully in :::", destFolderURL.path)
try fileManager.copyItem(at: sourceURL, to: fullDestURL)
print("Saved file successfully in :::", fullDestURL.path)
} else {
print("Folder already exists!")
if fileManager.fileExists(atPath: fullDestURL.path) {
print("File exists in ::: \(fullDestURL.path)")
} else {
try fileManager.copyItem(at: sourceURL, to: fullDestURL)
print("Saved file successfully in :::", fullDestURL.path)
}
}
return fullDestURL
}
Edit 1:
Hi I created the new project and use the same code I posted in main, and it's working. But in the real project it not working.
Not sure what exactly going on in your project, try to debug it. It's part of development as well. :)
If you are in hurry to fix this issue in this weekend try to use the following code snippet.
// collect data from bundle
let constFileURL = Bundle.main.url(forResource: "AppConst", withExtension: "json")!
let data = try! Data(contentsOf: constFileURL)
// try to write data in document directory
do {
let constFileURL = try saveFileInDocumentDirectory(filePath: "MyFolder/AppConst.json", data: data)
// use your `constFileURL`
} catch (let error as FileOperationError) {
switch error {
case .fileAlreadyExists(let url):
let data = try! Data(contentsOf: url)
print(String(data: data, encoding: .utf8))
case .IOError(let error):
print("IO Error \(error)")
}
} catch {
print("Unknown Error \(error)")
}
// Helpers
enum FileOperationError: Error {
case fileAlreadyExists(url: URL)
case IOError(Error)
}
func saveFileInDocumentDirectory(filePath: String, data: Data) throws -> URL {
// final destination path
let destURLPath = fullURLPathOf(filePath, relativeTo: .documentDirectory)
// check for file's existance and throw error if found
guard FileManager.default.fileExists(atPath: destURLPath.path) == false else {
throw FileOperationError.fileAlreadyExists(url: destURLPath)
}
// Create Intermidiate Folders
let intermidiateDicPath = destURLPath.deletingLastPathComponent()
if FileManager.default.fileExists(atPath: intermidiateDicPath.path) == false {
do {
try FileManager.default.createDirectory(at: intermidiateDicPath, withIntermediateDirectories: true, attributes: nil)
} catch {
throw FileOperationError.IOError(error)
}
}
// File Writing
do {
try data.write(to: destURLPath, options: .atomic)
} catch {
throw FileOperationError.IOError(error)
}
return destURLPath
}
func fullURLPathOf(_ relativePath: String, relativeTo dic:FileManager.SearchPathDirectory ) -> URL {
return FileManager.default.urls(for: dic, in: .userDomainMask).first!.appendingPathComponent(relativePath)
}
Original Answer
Why don't you just return "MyFolder/\(fileName)" on successful file operation? If you need to access the path later you can always do that using FileManager APIs.
let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let constFilePath = docDir.appendingPathComponent("MyFolder/\(fileName)")
// Access const file data
do {
let fileData = try Data(contentsOf: constFilePath)
// Use you data for any further checking
} catch {
// Error in reading file data
print("Error in file data access : \(error)")
}
I'm trying to store images inside the app's document folder, so the user can retrieve them at any later time that they want to. This is my code to store them:
func store(_ image: UIImage) -> String {
let imageName = "\(Date().timeIntervalSince1970)"
let imagePath = "\(documentasPath)/\(imageName).png"
let imageData = UIImagePNGRepresentation(image)
fileManager.createFile(atPath: imagePath, contents: imageData, attributes: nil)
return imagePath
}
And this is my code to retrieve the image from the storage:
func retrieveImage(from path: String) -> UIImage? {
guard fileManager.fileExists(atPath: path) else {
return nil
}
return UIImage(contentsOfFile: path)
}
It seems to work fine, except when I rebuild the app from xcode. Then all of my stored images disappear (although all of the paths I stored that pointed to them are still present and correct).
Is this some behavior of the default file manager? And is there a way to avoid this from happening? I want the images to only be deleted either manually or when I uninstall the app.
Thanks
The problem is that you are storing an absolute path. You can't do that, because your app is sandboxed, which means (in part) that the URL of the Documents folder can change. Store just the document name, and each time you want to save to it or write from it, calculate the path to the Documents folder again and append the document name and use that result as your path.
Change to this
func store(_ image: UIImage) -> String {
let imageName = "\(Date().timeIntervalSince1970)"
let documentsUrl = NSURL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
var imagePath = documentsUrl.appendingPathComponent("\(imageName).png")
let imageData = UIImagePNGRepresentation(image)
fileManager.createFile(atPath: imagePath, contents: imageData, attributes: nil)
return imagePath
}
func retrieveImage(from path: String) -> UIImage? {
guard fileManager.fileExists(atPath: path) else {
return nil
}
return UIImage(contentsOfFile: path)
}
I've successfully downloaded the file but I am unable to save the file. Because I keep getting the error(s):
[SSZipArchive] Error: You don’t have permission to save the file “fileName” in the folder “Folder_Name”.
[SSZipArchive] Error: You don’t have permission to save the file “fileName” in the folder “__MACOSX”.
Any help would be appreciated!
Code
Unzip file function call
ZipManager.unzipFile(atPath: filePath, delegate: self)
ZipManager.swift
private static let documentsURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
static func unzipFile(atPath path: String, delegate: SSZipArchiveDelegate)
{
let destFolder = "/Folder_Name"
let destPath = documentsURL.appendingPathComponent(destFolder, isDirectory: true)
let destString = destPath.absoluteString
if ( !FileManager.default.fileExists(atPath: destString) )
{
try! FileManager.default.createDirectory(at: destPath, withIntermediateDirectories: true, attributes: nil)
}
SSZipArchive.unzipFile(atPath: path, toDestination: destString, delegate: delegate)
}
Thanks to this post I realized its because of this line:
let destString = destPath.absoluteString
I had to change it to:
let destString = documentsURL.relativePath
Which allowed me to greatly simplify my function:
static func unzipFile(atPath path: String) -> Bool
{
let destString = documentsURL.relativePath
let success: Void? = try? SSZipArchive.unzipFile(atPath: path, toDestination: documentsURL.relativePath, overwrite: true, password: nil)
if success == nil
{
return false
}
return true
}
If I attempt to remove a file using the FileManager class I get an error.
The function below returns true, which means that I need to set the delete privileges attributes. However I haven't found an example on how to do it.
func isDeletableFile(atPath path: String) -> Bool
Any help?
Code:
func fileManager(_ fileManager: FileManager, shouldRemoveItemAtPath path: String) -> Bool {
// print("Should remove invoked for path \(path)")
return true
}
func fileManager(_ fileManager: FileManager, shouldProceedAfterError error: Error, removingItemAt URL: URL) -> Bool {
//print("Should process")
return true
}
func deleteAllFiles(subPath : String) {
var url = Bundle.main.bundleURL
url = url.appendingPathComponent(subPath)
let fileManager = FileManager.default
fileManager.delegate = self
if let enumerator = fileManager.enumerator(at: url, includingPropertiesForKeys: nil) {
for file in enumerator {
let fileAsNSURL = file as! NSURL
print("")
print("Deleting: \(fileAsNSURL.absoluteString!)")
print("")
do {
// I would like to set the deletable permissions before checking this..
if (fileManager.isDeletableFile(atPath: fileAsNSURL.absoluteString!)){
try fileManager.removeItem(atPath: fileAsNSURL.absoluteString!)
}
else{
print("its not deletable")
}
}
catch let error {
print("file-delete-error:\n\(error) for path \(fileAsNSURL.absoluteString!)")
}
}
}
}
There is a common misunderstanding:
In the file system a you have to call path on the URL to get the path
fileManager.isDeletableFile(atPath: fileAsNSURL.path)
absoluteString returns the (percent escaped) string representation of the URL starting with the scheme (http://, file://)
For example you have an URL (don't use NSURL in Swift 3):
let url = URL(fileURLWithPath:"/Users/myUser/Application Support")
url.path returns "/Users/myUser/Application Support"
url.absoluteString returns "file:///Users/myUser/Application%20Support"
Edit 2:
I tried Zip from marmelroy (https://github.com/marmelroy/Zip) and it failed on the zip-file created witch ZipArchive too.
Then I created another zip-file with Zip and this one worked fine for unzipping.
There seems to be a problem with ZipArchive for me. I will use Zip instead and thats it for me ...
Thx for the answers !!
End edit 2.
I need to unzip certain files so I manually installed SSZipArchive.
Copied all 3 folders (SSZipArchive, minizip, aes) to project, added #import "ZipArchive.h" in my bridging.h and all builds nicely.
Edit:
Used Carthage as recommended, but same behavior.
End edit.
I use Xcode 8 / Swift 3
After a few tests with no unzipping any file, I created my own zip using SSZipArchive.
let file = "file.txt"
let text = "some text" //just a text
let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let path = dir?.appendingPathComponent(file)
//writing
do {
try text.write(to: path!, atomically: false, encoding: String.Encoding.utf8)
}
catch {
print("Failed writing")
}
Here I print() the Documentsdirectory: ["file.txt"]
let zipPath = tempZipPath() //tempZipPath is taken from the SSZipArchiveExample except it uses DocumentDirectory instead of Cache...
print("zipPath: \(zipPath)")
print zipPath: /var/mobile/Containers/Data/Application/EB439A61-07B9-4910-BF28-03E85C50B292/Documents/93428A1B-E5B6-434F-B049-632B7519B126.zip
let success = SSZipArchive.createZipFile(atPath: zipPath, withContentsOfDirectory: (dir?.absoluteString)!)
if success {
print("zipped")
} else {
print(" NOT zipped")
}
Again the Documentsdirectories entries: ["93428A1B-E5B6-434F-B049-632B7519B126.zip", "file.txt"]
// this is again taken from the original Example
guard let unzipPath = tempUnzipPath() else {
return
}
print("unzipPath: \(unzipPath)")
print unzipPath: /var/mobile/Containers/Data/Application/EB439A61-07B9-4910-BF28-03E85C50B292/Documents/E4FC7DE6-F21B-46D8-9953-DCBF86E2268E
Reading the entries of this new directory: []
let filePath = zipPath
var fileSize : UInt64 = 0
do {
let attr : NSDictionary? = try FileManager.default.attributesOfItem(atPath: filePath) as NSDictionary?
if let _attr = attr {
fileSize = _attr.fileSize();
print("fileSize: \(fileSize)")
}
} catch {
print("Error: \(error)")
}
this writes 22. I also tried to read the zip-file which works fine
From this part on I was desperate what to do
let url = URL(fileURLWithPath: zipPath)
print("url: \(url)")
print("url.path: \(url.path)")
print("url.absoluteString: \(url.absoluteString)")
print("url.absoluteURL: \(url.absoluteURL)")
print("url.absoluteURL.absoluteString: \(url.absoluteURL.absoluteString)")
The output of these lines look all good for me:
url:
file:///var/mobile/Containers/Data/Application/EB439A61-07B9-4910-BF28-03E85C50B292/Documents/93428A1B-E5B6-434F-B049-632B7519B126.zip
url.path:
/var/mobile/Containers/Data/Application/EB439A61-07B9-4910-BF28-03E85C50B292/Documents/93428A1B-E5B6-434F-B049-632B7519B126.zip
url.absoluteString:
file:///var/mobile/Containers/Data/Application/EB439A61-07B9-4910-BF28-03E85C50B292/Documents/93428A1B-E5B6-434F-B049-632B7519B126.zip
url.absoluteURL:
file:///var/mobile/Containers/Data/Application/EB439A61-07B9-4910-BF28-03E85C50B292/Documents/93428A1B-E5B6-434F-B049-632B7519B126.zip
url.absoluteURL.absoluteString:
file:///var/mobile/Containers/Data/Application/EB439A61-07B9-4910-BF28-03E85C50B292/Documents/93428A1B-E5B6-434F-B049-632B7519B126.zip
And then I tried them all (Strings of course. No url allowed)
var success2 = SSZipArchive.unzipFile(atPath: zipPath, toDestination: unzipPath)
if !success2 {
print("zipPath")
}
success2 = SSZipArchive.unzipFile(atPath: url.path, toDestination: unzipPath)
if !success2 {
print("url.path")
}
success2 = SSZipArchive.unzipFile(atPath: url.absoluteString, toDestination: unzipPath)
if !success2 {
print("url.absoluteString")
}
success2 = SSZipArchive.unzipFile(atPath: url.absoluteURL.path, toDestination: unzipPath)
if !success2 {
print("url.absoluteURL.path")
}
And it prints every line, so they all failed. At the end here comes the "long" way with unipFileAtPath. I tried this one too with zipPath, ...
All the same results.
do {
success2 = try SSZipArchive.unzipFileAtPath(url.path, toDestination: unzipPath, overwrite: true, password: nil, delegate: nil)
if !success2 {
return
}
} catch {
print("error \(error)")
print("error \(error.localizedDescription)")
}
error Error Domain=SSZipArchiveErrorDomain Code=-1 "failed to open zip
file" UserInfo={NSLocalizedDescription=failed to open zip file} error
failed to open zip file
Here the to func to get the path-strings. btw. I modified tempZipPath so returns an URL or an URL.path
but hey: I was desperate
func tempZipPath() -> String {
var path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
path += "/\(UUID().uuidString).zip"
return path
}
func tempUnzipPath() -> String? {
var path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
path += "/\(UUID().uuidString)"
let url = URL(fileURLWithPath: path)
do {
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
} catch {
return nil
}
return url.path
}
Any suggestions? Is there something to consider manually installing SSZipArchive?
The tip "use .path instead of .absoluteString" didn't work for me as u can see above.
a desperate tartsigam
I had the problem, too. If you use url.path instead of url.absolutString, it works.
Thanks to vadian for the hint!
Before zip don't extract file and return false.
After put this in Build Settings / Apple CLang Prepocessing / Preprocessor Macros Debug e Release works here
GCC_PREPROCESSOR_DEFINITIONS: HAVE_INTTYPES_H HAVE_PKCRYPT HAVE_STDINT_H HAVE_WZAES HAVE_ZLIB MZ_ZIP_NO_SIGNING $(inherited)
In my case, using ObjC and manual installation, I forgot to Add the following GCC_PREPROCESSOR_DEFINITIONS:
$(inherited)
HAVE_INTTYPES_H
HAVE_PKCRYPT
HAVE_STDINT_H
HAVE_WZAES
HAVE_ZLIB
After adding these GCC_PREPROCESSOR_DEFINITIONS it worked as expected.