Home > Articles > Mobile Application Development & Programming

This chapter is from the book

Working with UIDocument

The UIDocument class is an abstract class that supports cloud-ready asynchronous reading and writing of data. UIDocument simplifies the way you work with iCloud data, because it handles nearly all the issues you encounter about coordinating local data with cloud data. Recipe 18-1’s “just write to the right folder” approach cannot be recommended for serious development. What UIDocument does is add a shell for managing your document, letting you send read, save, and write requests through it in a safer and more structured way.

You never work directly with the UIDocument class. Instead, you subclass it and implement several simple key features that link the class to the ubiquitous data.

Establishing the Document as a File Presenter

UIDocument implements the NSFilePresenter class. This means that you can register the class to receive updates whenever its cloud data gets updated by telling NSFileCoordinator to add the document as “presenter.” This is terribly unhelpful naming, I know, but what it means is that a presenter class is one that takes a strong interest in knowing when outside changes happen to a given file. Receiving alerts about these changes allows it to update its GUI presentation in response.

The registration process works like this. You create a document and a coordinator, initializing the coordinator with the document as its performer as the following code snippet shows. Registering the new document/presenter with NSFileCoordinator class allows it to receive updates. You must perform both these steps, both at the instance and class level.

imageDocument = [[ImageDocument alloc] initWithFileURL:ubiquityURL];
imageDocument.delegate = self;
coordinator = [[NSFileCoordinator alloc]    
    initWithFilePresenter:imageDocument];    
[NSFileCoordinator addFilePresenter:imageDocument];

Although all the actual coordination implementation details are handled for you via the UIDocument class (for example, UIDocument handles all file coordination issues for you, so you do not need to manually coordinate reading and writing), this set up portion is not. Unless you establish the presenter/coordinator pair manually, your document will not receive updates from the cloud.

Moving to the Cloud

NSFileManager allows you to move local files to and from the cloud using the setUbiquitous:itemAtURL:destinationURL:error: method. What this method does is nothing more than move (not copy) the file in question from your sandbox into the central iCloud folder and back. However, it does so safely in an approved manner.

The method takes three arguments. The first establishes the direction of movement. YES brings items from the sandbox to the cloud and NO the reverse. The second argument must always be the source URL and the third its destination. The first argument is a little pointless in that the source URL will always be either sandbox or cloud and the destination must be the reverse. If all three arguments do not line up, the method will fail.

To move to the cloud, use YES, the local sandbox URL, and the destination cloud URL, as shown here. To remove an item from the cloud, use NO, then the cloud URL, and then the local sandbox URL.

if (![[NSFileManager defaultManager] setUbiquitous:YES
    itemAtURL:localURL destinationURL:ubiquityURL error:&error])    
{    
    NSLog(@"Error making local file ubiquitous. %@",    
        [error localizedFailureReason]);    
    return;    
}

Retrieve the cloud URL by appending the file name to the results of the ubiquityDocumentsURL method, which is shown in Recipe 18-1.

Evicting Items from the Cloud

Removing an item from the cloud by setting its ubiquity to NO deletes it from both the cloud and from all of your user’s devices and computers. At times, you may want to remove a local copy in order to force it to refresh from the cloud without deleting it. To do this, do not alter the file’s ubiquity. Instead, call an NSFileManager eviction method, as follows. Evicting a file allows iCloud to retrieve a new copy, but does not remove it from central cloud storage.

+ (BOOL) evictFile: (NSString *) filename
    forContainer: (NSString *) container    
{    
    if (!filename) return NO;        

    NSURL *targetURL =    
        [self ubiquityDocumentsFileURL:filename    
            forContainer:container];    

    BOOL targetExists =    
        [[NSFileManager defaultManager]    
            fileExistsAtPath:targetURL.path];    
    if (!targetExists) return NO;        

    NSError *error;    
    if (![[NSFileManager defaultManager]
       evictUbiquitousItemAtURL:targetURL error:&error])    
    {    
       NSLog(@"Error evicting current copy of %@.: %@",
           filename, error.localizedFailureReason);    
       return NO;
    }        
     return YES;    
}        
+ (BOOL) evictFile: (NSString *) filename    
{
    return [self evictFile:filename forContainer:nil];    
}

In addition to file eviction, you can actually force iCloud to download a new copy. This is of use only when you write your own data handlers. You pretty much never have to do this if you use Apple’s recommended UIDocument and UIManagedDocument classes.

+ (BOOL) forceDownload: (NSString *) filename
    forContainer: (NSString*) container    
{    
    if (!filename) return NO;    
    NSURL *targetURL =
        [self ubiquityDocumentsFileURL:filename    
            forContainer:container];
    if (!targetURL) return NO;

    NSError *error;    
    if (![self evictFile:filename forContainer:container])    
        return NO;    
    if (![[NSFileManager defaultManager]    
         startDownloadingUbiquitousItemAtURL:targetURL    
          error:&error])
    {
        NSLog(@"Error starting download of %@: %@", filename,   
              error.localizedFailureReason);
        return NO;    
    }    
    return YES;
}        
+ (BOOL) forceDownload: (NSString *) filename    
{    
    return [self forceDownload:filename forContainer:nil];    
}

Opening and Saving Files

You open and save UIDocument instances to read them in from a file and to store your changes out. Opening the file is generally performed as part of creating the document. You provide it with a file URL pointing to the stored file, and ask it to open the file. For iCloud materials, this path must point to the correct place in the ubiquitous documents folder to work with iCloud and not to a file saved locally. You can also use the UIDocument class to access and read non-iCloud files that have been moved back to the sandbox. This flexibility unifies how you access files, letting users choose which items to send to the cloud.

imageDocument = [[ImageDocument alloc] initWithFileURL:theURL];
[imageDocument openWithCompletionHandler:^(BOOL success) {    
    NSLog(@"Open file was: %@", success ? @"successful" : @"failure");    
    if (success) imageView.image = imageDocument.image;}];

Each time you make changes to your document, you’ll want to save those updates as transparently as possible. Saving data, especially to small files, is cheap. Making updates as soon as your user creates changes allows you to build a persistent system that synchronizes with your user’s adjustments.

imageDocument.image = image;
imageDocument saveToURL:imageDocument.fileURL    
    forSaveOperation:UIDocumentSaveForOverwriting    
    completionHandler:^(BOOL success){    
      NSLog(@"Attempt to save to URL %@",    
         success ? @"succeeded" : @"failed");    
    }];

UIDocument is fully undo-aware and provides its own undoManager property hook. This allows you to save without having to worry about creating permanent changes from which you cannot recover.

Subscribing to UIDocument Notifications

When working with documents, your primary class must subscribe to a single notification: UIDocumentStateChangedNotification. Your handler uses that notification to handle conflict issues, allowing your document to update to the latest version. At its simplest, the notification subscription looks like this. This snippet resolves all conflict issues by accepting the most recent version and updating its presentation. This example, which works with the code in Recipe 18-2, is built around a simple image presentation app. When new images arrive, the application view updates and shows them.

[[NSNotificationCenter defaultCenter]
   addObserverForName: UIDocumentStateChangedNotification    
   object:nil queue:[NSOperationQueue mainQueue]    
   usingBlock:^(NSNotification __strong *notification)    
   {    
       if (imageDocument.documentState == UIDocumentStateInConflict)    
       {    
           NSError *error;    
           NSLog(@"Resolving conflict. (Newest wins.)");    
           if (![NSFileVersion    
                   removeOtherVersionsOfItemAtURL:    
                       imageDocument.fileURL    
                   error:&error])    
       {    
          NSLog(@"Error removing other document versions: %@",    
               error.localizedFailureReason);    
           return;    
       }    
       imageView.image = imageDocument.image;    
   }    
}];

This code, which uses “newest version wins,” represents the simplest way you can handle document conflicts using NSFileVersion, a class built around time-based file revisions. Although it’s the simplest approach, it’s not always the best. You’ll probably want to use a little more finesse in your applications. iOS can store more than one version of a file at a time. You can retrieve all conflicted versions of the file version class through something like the following. The snippet

[NSFileVersion unresolvedConflictVersionsOfItemAtURL:
    imageDocument.fileURL]

returns an array of file version instances. You can iterate through those instances to help resolve your conflicts, choosing which of the conflicted saves best represents the state that you want to present to the user.

When you need to examine the actual version data to better judge or merge that material, you can pull each file’s versioned contents from the instance’s URL property. From there, you can select which file version to use by removing the other versions (as in the previous example) or replace the file data entirely.

The document’s documentState property lets you know whether your saves are currently conflicted or if you’re clear to make changes. Additional states include UIDocumentStateNormal, which allows users to make unhindered changes; UIDocumentStateClosed, indicating that the document wasn’t opened open properly (or has been deliberately closed) and is probably not valid for edits; UIDocumentStateSavingError, telling you that the file could not be saved out; and UIDocumentStateEditingDisabled, which means that the file is busy. A busy file is never safe for user edits, so your application should disable changes until the state changes back to something safer.

You may allow edits during both normal and conflict states but you should inform your user when conflict issues may prevent updates from propagating out to the cloud, such as the Mom-on-a-Plane scenario discussed earlier. Apple recommends offering visual feedback, such as a green light for unhindered updates and a yellow one for modifications that may need later intervention to integrate.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020