Swift how to create complex folder structure in temp folder - ios

I would like to create folder structure in the temp folder of the application.
Structure of the folders should be:
iPhone
Intensity
Positive
Negative
Elipses
Positive
Negative
Composite
Positive
Negative
I am using FileManager to create directories.
class func createBaseDirectory() {
let filemgr = FileManager.default
let dirPaths = filemgr.urls(for: .documentDirectory, in: .userDomainMask)
let docsURL = dirPaths[0]
let newDir = docsURL.appendingPathComponent("iPhone").path
do {
try filemgr.createDirectory(atPath: newDir, withIntermediateDirectories: true, attributes: nil)
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
}
But this way is not efficient.Because i should duplicate quite a lot of code snippets. Do Swift language have some more convenient ways how to create folder structure ?

I created the following in a Swift playground (same as Martin R suggested), seems to work fine:
import Foundation
let fm = FileManager.default
let baseUrl = fm.urls(for: .documentDirectory, in: .userDomainMask).first!
let subPaths = [
"iPhone/Intensity/Positive",
"iPhone/Intensity/Negative",
"iPhone/Elipses/Positive",
"iPhone/Elipses/Negative",
"iPhone/Composite/Negative",
"iPhone/Composite/Negative",
]
subPaths.forEach { subPath in
let url = baseUrl.appendingPathComponent(subPath)
do {
try fm.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
print("path created: \(url)")
} catch let error {
print("error: \(error)")
}
}
You could create an extension on NSFileManager using this code for reusability.

Related

When should we use FileManager.default.urls as opposed to NSSearchPathForDirectoriesInDomains? [duplicate]

This question already has an answer here:
UIImage(contentsOfFile:) returning nil despite file existing in caches directory [duplicate]
(1 answer)
Closed 1 year ago.
Currently, if I want to create a directory hierarchy in Document directory, I would perform the following
Using NSSearchPathForDirectoriesInDomains (Works fine)
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths[0]
let documentUrl1 = URL(string: documentsDirectory)!
//
// /Users/yccheok/Library/Developer/...
//
print("documentUrl1 -> \(documentUrl1)")
let dataPath = documentUrl1.appendingPathComponent("SubFolder1").appendingPathComponent("SubFolder2")
print("dataPath.absoluteString -> \(dataPath.absoluteString)")
if !FileManager.default.fileExists(atPath: dataPath.absoluteString) {
do {
try FileManager.default.createDirectory(atPath: dataPath.absoluteString, withIntermediateDirectories: true, attributes: nil)
print("Folder creation done!")
} catch {
print(error.localizedDescription)
}
}
But, if I use the following
Using FileManager.default.urls (Not working)
let documentUrl0 = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
//
// file:///Users/yccheok/Library/Developer/...
//
print("documentUrl0 -> \(documentUrl0)")
let dataPath = documentUrl0.appendingPathComponent("SubFolder1").appendingPathComponent("SubFolder2")
print("dataPath.absoluteString -> \(dataPath.absoluteString)")
if !FileManager.default.fileExists(atPath: dataPath.absoluteString) {
do {
try FileManager.default.createDirectory(atPath: dataPath.absoluteString, withIntermediateDirectories: true, attributes: nil)
print("Folder creation done!")
} catch {
print(error.localizedDescription)
}
}
The following error will be printed
You can’t save the file “SubFolder2” because the volume is read only.
I was wondering, under what use case, that FileManager.default.urls will be useful? Thanks.
URL(string and absoluteString are the wrong APIs to work with file system paths.
Your code accidentally works because URL(string applied to a path creates a path rather than a valid URL, and absoluteString applied to a path returns the path because there is no scheme (file://).
However you are strongly discouraged from doing that. A file system URL must be created with URL(fileURLWithPath and you can get the path with the path property.
You are encouraged to use the FileManager API because it provides a better error handling and it provides also the URL related API which is preferred over the string path API.
This is the correct FileManager way to create the directory if it doesn't exist
let fm = FileManager.default
do {
let documentUrl = try fm.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
//
// file:///Users/yccheok/Library/Developer/...
//
print("documentUrl -> \(documentUrl)")
let dataURL = documentUrl.appendingPathComponent("SubFolder1").appendingPathComponent("SubFolder2")
print("dataPath.path -> \(dataURL.path)")
if !fm.fileExists(atPath: dataURL.path) {
try fm.createDirectory(at: dataURL, withIntermediateDirectories: true, attributes: nil)
print("Folder creation done!")
}
} catch { print(error) }

Failure when trying to read saved data from Documents directory after relaunch

I've read some answers for similar questions and every time the problem was in saving absolute path instead of saving just file name and recreating url every time. I've tried to follow examples of code that I saw in these answers, but result was the same : "Could not read from file because there is no such file or directory". I messed up somewhere for sure, and some help would be appreciated
Writing:
let dir = FileManager.default
.urls(for: .documentDirectory, in: .userDomainMask)[0]
let accountDir = dir.appendingPathComponent("\(self.getHexString(str: self.account))", isDirectory: true)
let imageUrl = accountDir.appendingPathComponent("\(self.path.rawValue)")
if let imageData = UIImagePNGRepresentation(image) {
FileManager().createFile(atPath: imageUrl.path, contents: imageData, attributes: nil)
}
Reading:
let dir = FileManager.default
.urls(for: .documentDirectory, in: .userDomainMask)[0]
let accountDir = dir.appendingPathComponent("\(self.getHexString(str: self.account))", isDirectory: true)
let imageUrl = accountDir.appendingPathComponent("\(self.path.rawValue)")
let imageData = try Data(contentsOf: imageUrl)
There can be two reasons:
It is not creating the path properly.
Try changing image.path to image.absoluteString
I think it is just creating the file but not writing anything in that
Try the below code after createfile:
let file = try FileHandle(forUpdating: imageUrl)
file.seekToEndOfFile()
file.write(data: imageData)
file.closeFile()
I thought that directory where I wanted to store my images is created automatically when I append it to path and flag isDirectory: true, but it doesn't. So the correct code is :
let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let accountDir = dir.appendingPathComponent("\(self.getHexString(str: self.account))", isDirectory: true)
let isDirectoryExists = FileManager.default.fileExists(atPath: accountDir.path)
if !isDirectoryExists {
do {
try FileManager.default.createDirectory(atPath: accountDir.path, withIntermediateDirectories: false, attributes: nil)
} catch(let error) {
print(error)
}
}
let imageUrl = accountDir.appendingPathComponent("\(self.path.rawValue)")
if let imageData = UIImagePNGRepresentation(image) {
FileManager().createFile(atPath: imageUrl.path, contents: imageData, attributes: nil)
}

Remove all files from within documentDirectory in Swift

I am making an audio app, and the user can download files locally stored to the documentDirectory using FileManager.
Next, I'd like to allow the user to delete all files using a button. In the documentation, there is a method to remove items.
Here's my code:
#IBAction func deleteDirectoryButton(_ sender: Any) {
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
do {
try FileManager.default.removeItem(at: documentsUrl, includingPropertiesForKeys: nil, options: [])
} catch let error {
print(error)
}
}
Unfortunately, this won't build with an error Ambiguous reference to member 'removeItem(atPath:)'.
Is there a better approach to access the documentDirectory and remove all files from the directory in one swoop?
First of all the error occurs because the signature of the API is wrong. It's just removeItem(at:) without the other parameters.
A second issue is that you are going to delete the Documents directory itself rather than the files in the directory which you are discouraged from doing that.
You have to get the contents of the directory and add a check for example to delete only MP3 files. A better solution would be to use a subfolder.
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
do {
let fileURLs = try FileManager.default.contentsOfDirectory(at: documentsUrl,
includingPropertiesForKeys: nil,
options: .skipsHiddenFiles)
for fileURL in fileURLs where fileURL.pathExtension == "mp3" {
try FileManager.default.removeItem(at: fileURL)
}
} catch { print(error) }
Side note: It is highly recommended to use always the URL related API of FileManager.
Try this
func clearAllFiles() {
let fileManager = FileManager.default
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
print("Directory: \(paths)")
do {
let fileName = try fileManager.contentsOfDirectory(atPath: paths)
for file in fileName {
// For each file in the directory, create full path and delete the file
let filePath = URL(fileURLWithPath: paths).appendingPathComponent(file).absoluteURL
try fileManager.removeItem(at: filePath)
}
} catch let error {
print(error)
}
}
Just use code as Follow
to save AudioFile in Document Directory as
func getDocumentsDirectory() -> URL
{
//Get Basic URL
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
/// Enter a Directory Name in which files will be saved
let dataPath1 = documentsDirectory.appendingPathComponent("folder_name_enter")
let dataPath = dataPath1.appendingPathComponent("folder inside directory if required (name)")
//Handler
do
{
try FileManager.default.createDirectory(atPath: dataPath.path, withIntermediateDirectories: true, attributes: nil)
}
catch let error as NSError
{
print("Error creating directory: \(error.localizedDescription)")
}
return dataPath
}
Delete
func clearAllFilesFromTempDirectory()
{
let fileManager = FileManager.default
let dirPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let tempDirPath = dirPath.appending("/folder_name/\(inside_directoryName)")
do {
let folderPath = tempDirPath
let paths = try fileManager.contentsOfDirectory(atPath: tempDirPath)
for path in paths
{
try fileManager.removeItem(atPath: "\(folderPath)/\(path)")
}
}
catch let error as NSError
{
print(error.localizedDescription)
}
}
Saving Method
getDocumentsDirectory().appendingPathComponent("\(audioName).wav")
Deletion Method
/// Just call
clearAllFilesFromTempDirectory
This my extension for remove all files and caches from directory.
// MARK: - FileManager extensions
extension FileManager {
/// Remove all files and caches from directory.
public static func removeAllFilesDirectory() {
let fileManager = FileManager()
let mainPaths = [
FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).map(\.path)[0],
FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).map(\.path)[0]
]
mainPaths.forEach { mainPath in
do {
let content = try fileManager.contentsOfDirectory(atPath: mainPath)
content.forEach { file in
do {
try fileManager.removeItem(atPath: URL(fileURLWithPath: mainPath).appendingPathComponent(file).path)
} catch {
// Crashlytics.crashlytics().record(error: error)
}
}
} catch {
// Crashlytics.crashlytics().record(error: error)
}
}
}
}
Swift 5
Delete the whole folder:
If you'd like to delete a whole folder you can simply do this:
func deleteFolder(_ folderName: String, completion: () -> Void) {
let fileManager = FileManager.default
let directory = fileManager.cachesDirectory().appendingPathComponent(folderName)
_ = try? fileManager.removeItem(at: directory)
completion()
}
Delete certain files based on their name:
This will loop through all the files and remove all that contain the
func removeFiles(containing: String, completion: () -> Void) {
let fileManager = FileManager.default
let directory = fileManager.cachesDirectory()
if let fileNames = try? fileManager.contentsOfDirectory(atPath: directory.path) {
for file in fileNames {
if file.contains(containing) {
let filePath = URL(fileURLWithPath: directory.path).appendingPathComponent(file).absoluteURL
_ = try? fileManager.removeItem(at: filePath)
}
}
}
completion()
}

Swift 3 - Copy Folder w/ contents from Main Bundle to Documents Directory

I have folders with files inside them in my main bundle, and I want to copy/cut them to the Documents Directory at first launch of the application to access them from there. I've seen examples but they're all in Obj-C and I'm using Swift 3. How can I do this?
I managed to do it using 2 functions:
func copyFolders() {
let filemgr = FileManager.default
filemgr.delegate = self
let dirPaths = filemgr.urls(for: .documentDirectory, in: .userDomainMask)
let docsURL = dirPaths[0]
let folderPath = Bundle.main.resourceURL!.appendingPathComponent("Test").path
let docsFolder = docsURL.appendingPathComponent("Test").path
copyFiles(pathFromBundle: folderPath, pathDestDocs: docsFolder)
}
func copyFiles(pathFromBundle : String, pathDestDocs: String) {
let fileManagerIs = FileManager.default
fileManagerIs.delegate = self
do {
let filelist = try fileManagerIs.contentsOfDirectory(atPath: pathFromBundle)
try? fileManagerIs.copyItem(atPath: pathFromBundle, toPath: pathDestDocs)
for filename in filelist {
try? fileManagerIs.copyItem(atPath: "\(pathFromBundle)/\(filename)", toPath: "\(pathDestDocs)/\(filename)")
}
} catch {
print("\nError\n")
}
}

Create Directory in Swift 3.0

I am a new student in 9th grade learning swift, creating a school project .
I am trying to create a directory where I want to save a scanned file into pdf format.
While creating directory I am getting error below.
Error 1:
Cannot use instance member 'filemgr' within property initializer; property initializers run before 'self' is available.
Error 2:
Expected declaration
Code:
let filemgr = FileManager.default
let dirPaths = filemgr.urls(for: .documentDirectory, in: .userDomainMask)
let docsURL = dirPaths[0]
let newDir = docsURL.appendingPathComponent("data").path
do{
try filemgr.createDirectory(atPath: newDir,withIntermediateDirectories: true, attributes: nil)
} catch {
print("Error: \(error.localizedDescription)")
}
Please assist me in resolving this issue.
Thanks.
Please use this code:
Swift 5.0,
Swift 4.0 And
Swift 3.0
let DocumentDirectory = NSURL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let DirPath = DocumentDirectory.appendingPathComponent("FOLDER_NAME")
do
{
try FileManager.default.createDirectory(atPath: DirPath!.path, withIntermediateDirectories: true, attributes: nil)
}
catch let error as NSError
{
print("Unable to create directory \(error.debugDescription)")
}
print("Dir Path = \(DirPath!)")
For Swift 4.0
Please use this
let fileManager = FileManager.default
if let tDocumentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
let filePath = tDocumentDirectory.appendingPathComponent("\(FOLDER_NAME)")
if !fileManager.fileExists(atPath: filePath.path) {
do {
try fileManager.createDirectory(atPath: filePath.path, withIntermediateDirectories: true, attributes: nil)
} catch {
NSLog("Couldn't create document directory")
}
}
NSLog("Document directory is \(filePath)")
}
For Swift 4.0, I created the following extension off of URL that allows for the creation of a folder off of the documents directory within the application.
import Foundation
extension URL {
static func createFolder(folderName: String) -> URL? {
let fileManager = FileManager.default
// Get document directory for device, this should succeed
if let documentDirectory = fileManager.urls(for: .documentDirectory,
in: .userDomainMask).first {
// Construct a URL with desired folder name
let folderURL = documentDirectory.appendingPathComponent(folderName)
// If folder URL does not exist, create it
if !fileManager.fileExists(atPath: folderURL.path) {
do {
// Attempt to create folder
try fileManager.createDirectory(atPath: folderURL.path,
withIntermediateDirectories: true,
attributes: nil)
} catch {
// Creation failed. Print error & return nil
print(error.localizedDescription)
return nil
}
}
// Folder either exists, or was created. Return URL
return folderURL
}
// Will only be called if document directory not found
return nil
}
}
If the desired folder does not exist, it will create it. Then, assuming the folder exists, it returns the URL back to the user. Otherwise, if it fails, then nil is returned.
For example, to create the folder "MyStuff", you would call it like this:
let myStuffURL = URL.createFolder(folderName: "MyStuff")
This would return:
file:///var/mobile/Containers/Data/Application/4DE0A1C0-8629-47C9-87D7-C2B4F3A16D24/Documents/MyStuff/
You can also create nested folders with the following:
let myStuffHereURL = URL.createFolder(folderName: "My/Stuff/Here")
Which gives you:
file:///var/mobile/Containers/Data/Application/4DE0A1C0-8629-47C9-87D7-C2B4F3A16D24/Documents/My/Stuff/Here/
You are getting this because you are assigning value to newDir instance at wrong place.
I wrote your code in viewDidLoad and it works perfectly.
For Swift 5 and up Version
let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let directoryURL = documentDirectoryURL.appendingPathComponent("FolderName", isDirectory: true)
if FileManager.default.fileExists(atPath: directoryURL.path) {
print(directoryURL.path)
} else {
do {
try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
print(directoryURL.path)
} catch {
print(error.localizedDescription)
}

Resources