35

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?

iOSProgrammingIsFun
  • 1,418
  • 1
  • 15
  • 32
  • Why do you append the bundle id to the end of the path? The directory is already unique to your app. Adding the bundle id is redundant. – rmaddy Apr 25 '13 at 01:43
  • Because that code is copied DIRECTLY from the Apple File System Programming Guide (Listing 2-1) as the correct way to create a URL for an item in the app support directory. – iOSProgrammingIsFun Apr 25 '13 at 15:15
  • 3
    That code makes no sense for iOS. It's fine for OS X though. In iOS, the `Application Support` directory is already inside your app's sandbox. In OS X, it's not. – rmaddy Apr 25 '13 at 15:26
  • 1
    @rmaddy I really respect you as an iOS authority, so I wanted to double check with you on this. It explicitly says in the iOS docs several times to append the bundle id to the end of the path. Why do they keep saying this if they don't mean it/it isn't necessary? – SAHM Jun 13 '16 at 02:25
  • 1
    https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html : "Use the Application Support directory constant NSApplicationSupportDirectory, appending your for: Resource and data files that your app creates and manages for the user. You might use this directory to store app state information, computed or downloaded data, or even user created data that you manage on behalf of the user." – SAHM Jun 13 '16 at 02:25
  • https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW1 : "All content in this directory should be placed in a custom subdirectory whose name is that of your app’s bundle identifier or your company" – SAHM Jun 13 '16 at 02:35
  • 3
    @SAHM It's possible that other libraries used by your app might also write to your app's Application Support folder. So if your own code appends your bundle id it prevents a possible naming collision. – rmaddy Jun 13 '16 at 02:39
  • 1
    @rmaddy So it would probably be a good idea to do it, even though it might not be *totally* necessary, right? – SAHM Jun 13 '16 at 02:44
  • @SAHM Thank you guys for this conversation! Esp. SAHM for asking pin-point Questions! :) – N a y y a r May 03 '21 at 22:05

6 Answers6

52

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");
        }
    }
}
0xWood
  • 1,326
  • 12
  • 15
51

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;
Léo Natan
  • 56,823
  • 9
  • 150
  • 195
rmaddy
  • 314,917
  • 42
  • 532
  • 579
9

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.

mbelsky
  • 6,093
  • 2
  • 26
  • 34
csch
  • 1,455
  • 14
  • 12
6

One liner - will create if necessary too:

[[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil]
RunLoop
  • 20,288
  • 21
  • 96
  • 151
4

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
   }
rmaddy
  • 314,917
  • 42
  • 532
  • 579
RenniePet
  • 11,420
  • 7
  • 80
  • 106
1

Swift 4.2 version

    let fileManager = FileManager.default
    let urls = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)
    if let applicationSupportURL = urls.last {
        do{
            try fileManager.createDirectory(at: applicationSupportURL, withIntermediateDirectories: true, attributes: nil)
        }
        catch{
            print(error)
        }
    }
Ozgur Sahin
  • 1,305
  • 16
  • 24