iOS: Can't save file to 'Application Support' folder, but can to 'Documents'

19,576

Solution 1

Unlike the Documents directory, the Application Support directory does not exist in the app's sandbox by default. You need to create it before you can use it.

And a much simpler way to get a reference to the directory is:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *appSupportDirectory = paths.firstObject;

Solution 2

In case anyone is unsure how to do what rmaddy describes:

NSString *appSupportDir = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) lastObject];
//If there isn't an App Support Directory yet ... 
if (![[NSFileManager defaultManager] fileExistsAtPath:appSupportDir isDirectory:NULL]) {
    NSError *error = nil;
//Create one 
    if (![[NSFileManager defaultManager] createDirectoryAtPath:appSupportDir withIntermediateDirectories:YES attributes:nil error:&error]) {
        NSLog(@"%@", error.localizedDescription);
    }
    else {
// *** OPTIONAL *** Mark the directory as excluded from iCloud backups 
        NSURL *url = [NSURL fileURLWithPath:appSupportDir];
        if (![url setResourceValue:@YES
                            forKey:NSURLIsExcludedFromBackupKey
                             error:&error])
        {
            NSLog(@"Error excluding %@ from backup %@", url.lastPathComponent, error.localizedDescription);
        }
        else {
            NSLog(@"Yay");
        }
    }
}

Solution 3

I came across the same issue and decided to use a more concise approach:

let fileManager = NSFileManager.defaultManager()
let urls = fileManager.URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask) as! [NSURL]
if let applicationSupportURL = urls.last {
    fileManager.createDirectoryAtURL(applicationSupportURL, withIntermediateDirectories: true, attributes: nil, error: nil)
}

This works because createDirectoryAtURL using withIntermediateDirectories: true only creates the folder if it doesn't exist.

Solution 4

One liner - will create if necessary too:

[[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil]

Solution 5

Here's some Swift code for iOS that can write a binary data file to the application support directory. Portions of this were inspired by the answer by chrysAllwood.

   /// Method to write a file containing binary data to the "application support" directory.
   ///
   /// - Parameters:
   ///   - fileName: Name of the file to be written.
   ///   - dataBytes: File contents as a byte array.
   ///   - optionalSubfolder: Subfolder to contain the file, in addition to the bundle ID subfolder.
   ///                        If this is omitted no extra subfolder is created/used.
   ///   - iCloudBackupForFolder: Specify false to opt out from iCloud backup for whole folder or
   ///                            subfolder. This is only relevant if this method call results in
   ///                            creation of the folder or subfolder, otherwise it is ignored.
   /// - Returns: Nil if all OK, otherwise text for a couple of non-Error errors.
   /// - Throws: Various errors possible, probably of type NSError.
   public func writeBytesToApplicationSupportFile(_ fileName : String,
                                                  _ dataBytes : [UInt8],
                                                  optionalSubfolder : String? = nil,
                                                  iCloudBackupForFolder : Bool = true)
                                                 throws -> String? {

      let fileManager = FileManager.default

      // Get iOS directory for "application support" files
      let appSupportDirectory =
                  fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
      if appSupportDirectory == nil {
         return "Unable to determine iOS application support directory for this app."
      }

      // Add "bundle ID" as subfolder. This is recommended by Apple, although it is probably not
      // necessary.
      if Bundle.main.bundleIdentifier == nil {
         return "Unable to determine bundle ID for the app."
      }
      var mySupportDirectory =
                  appSupportDirectory!.appendingPathComponent(Bundle.main.bundleIdentifier!)

      // Add an additional subfolder if that option was specified
      if optionalSubfolder != nil {
         mySupportDirectory = appSupportDirectory!.appendingPathComponent(optionalSubfolder!)
      }

      // Create the folder and subfolder(s) as needed
      if !fileManager.fileExists(atPath: mySupportDirectory.path) {
         try fileManager.createDirectory(atPath: mySupportDirectory.path,
                                         withIntermediateDirectories: true, attributes: nil)

         // Opt out from iCloud backup for this subfolder if requested
         if !iCloudBackupForFolder {
            var resourceValues : URLResourceValues = URLResourceValues()
            resourceValues.isExcludedFromBackup = true
            try mySupportDirectory.setResourceValues(resourceValues)
         }
      }

      // Create the file if necessary
      let mySupportFile = mySupportDirectory.appendingPathComponent(fileName)
      if !fileManager.fileExists(atPath: mySupportFile.path) {
         if !fileManager.createFile(atPath: mySupportFile.path, contents: nil, attributes: nil) {
            return "File creation failed."
         }
      }

      // Write the file (finally)
      let fileHandle = try FileHandle(forWritingTo: mySupportFile)
      fileHandle.write(NSData(bytes: UnsafePointer(dataBytes), length: dataBytes.count) as Data)
      fileHandle.closeFile()

      return nil
   }
Share:
19,576
iOSProgrammingIsFun
Author by

iOSProgrammingIsFun

Updated on June 18, 2022

Comments

  • iOSProgrammingIsFun
    iOSProgrammingIsFun almost 2 years

    I can download and save a binary file to the 'Documents' folder with a custom name perfectly fine.

    If I just change the URL to the 'Application Support' folder instead of the 'Documents' folder, it fails to write to that URL saying it doesn't exist.

    Here's the URL construction code:

    - ( NSURL * ) getSaveFolder
    {
        NSURL * appSupportDir    = nil;
        NSURL * appDirectory     = nil;
        NSArray * possibleURLs   = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSAllDomainsMask];
        
        if ( [possibleURLs count] >= 1 )
        {
            appSupportDir = [possibleURLs objectAtIndex:0];
        }
    
        if ( appSupportDir != nil)
        {
            NSString * appBundleID = [[NSBundle mainBundle] bundleIdentifier];
            appDirectory           = [appSupportDir URLByAppendingPathComponent:appBundleID];
        }
    
        return appSupportDir;
    }
    

    Here's the saving code:

    - ( void ) writeOutDataToFile:( NSData * )data
    {
        NSURL * finalURL = [self.rootPathURL URLByAppendingPathComponent:self.aFileName];
    
        [data writeToURL:finalURL atomically:YES];
    }
    

    If I change the NSArray to:

    NSArray * possibleURLs   = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
    

    then it saves fine.

    I've read the Apple Docs on File stuff and can't fix this - what am I missing?

  • rmaddy
    rmaddy about 11 years
    Did you create the directory like I stated?
  • Doug
    Doug almost 9 years
    Thanks for taking the time to share an example!
  • Juan Boero
    Juan Boero over 8 years
    @chrysallwood , care to share the swift version?
  • matm
    matm over 7 years
    From Technical Q&A QA1719: "Apps should create their own directories for exclusion, rather than excluding the system defined directories". I think it is a good practice.
  • Jeremy Wiebe
    Jeremy Wiebe almost 6 years
    Way late to this party, but the original question is using NSAllDomainsMask instead of NSUserDomainMask. I suspect that's the issue.