I try to create an OutputStream to an app group folder, which is created as follows:
func createProjectDirectoryPath(path:String) -> String
{
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.xyz")
let logsPath = containerURL!.appendingPathComponent(path)
NSLog("12345- folder path: %#", logsPath.path)
do {
try FileManager.default.createDirectory(atPath: logsPath.path, withIntermediateDirectories: true, attributes: nil)
} catch let error as NSError {
NSLog("12345- Unable to create directory %#", error.debugDescription)
}
return logsPath.path
}
This function gives me a path like this
/private/var/mobile/Containers/Shared/AppGroup/40215F20-4713-4E23-87EF-1E21CCFB45DF/pcapFiles
This folder exists, because the line FileManager.default.fileExists(path) returns true. The next step is to append a generated filename to the path, which I am doing here
let urlToFile = URL(string: createProjectDirectoryPath(path: "pcapFiles").appending("/\(filename)"))
which gives me the correct new path
/private/var/mobile/Containers/Shared/AppGroup/40215F20-4713-4E23-87EF-1E21CCFB45DF/pcapFiles/39CC2DB4-A6D9-412E-BAAF-2FAA4AD70B22.pcap
If I call this line, ostream is always nil
let ostream = OutputStream(url: urlToFile!, append: false)
Do I miss something? The OutputStream should create the file on this path, but for unknown reason, it is not possible.
PS: AppGroup is enabled in Capabilities and in developers console.
Your createProjectDirectoryPath() function returns a file path,
therefore you must use URL(fileURLWithPath:) to convert that to an
URL. Alternatively, modify your function to return an URL instead:
func createProjectDirectoryPath(path:String) -> URL? {
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.xyz")
let logsURL = containerURL!.appendingPathComponent(path)
do {
try FileManager.default.createDirectory(at: logsURL, withIntermediateDirectories: true)
} catch let error as NSError {
NSLog("Unable to create directory %#", error.debugDescription)
return nil
}
return logsURL
}
In addition, you have to call open() on all Stream objects before
they can be used, this will also create the file if it did not exist
before:
guard let logsURL = createProjectDirectoryPath(path: "pcapFiles") else {
fatalError("Cannot create directory")
}
let urlToFile = logsURL.appendingPathComponent(filename)
guard let ostream = OutputStream(url: urlToFile, append: false) else {
fatalError("Cannot open file")
}
ostream.open()
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)")
}
let filename = getDocumentsDirectory().appendingPathComponent(upload.fileName)
print("deleting")
let fileNameToDelete = upload.fileName
var filePath = ""
// Fine documents directory on device
let dirs : [String] = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.allDomainsMask, true)
if dirs.count > 0 {
let dir = dirs[0] //documents directory
filePath = dir.appendingFormat("/" + fileNameToDelete)
print("Local path = \(filePath)")
} else {
print("Could not find local directory to store file")
return
}
print(filename)
print("deleting111111")
do {
let fileManager = FileManager.default
// Check if file exists
print("filePath")
print(filePath)
print("filePath")
if fileManager.fileExists(atPath: filePath) {
// Delete file
try fileManager.removeItem(atPath: filePath)
} else {
print("File does not exist")
}
}
catch let error as NSError {
print("An error took place: \(error)")
}}
This gets printed below. Why is delete not working? Why is above function throwing to me on file exists it does not exist
deleting
Local path = /var/mobile/Containers/Data/Application/C763B3ED-3371-47AB-8F61-4F086D01E430/Documents/profile-FFCEBEA9-2F8D-49E2-9A09-2BF87BD0B542--A9636AF4-350D-4D72-A4BD-E4F2B183F4BB.png
file:///var/mobile/Containers/Data/Application/C763B3ED-3371-47AB-8F61-4F086D01E430/Documents/profile-FFCEBEA9-2F8D-49E2-9A09-2BF87BD0B542--A9636AF4-350D-4D72-A4BD-E4F2B183F4BB.png
deleting111111
filePath
/var/mobile/Containers/Data/Application/C763B3ED-3371-47AB-8F61-4F086D01E430/Documents/profile-FFCEBEA9-2F8D-49E2-9A09-2BF87BD0B542--A9636AF4-350D-4D72-A4BD-E4F2B183F4BB.png
filePath
File does not exist
If you created the file by writing to a filename generated with
filename = getDocumentsDirectory().appendingPathComponent(nameOfImage+".PNG")
...then it is absolutely crucial that, when the time comes to delete the file, you generate the filename in exactly the same way. That is not what you are doing.
Indeed, in the code you have shown, you do generate a variable called filename with code that looks similar:
let filename = getDocumentsDirectory().appendingPathComponent(upload.fileName)
...but then you never use filename for anything! Thus you keep faking yourself out. You create filename, you print filename, but you do not use filename as the path to delete. You use some other variable, filePath, obtained in a different way.
put your remove code in DispatchQueue.main.async like this:
let filename = getDocumentsDirectory().appendingPathComponent(upload.fileName)
print("deleting")
let fileNameToDelete = upload.fileName
var filePath = ""
// Fine documents directory on device
let dirs : [String] = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.allDomainsMask, true)
if dirs.count > 0 {
let dir = dirs[0] //documents directory
filePath = dir.appendingFormat("/" + fileNameToDelete)
print("Local path = \(filePath)")
} else {
print("Could not find local directory to store file")
return
}
print(filename)
print("deleting111111")
do {
let fileManager = FileManager.default
// Check if file exists
print("filePath")
print(filePath)
print("filePath")
if fileManager.fileExists(atPath: filePath) {
// Delete file
DispatchQueue.main.async { // <- here
try fileManager.removeItem(atPath: filePath)
}
} else {
print("File does not exist")
}
}
catch let error as NSError {
print("An error took place: \(error)")
}}
I created this FileManager extension. With this extension, I want to create a file hierarchy like so:
Application Support
Favorites
Feed
Images
This is the code I have in FileManager extension which I would call in app delegate as soon as the app launches. Then I would use this code to always retrieve the path's of the folders.
Is this a good way to create this hierarchy and retrieve the paths when I need them? Is this good practice?
extension FileManager {
static func createOrFindApplicationDirectory() -> URL? {
let bundleID = Bundle.main.bundleIdentifier
// Find the application support directory in the home directory.
let appSupportDir = self.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard appSupportDir.count > 0 else {
return nil
}
// Append the bundle ID to the URL for the Application Support directory.
let dirPath = appSupportDir[0].appendingPathComponent(bundleID!)
// If the directory does not exist, this method creates it.
do {
try self.default.createDirectory(at: dirPath, withIntermediateDirectories: true, attributes: nil)
return dirPath
} catch let error {
print("Error creating Application Support directory with error: \(error)")
return nil
}
}
static func createOrFindFavoritesDirectory() -> URL? {
guard let appSupportDir = createOrFindApplicationDirectory() else {
return nil
}
let dirPath = appSupportDir.appendingPathComponent("Favorites")
// If the directory does not exist, this method creates it.
do {
try self.default.createDirectory(at: dirPath, withIntermediateDirectories: true, attributes: nil)
return dirPath
} catch let error {
print("Error creating Favorites directory with error: \(error)")
return nil
}
}
static func createOrFindFeedDirectory() -> URL? {
guard let appSupportDir = createOrFindFavoritesDirectory() else {
return nil
}
let dirPath = appSupportDir.appendingPathComponent("Feed")
// If the directory does not exist, this method creates it.
do {
try self.default.createDirectory(at: dirPath, withIntermediateDirectories: true, attributes: nil)
return dirPath
} catch let error {
print("Error creating Favorites directory with error: \(error)")
return nil
}
}
static func currentImagesDirectory() -> URL? {
guard let feedDir = createOrFindFeedDirectory() else {
return nil
}
let dirPath = feedDir.appendingPathComponent("Images")
// If the directory does not exist, this method creates it.
do {
try self.default.createDirectory(at: dirPath, withIntermediateDirectories: true, attributes: nil)
return dirPath
} catch let error {
print("Error creating Images directory with error: \(error)")
return nil
}
}
}
It looks pretty good, but you could combine a bit of the code and have better error checking:
extension FileManager {
static func createOrFindApplicationDirectory() -> URL? {
guard let bundleID = Bundle.main.bundleIdentifier else {
return nil
}
// Find the application support directory in the home directory.
let appSupportDirArray = self.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard let appSupportDir = appSupportDirArray.first else {
return nil
}
// Append the bundle ID to the URL for the Application Support directory.
let dirPath = appSupportDir.appendingPathComponent(bundleID)
// If the directory does not exist, this method creates it.
do {
try self.default.createDirectory(at: dirPath, withIntermediateDirectories: true, attributes: nil)
return dirPath
} catch let error {
print("Error creating Application Support directory with error: \(error)")
return nil
}
}
static func createOrFindDirectory(named name: String) -> URL? {
guard let appSupportDir = createOrFindApplicationDirectory() else {
return nil
}
let dirPath = appSupportDir.appendingPathComponent(name)
// If the directory does not exist, this method creates it.
do {
try self.default.createDirectory(at: dirPath, withIntermediateDirectories: true, attributes: nil)
return dirPath
} catch let error {
print("Error creating \(name) directory with error: \(error)")
return nil
}
}
static func currentImagesDirectory() -> URL? {
guard let feedDir = createOrFindDirectory(named: "Feed") else {
return nil
}
let dirPath = feedDir.appendingPathComponent("Images")
// If the directory does not exist, this method creates it.
do {
try self.default.createDirectory(at: dirPath, withIntermediateDirectories: true, attributes: nil)
return dirPath
} catch let error {
print("Error creating Images directory with error: \(error)")
return nil
}
}
}
I have this function to save an image inside the tmp folder
private func saveImageToTempFolder(image: UIImage, withName name: String) {
if let data = UIImageJPEGRepresentation(image, 1) {
let tempDirectoryURL = NSURL.fileURLWithPath(NSTemporaryDirectory(), isDirectory: true)
let targetURL = tempDirectoryURL.URLByAppendingPathComponent("\(name).jpg").absoluteString
print("target: \(targetURL)")
data.writeToFile(targetURL, atomically: true)
}
}
But when I open the temp folder of my app, it is empty. What am I doing wrong to save the image inside the temp folder?
absoluteString is not the correct method to get a file path of
an NSURL, use path instead:
let targetPath = tempDirectoryURL.URLByAppendingPathComponent("\(name).jpg").path!
data.writeToFile(targetPath, atomically: true)
Or better, work with URLs only:
let targetURL = tempDirectoryURL.URLByAppendingPathComponent("\(name).jpg")
data.writeToURL(targetURL, atomically: true)
Even better, use writeToURL(url: options) throws
and check for success or failure:
do {
try data.writeToURL(targetURL, options: [])
} catch let error as NSError {
print("Could not write file", error.localizedDescription)
}
Swift 3/4 update:
let targetURL = tempDirectoryURL.appendingPathComponent("\(name).jpg")
do {
try data.write(to: targetURL)
} catch {
print("Could not write file", error.localizedDescription)
}
A solution is to try to write directly to the complete FileManager() file path like so :
let filePath = FileManager.default.temporaryDirectory.appendingPathComponent("\(id).mp4")
//You can choose a specific directory with FileManager.default.urls(for:in:)
before doing
DispatchQueue.main.async {
do {
try data?.write(to: filePath)
//Write is a success
} catch {
print(error.localizedDescription)
//Error while trying to write
}
}
I have this function to save an image inside the tmp folder
private func saveImageToTempFolder(image: UIImage, withName name: String) {
if let data = UIImageJPEGRepresentation(image, 1) {
let tempDirectoryURL = NSURL.fileURLWithPath(NSTemporaryDirectory(), isDirectory: true)
let targetURL = tempDirectoryURL.URLByAppendingPathComponent("\(name).jpg").absoluteString
print("target: \(targetURL)")
data.writeToFile(targetURL, atomically: true)
}
}
But when I open the temp folder of my app, it is empty. What am I doing wrong to save the image inside the temp folder?
absoluteString is not the correct method to get a file path of
an NSURL, use path instead:
let targetPath = tempDirectoryURL.URLByAppendingPathComponent("\(name).jpg").path!
data.writeToFile(targetPath, atomically: true)
Or better, work with URLs only:
let targetURL = tempDirectoryURL.URLByAppendingPathComponent("\(name).jpg")
data.writeToURL(targetURL, atomically: true)
Even better, use writeToURL(url: options) throws
and check for success or failure:
do {
try data.writeToURL(targetURL, options: [])
} catch let error as NSError {
print("Could not write file", error.localizedDescription)
}
Swift 3/4 update:
let targetURL = tempDirectoryURL.appendingPathComponent("\(name).jpg")
do {
try data.write(to: targetURL)
} catch {
print("Could not write file", error.localizedDescription)
}
A solution is to try to write directly to the complete FileManager() file path like so :
let filePath = FileManager.default.temporaryDirectory.appendingPathComponent("\(id).mp4")
//You can choose a specific directory with FileManager.default.urls(for:in:)
before doing
DispatchQueue.main.async {
do {
try data?.write(to: filePath)
//Write is a success
} catch {
print(error.localizedDescription)
//Error while trying to write
}
}