Working with Directories in Swift on iOS 8
Previous | Table of Contents | Next |
An Example Swift iOS 8 UIPageViewController Application | Working with Files in Swift on iOS 8 |
<google>BUY_IOS8</google>
It is sometimes easy to forget that iOS is an operating system much like that running on many other computers today. Given this fact, it should come as no surprise that iOS has a file system much like any other operating system allowing applications to store persistent data on behalf of the user. Much like other platforms, the iOS file system provides a directory based structure into which files can be created and organized.
Since the introduction of iOS 5 the iOS app developer has had two options in terms of storing data. Files and data may now be stored on the file system of the local device or remotely using Apple’s iCloud service. In practice, however, it is most likely that an application will utilize iCloud storage to augment, rather than replace, the use of the local file system so familiarity with both concepts is still a necessity.
The topic of iCloud based storage will be covered in detail beginning with the chapter entitled Preparing an iOS 8 App to use iCloud Storage. The goal of this chapter, however, is to provide an overview of how to work with local file system directories from within an iOS 8 application. Topics covered include identifying the application’s document and temporary directories, finding the current working directory, creating, removing and renaming directories and obtaining listings of a directory’s content. Once the topic of directory management has been covered, we will move on to handling files in Working with Files in Swift on iOS 8.
The Application Documents Directory
An iPhone or iPad user can install multiple applications on a single device. The iOS platform is responsible for ensuring that these applications cannot interfere with each other, both in terms of memory usage and data storage. As such, each application is restricted in terms of where it can store data on the file system of the device. iOS achieves this by allowing applications to read and write only to their own Documents and tmp directories. Within these two directories the corresponding application can create files and also sub-directories to any required level of depth. This area constitutes the application’s sandbox and the application cannot usually create or modify files or directories outside of these directories unless using the UIDocumentPickerViewController class.
The NSFileManager, NSFileHandle and NSData Classes
The Foundation Framework provides three classes that are indispensable when it comes to working with files and directories:
- NSFileManager - The NSFileManager class can be used to perform basic file and directory operations such as creating, moving, reading and writing files and reading and setting file attributes. In addition, this class provides methods for, amongst other tasks, identifying the current working directory, changing to a new directory, creating directories and listing the contents of a directory.
- NSFileHandle - The NSFileHandle class is provided for performing lower level operations on files, such as seeking to a specific position in a file and reading and writing a file's contents by a specified number of byte chunks and appending data to an existing file. This class will be used extensively in the chapter entitled Working with Files in Swift on iOS 8.
- NSData - The NSData class provides a useful storage buffer into which the contents of a file may be read, or from which dynamically stored data may be written to a file.
Understanding Pathnames in Swift
As with Mac OS X, iOS defines pathnames using the standard UNIX convention. As such each component of a path is separated by a forward slash (/). When an application starts, the current working directory is the file system’s root directory represented by a single /. From this location, the application must navigate to its own Documents and tmp directories in order to be able to write files to the file system. Path names that begin with a / are said to be absolute path names in that they specify a file system location relative to the root directory. For example, /var/mobile is an absolute path name.
Paths that do not begin with a slash are interpreted to be relative to a current working directory. For example, if the current working directory is /User/demo and the path name is mapdata/local.xml then the file is considered to have an equivalent full, absolute pathname of /User/demo/mapdata/local.xml.
Obtaining a Reference to the Default NSFileManager Object
The NSFileManager class contains a class method named defaultManager that is used to obtain a reference to the application’s default file manager instance:
let filemgr = NSFileManager.defaultManager()
Having obtained the object reference we can begin to use it to work with files and directories.
Identifying the Current Working Directory
As previously mentioned, when an application first loads, its current working directory is the application’s root directory, represented by a / character. The current working directory may be identified at any time by accessing the currentDirectoryPath property of the file manager object. For example, the following code fragment identifies the current working directory:
let currentPath = filemgr.currentDirectoryPath
Identifying the Documents Directory
Each iOS application on a device has its own private Documents and tmp directories into which it is permitted to read and write data. Because the location of these directories is different for each application the only way to find the correct path is to ask iOS. In fact, the exact location will also differ depending on whether the application is running on a physical iPhone or iPad device or in the iOS Simulator. The Documents directory for an application may be identified by making a call to a C function named NSSearchPathForDirectoriesInDomains, passing through an argument (in this case NSDocumentDirectory) indicating that we require the path to the Documents directory. Since this is a C function, as opposed to a method of a Swift class, there is no need for us to establish an instance of a Foundation class such as NSFileManager before making the call. That said, the function does return an object in the form of an NSArray containing the results of the request. We can, therefore, obtain the path to the current application’s Documents directory as follows:
let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) let docsDir = dirPaths[0] as String
When executed, the above code will assign the path to the Documents directory to the docsDir constant.
When executed within the iOS Simulator environment, the path returned will take the form of:
/Users/<user name>/Library/Developer/CoreSimulator/Devices/<device id>/data/Containers/Data/Application/<app id>/Documents
Where <user name> is the name of the user currently logged into the Mac OS X system on which the simulator is running, <device id> is the unique ID of the device on which the app is running and <app id> is the unique ID of the app, for example:
06A3AEBA-8C34-476E-937F-A27BDD2E450A
Clearly this references a path on your Mac OS X system so feel free to open up a Finder window and explore the file system sandbox areas for your iOS applications. When executed on a physical iOS device, however, the path returned by the function call will take the following form:
/var/mobile/Containers/Data/Application/<app id>/Documents
Identifying the Temporary Directory
In addition to the Documents directory, iOS applications are also provided with a tmp directory for the storage of temporary files. The path to the current application’s temporary directory may be ascertained with a call to the NSTemporaryDirectory C function as follows:
let tmpDir = NSTemporaryDirectory() as String
Once executed, the string object referenced by tmpDir will contain the path to the temporary directory for the application. <google>BUY_IOS8</google>
Changing Directory
Having identified the path to the application’s document or temporary directory the chances are good that you will need to make that directory the current working directory. The current working directory of a running iOS application can be changed with a call to the changeCurrentDirectoryPath method of an NSFileManager instance. The destination directory path is passed as an argument to the instance method in the form of a String object. Note that this method returns a Boolean true or false result to indicate if the requested directory change was successful or not. A failure result typically indicates either that the specified directory does not exist, or that the application lacks the appropriate access permissions:
let filemgr = NSFileManager.defaultManager() let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) let docsDir = dirPaths[0] as String if filemgr.changeCurrentDirectoryPath(docsDir) { // Success } else { // Failure }
In the above example, the path to the Documents directory is identified and then used as an argument to the changeCurrentDirectoryPath method of the file manager object to change the current working directory to that location.
Creating a New Directory
A new directory on an iOS device is created using the createDirectoryAtPath instance method of the NSFileManager class, once again passing through the pathname of the new directory as an argument and returning a Boolean success or failure result. The second argument to this method defines whether any intermediate directory levels should be created automatically. For example, if we wanted to create a directory with the path /var/mobile/Containers/Data/Application/<app id>/Documents/mydata/maps and the mydata subdirectory does not yet exist, setting the withIntermediateDirectories argument to true will cause this directory to be created automatically before then creating the maps sub-directory within it. If this argument is set to false, then the attempt to create the directory will fail because mydata does not already exist and we have not given permission for it to be created on our behalf.
This method also takes additional arguments in the form of a set of attributes for the new directory. Specifying nil will use the default attributes.
The final argument provides the option to reference an NSError object to contain error information in the event of a failure. If this is not required, nil may be specified for this argument.
The following code fragment identifies the documents directory and creates a new sub-directory named data in that directory:
let filemgr = NSFileManager.defaultManager() let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) let docsDir = dirPaths[0] as String let newDir = docsDir.stringByAppendingPathComponent("data") var error: NSError? if !filemgr.createDirectoryAtPath(newDir, withIntermediateDirectories: true, attributes: nil, error: &error) { println("Failed to create dir: \(error!.localizedDescription)") }
Deleting a Directory
An existing directory may be removed from the file system using the removeItemAtPath method, passing through the path of the directory to be removed as an argument. For example, to remove the data directory created in the preceding example we might write the following code:
var error: NSError? if !filemgr.removeItemAtPath(newDir, error: &error) { println("Failed to delete directory: \(error!.localizedDescription)") }
Listing the Contents of a Directory
A listing of the files contained within a specified directory can be obtained using the directoryContentsAtPath method. This method takes the directory pathname as an argument and returns an array object containing the names of the files and sub-directories in that directory. The following example obtains a listing of the contents of the root directory (/) and displays each item in the Xcode console panel during execution:
let filemgr = NSFileManager.defaultManager() let filelist = filemgr.contentsOfDirectoryAtPath("/", error: &error) for filename in filelist! { println(filename) }
Getting the Attributes of a File or Directory
The attributes of a file or directory may be obtained using the attributesOfItemAtPath method. This takes as arguments the path of the directory and an optional NSError object into which information about any errors will be placed (may be specified as nil if this information is not required). The results are returned in the form of an NSDictionary dictionary object. The keys for this dictionary are as follows:
NSFileType NSFileTypeDirectory NSFileTypeRegular NSFileTypeSymbolicLink NSFileTypeSocket NSFileTypeCharacterSpecial NSFileTypeBlockSpecial NSFileTypeUnknown NSFileSize NSFileModificationDate NSFileReferenceCount NSFileDeviceIdentifier NSFileOwnerAccountName NSFileGroupOwnerAccountName NSFilePosixPermissions NSFileSystemNumber NSFileSystemFileNumber NSFileExtensionHidden NSFileHFSCreatorCode NSFileHFSTypeCode NSFileImmutable NSFileAppendOnly NSFileCreationDate NSFileOwnerAccountID NSFileGroupOwnerAccountID
For example, we can extract the file type for the /Applications directory using the following code excerpt:
let filemgr = NSFileManager.defaultManager() var error: NSError? let attribs: NSDictionary? = filemgr.attributesOfItemAtPath( "/Applications", error: &error) if let fileattribs = attribs { let type = fileattribs["NSFileType"] as String println("File type \(type)") }
When executed, results similar to the following output will appear in the Xcode console:
File type NSFileTypeDirectory
<google>BUY_IOS8</google>
Previous | Table of Contents | Next |
An Example Swift iOS 8 UIPageViewController Application | Working with Files in Swift on iOS 8 |