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
}
}
I am using Swift 4, Xcode 9, and development target iOS 11.0.
I am trying to append a custom folder (MyFolder) to the path variable.
let outputFilePath = (NSTemporaryDirectory() as NSString).appending("MyFolder").appendingPathComponent((outputFileName as NSString).appendingPathExtension("mov")!)
But builder is giving error message:
appendingPathComponent' is unavailable: Use appendingPathComponent on URL instead.
I know, I am doing some silly mistake. Can you kindly help me in this?
Use this line
URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("MyFolder").appendingPathComponent(outputFileName).appendingPathExtension("mov")
instead of
(NSTemporaryDirectory() as NSString).appending("MyFolder").appendingPathComponent((outputFileName as NSString).appendingPathExtension("mov")!)
This will return you a url and use url.Path to get its path in string .
Hope this helps you.
Check below code for reference in document Directory
class func getDocumentsDirectory() -> URL {
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let dataPath = documentsDirectory.appendingPathComponent("FolderName")
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
}
For Appending Files in Folder You can use this
//name for file to be added
let uuid = UUID().uuidString
// storing a Audio File in Directory
let audioFilename = getDocumentsDirectory().appendingPathComponent("\(uuid).m4a")
To get Names of Files Available in the respected Folder created
//This function returns a Array with file names Available
class func getListOfRecordingsAvailable() -> [String] {
var fileNameArray = [String]()
let documentDirectoryPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let myFilesPath = documentDirectoryPath.appending("/FolderName")
let files = FileManager.default.enumerator(atPath: myFilesPath)
while let file = files?.nextObject() {
//myfilesPath - Path
//file - fileName
fileNameArray.append(file as! String)
}
print(fileNameArray)
return fileNameArray
}
I'm using FMdatabase.
I want to use a prepared database.
I think I should move database file from bundle to documents folder.
my code:
import FMDB
class DatabaseManager {
private let dbFileName = "kashanmapDB_upgrade_3-4.db"
private var database:FMDatabase!
let TABLE_LOCATION_FA = "LocationInfoFa";
let TABLE_LOCATION_EN = "LocationInfoEn";
let TABLE_GREAT_PEOPLE_FA = "GreatPeopleInfoFa";
let TABLE_GREAT_PEOPLE_EN = "GreatPeopleInfoEn";
let TABLE_TAGS = "Tags";
let TABLE_RELATION_TAG_LOCATION = "RelationTagLocation";
let TABLE_NECESSARY_INFORMATION = "NecessaryInformation";
let TABLE_SLIDER_FA = "SliderFa";
let TABLE_SLIDER_EN = "SliderEn";
let DATABASE_VERSION = 4;
static var LANGUAGE = 1 ; //1:Fa , 2:En
var utilities = Utilities()
init() {
openDatabase()
if(utilities.getData(key: "lang") == "2")
{
DatabaseManager.LANGUAGE = 2
}
}
func copyDatabaseIfNeeded() {
// Move database file from bundle to documents folder
let fileManager = FileManager.default
let documentsUrl = fileManager.urls(for: .documentDirectory,
in: .userDomainMask)
guard documentsUrl.count != 0 else {
return // Could not find documents URL
}
//let finalDatabaseURL = documentsUrl.first!.appendingPathComponent("kashanmapDB_upgrade_3-4.db")
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let finalDatabaseURL = URL(fileURLWithPath: paths).appendingPathComponent(dbFileName)
if !( (try? finalDatabaseURL.checkResourceIsReachable()) ?? false) {
print("DB does not exist in documents folder")
let documentsURL = Bundle.main.resourceURL?.appendingPathComponent("kashanmapDB_upgrade_3-4.db")
do {
try fileManager.copyItem(atPath: (documentsURL?.path)!, toPath: finalDatabaseURL.path)
} catch let error as NSError {
print("Couldn't copy file to final location! Error:\(error.description)")
}
} else {
print("Database file found at path: \(finalDatabaseURL.path)")
}
}
func openDatabase() {
self.copyDatabaseIfNeeded()
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let dbPath = URL(fileURLWithPath: paths).appendingPathComponent(dbFileName)
let str_path = Bundle.main.resourceURL!.appendingPathComponent(dbFileName).path
let database = FMDatabase(path: str_path)
/* Open database read-only. */
if (!(database.open(withFlags: 2))) {
print("Could not open database at \(dbPath).")
} else {
print("opened database")
self.database = database;
}
}
at the first time (when application installed ) I got this error message:
DB does not exist in documents folder
and I always got this message:
Error Domain=FMDatabase Code=8 "attempt to write a readonly database" UserInfo={NSLocalizedDescription=attempt to write a readonly database}
Hmmm... Looking at your code:
func openDatabase() {
self.copyDatabaseIfNeeded()
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let dbPath = URL(fileURLWithPath: paths).appendingPathComponent(dbFileName)
let str_path = Bundle.main.resourceURL!.appendingPathComponent(dbFileName).path
let database = FMDatabase(path: str_path)
/* Open database read-only. */
if (!(database.open(withFlags: 2))) {
print("Could not open database at \(dbPath).")
} else {
print("opened database")
self.database = database;
}
}
It appears you are setting dbPath equal to the path to the file in documents folder, but then you're trying to open database which is at str_path which is equal to the Bundle path.
Maybe just change:
let database = FMDatabase(path: str_path)
to:
let database = FMDatabase(path: dbPath)
Having copied the database, you are trying to open the database from the bundle. Open the one in the Documents folder. If you define the bundle URL inside the if statement that handles the missing database (like shown below), there's no possibility of accidentally grabbing the wrong database.
As an aside, Apple is getting more particular about what gets stored in Documents folder (see iOS Storage Best Practices). You might want to use Application Support folder instead.
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("test.sqlite")
let fileExists = (try? fileURL.checkResourceIsReachable()) ?? false
if !fileExists {
let bundleURL = Bundle.main.url(forResource: "test", withExtension: "sqlite")!
try FileManager.default.copyItem(at: bundleURL, to: fileURL)
}
let db = FMDatabase(url: fileURL)
guard db.open() else {
print("unable to open")
return
}
Alternatively, it’s often preferred to adopt the “ask for forgiveness rather than permission” strategy. I.e., rather than checking for existence before you open the database every time you open it, just try to open it and handle the file-not-found error scenario (which will happen just once, the first time you try opening it). Bottom line, just try opening the database, and if it fails, copy it from the bundle and try again.
The trick is to supply the SQLITE_OPEN_READWRITE parameter (made available if you import SQLite3) but not the SQLITE_OPEN_CREATE so that it won’t create a blank database if it’s not found that first time you try opening it:
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("test.sqlite")
let db = FMDatabase(url: fileURL)
if !db.open(withFlags: SQLITE_OPEN_READWRITE) {
let bundleURL = Bundle.main.url(forResource: "test", withExtension: "sqlite")!
try FileManager.default.copyItem(at: bundleURL, to: fileURL)
guard db.open(withFlags: SQLITE_OPEN_READWRITE) else {
print("unable to open")
return
}
}
I have an app that allows users to record multiple videos and then post them to view later from a database. After they post the video, I want the app to delete the video from the documents directory to save phone storage. I am trying this, but when i check my phone's storage nothing is updated. Here is what I am doing
This is where i write the video to:
func videoFileLocation() -> String {
let uniqueID = NSUUID().uuidString
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let outputPath = "\(documentsPath)/\(uniqueID).mov"
return outputPath
}
This is how I remove them:
func clearDirectory() {
do {
data = try context.fetch(VideoPath.fetchRequest())
for each in data {
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let path = "\(documentsPath)/\(each.fileLocations!)"
let url = URL(fileURLWithPath: path)
do {
try FileManager.default.removeItem(at: url)
print("Successfully Cleared")
} catch {
print("There was a problem removing the file")
}
}
The successfully cleared method gets printed out, but I see no change in my phone's storage. What am i doing wrong?
Replace
let path = "\(documentsPath)/\(each.fileLocations!)"
with below and try.
var path: String = nil
if let fileLocationPath = each.fileLocations as? String {
path = documentsPath.appendingPathComponent(fileLocationPath)
}
How to check if a file exists in the Documents directory in Swift?
I am using [ .writeFilePath ] method to save an image into the Documents directory and I want to load it every time the app is launched. But I have a default image if there is no saved image.
But I just cant get my head around how to use the [ func fileExistsAtPath(_:) ] function. Could someone give an example of using the function with a path argument passed into it.
I believe I don't need to paste any code in there as this is a generic question. Any help will be much appreciated.
Swift 4.x version
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
if let pathComponent = url.appendingPathComponent("nameOfFileHere") {
let filePath = pathComponent.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
print("FILE AVAILABLE")
} else {
print("FILE NOT AVAILABLE")
}
} else {
print("FILE PATH NOT AVAILABLE")
}
Swift 3.x version
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = URL(fileURLWithPath: path)
let filePath = url.appendingPathComponent("nameOfFileHere").path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
print("FILE AVAILABLE")
} else {
print("FILE NOT AVAILABLE")
}
Swift 2.x version, need to use URLByAppendingPathComponent
let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
let filePath = url.URLByAppendingPathComponent("nameOfFileHere").path!
let fileManager = NSFileManager.defaultManager()
if fileManager.fileExistsAtPath(filePath) {
print("FILE AVAILABLE")
} else {
print("FILE NOT AVAILABLE")
}
Check the below code:
Swift 1.2
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
let getImagePath = paths.stringByAppendingPathComponent("SavedFile.jpg")
let checkValidation = NSFileManager.defaultManager()
if (checkValidation.fileExistsAtPath(getImagePath))
{
println("FILE AVAILABLE");
}
else
{
println("FILE NOT AVAILABLE");
}
Swift 2.0
let paths = NSURL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0])
let getImagePath = paths.URLByAppendingPathComponent("SavedFile.jpg")
let checkValidation = NSFileManager.defaultManager()
if (checkValidation.fileExistsAtPath("\(getImagePath)"))
{
print("FILE AVAILABLE");
}
else
{
print("FILE NOT AVAILABLE");
}
Nowadays (2016) Apple recommends more and more to use the URL related API of NSURL, NSFileManager etc.
To get the documents directory in iOS and Swift 2 use
let documentDirectoryURL = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory,
inDomain: .UserDomainMask,
appropriateForURL: nil,
create: true)
The try! is safe in this case because this standard directory is guaranteed to exist.
Then append the appropriate path component for example an sqlite file
let databaseURL = documentDirectoryURL.URLByAppendingPathComponent("MyDataBase.sqlite")
Now check if the file exists with checkResourceIsReachableAndReturnError of NSURL.
let fileExists = databaseURL.checkResourceIsReachableAndReturnError(nil)
If you need the error pass the NSError pointer to the parameter.
var error : NSError?
let fileExists = databaseURL.checkResourceIsReachableAndReturnError(&error)
if !fileExists { print(error) }
Swift 3+:
let documentDirectoryURL = try! FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
let databaseURL = documentDirectoryURL.appendingPathComponent("MyDataBase.sqlite")
checkResourceIsReachable is marked as can throw
do {
let fileExists = try databaseURL.checkResourceIsReachable()
// handle the boolean result
} catch let error as NSError {
print(error)
}
To consider only the boolean return value and ignore the error use the nil-coalescing operator
let fileExists = (try? databaseURL.checkResourceIsReachable()) ?? false
Swift 4.2
extension URL {
func checkFileExist() -> Bool {
let path = self.path
if (FileManager.default.fileExists(atPath: path)) {
print("FILE AVAILABLE")
return true
}else {
print("FILE NOT AVAILABLE")
return false;
}
}
}
Using: -
if fileUrl.checkFileExist()
{
// Do Something
}
It's pretty user friendly. Just work with NSFileManager's defaultManager singleton and then use the fileExistsAtPath() method, which simply takes a string as an argument, and returns a Bool, allowing it to be placed directly in the if statement.
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentDirectory = paths[0] as! String
let myFilePath = documentDirectory.stringByAppendingPathComponent("nameOfMyFile")
let manager = NSFileManager.defaultManager()
if (manager.fileExistsAtPath(myFilePath)) {
// it's here!!
}
Note that the downcast to String isn't necessary in Swift 2.
works at Swift 5
do {
let documentDirectory = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let fileUrl = documentDirectory.appendingPathComponent("userInfo").appendingPathExtension("sqlite3")
if FileManager.default.fileExists(atPath: fileUrl.path) {
print("FILE AVAILABLE")
} else {
print("FILE NOT AVAILABLE")
}
} catch {
print(error)
}
where "userInfo" - file's name, and "sqlite3" - file's extension
An alternative/recommended Code Pattern in Swift 3 would be:
Use URL instead of FileManager
Use of exception handling
func verifyIfSqliteDBExists(){
let docsDir : URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let dbPath : URL = docsDir.appendingPathComponent("database.sqlite")
do{
let sqliteExists : Bool = try dbPath.checkResourceIsReachable()
print("An sqlite database exists at this path :: \(dbPath.path)")
}catch{
print("SQLite NOT Found at :: \(strDBPath)")
}
}
Swift 5
extension FileManager {
class func fileExists(filePath: String) -> Bool {
var isDirectory = ObjCBool(false)
return self.default.fileExists(atPath: filePath, isDirectory: &isDirectory)
}
}
Very simple:
If your path is a URL instance convert to string by 'path' method.
let fileManager = FileManager.default
var isDir: ObjCBool = false
if fileManager.fileExists(atPath: yourURLPath.path, isDirectory: &isDir) {
if isDir.boolValue {
//it's a Directory path
}else{
//it's a File path
}
}
For the benefit of Swift 3 beginners:
Swift 3 has done away with most of the NextStep syntax
So NSURL, NSFilemanager, NSSearchPathForDirectoriesInDomain are no longer used
Instead use URL and FileManager
NSSearchPathForDirectoriesInDomain is not needed
Instead use FileManager.default.urls
Here is a code sample to verify if a file named "database.sqlite" exists in application document directory:
func findIfSqliteDBExists(){
let docsDir : URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let dbPath : URL = docsDir.appendingPathComponent("database.sqlite")
let strDBPath : String = dbPath.path
let fileManager : FileManager = FileManager.default
if fileManager.fileExists(atPath:strDBPath){
print("An sqlite database exists at this path :: \(strDBPath)")
}else{
print("SQLite NOT Found at :: \(strDBPath)")
}
}
This works fine for me in swift4:
func existingFile(fileName: String) -> Bool {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
if let pathComponent = url.appendingPathComponent("\(fileName)") {
let filePath = pathComponent.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath)
{
return true
} else {
return false
}
} else {
return false
}
}
You can check with this call:
if existingFile(fileName: "yourfilename") == true {
// your code if file exists
} else {
// your code if file does not exist
}
I hope it is useful for someone. #;-]
You must add a "/" slash before filename, or you get path like ".../DocumentsFilename.jpg"
Swift 4 example:
var filePath: String {
//manager lets you examine contents of a files and folders in your app.
let manager = FileManager.default
//returns an array of urls from our documentDirectory and we take the first
let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first
//print("this is the url path in the document directory \(String(describing: url))")
//creates a new path component and creates a new file called "Data" where we store our data array
return(url!.appendingPathComponent("Data").path)
}
I put the check in my loadData function which I called in viewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
Then I defined loadData below.
func loadData() {
let manager = FileManager.default
if manager.fileExists(atPath: filePath) {
print("The file exists!")
//Do what you need with the file.
ourData = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as! Array<DataObject>
} else {
print("The file DOES NOT exist! Mournful trumpets sound...")
}
}