Working with Directories in Swift on iOS 10

From Techotopia
Revision as of 19:01, 3 November 2016 by Neil (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

PreviousTable of ContentsNext
An iOS 10 Stack View TutorialWorking with Files in Swift on iOS 10


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book


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 10 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 10 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 10.


Contents


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 FileManager, FileHandle and Data Classes

The Foundation Framework provides three classes that are indispensable when it comes to working with files and directories:

FileManager - The FileManager 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.

FileHandle - The FileHandle 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 10.

Data - The Data 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.

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book


Understanding Pathnames in Swift

As with macOS, 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 FileManager Object

The FileManager class contains a property named default that is used to obtain a reference to the application’s default file manager instance:

let filemgr = FileManager.default

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

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

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 file manager method named urls(for:), passing through an argument (in this case .documentDirectory) indicating that we require the path to the Documents directory. The .userDomainMask argument indicates to the urls(for:) method that we are looking for the Documents directory located in the application’s home directory. The method returns an object in the form of an array containing the results of the request. We can, therefore, obtain the path to the current application’s Documents directory as follows:

let filemgr = FileManager.default

let dirPaths = filemgr.urls(for: .documentDirectory, in: .userDomainMask)

let docsDir = dirPaths[0].path

In order to access the path string contained within the URL, we simply access the path property of the URL at index position 0 in the array and assign it to the docsDir constant as outlined above.

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 macOS 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 macOS 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

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

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()

Once executed, the string object referenced by tmpDir will contain the path to the temporary directory for the application.

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 a FileManager 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 = FileManager.default

let dirPaths = filemgr.urls(for: .documentDirectory, in: .userDomainMask)

let docsDir = dirPaths[0].path

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 createDirectory(atPath:) instance method of the FileManager 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.

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

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 following code fragment identifies the documents directory and creates a new sub-directory named data in that directory:

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 let error as NSError {
            print("Error: \(error.localizedDescription)")
}

Deleting a Directory

An existing directory may be removed from the file system using the removeItem(atPath:) 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:

do {
    try filemgr.removeItem(atPath: newDir)
} catch let error {
    print("Error: \(error.localizedDescription)")
}

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

Listing the Contents of a Directory

A listing of the files contained within a specified directory can be obtained using the contentsOfDirectory(atPath:) 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:

do {
    let filelist = try filemgr.contentsOfDirectory(atPath: "/")

    for filename in filelist {
        print(filename)
    }
} catch let error {
    print("Error: \(error.localizedDescription)")
}

Getting the Attributes of a File or Directory

The attributes of a file or directory may be obtained using the attributesOfItem(atPath:) method. This takes as arguments the path of the directory and an optional Error 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

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

For example, we can extract the file type for the /Applications directory using the following code excerpt:

let filemgr = FileManager.default

do {
    let attribs: NSDictionary =
           try filemgr.attributesOfItem(atPath: "/Applications") as NSDictionary
    let type = attribs["NSFileType"] as! String
    print("File type \(type)")
} catch let error {
    print("Error: \(error.localizedDescription)")
}

When executed, results similar to the following output will appear in the Xcode console:

File type NSFileTypeDirectory

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book



PreviousTable of ContentsNext
An iOS 10 Stack View TutorialWorking with Files in Swift on iOS 10