Resolving Document Version Conflicts
In an iCloud world, when a user has installed a document-based application on multiple devices or desktop systems, there can be conflicts between different versions of the same document. Recall that an application updates a document file in the local container directory and those changes are then transmitted—usually immediately—to iCloud. But what if this transmission is not immediate? For example, you edit a document using the Mac OS X version of your application, but you’ve also edited the same document using the iPad version of the application—and you did so while the device was in Airplane Mode. When you switch off Airplane Mode, the local change to the document is transferred to iCloud. iCloud notices a conflict and notifies the application.
Learning About Document Version Conflicts
As Monitoring Document-State Changes and Handling Errors describes, your application becomes aware of document-version conflicts by observing the UIDocumentStateChangedNotification
notification. If the documentState
property changes to UIDocumentStateInConflict
, multiple versions of the same document exist. The application is responsible for resolving those conflicts as soon as possible, with or without the user’s help.
You learn about the conflicting versions of a document through two class methods of the NSFileVersion
class. The currentVersionOfItemAtURL:
method returns an NSFileVersion
object representing what’s referred to as the current file; the current file is chosen by iCloud on some basis as the current “conflict winner” and is the same across all devices. By calling the unresolvedConflictVersionsOfItemAtURL:
method, you get an array of NSFileVersion
objects; these objects are called conflict versions, and each represents an unresolved version conflict for the file located at the specified URL. NSFileVersion
objects can give you information helpful in resolving conflicts, such as modification dates, localized document names, and localized names of saving computers.
Strategies for Resolving Document Version Conflicts
Your application can follow one of three strategies for resolving document-version conflicts:
Merge the changes from the conflicting versions.
Choose one of the document versions based on some pertinent factor, such as the version with the latest modification date.
Enable the user to view conflicting versions of a document and select the one to use.
Which strategy is best to use depends a lot upon your document data. If you can merge the contents of different document versions without introducing contradictory elements, then follow that strategy. Or choose the document version with the latest modification date if your application doesn’t suffer any loss of data as a result.
Generally, you should try to resolve the conflict without involving the user, but for some applications that might not be possible. If an application takes the user-centered approach, it should discreetly inform the user about the version conflict and expose a button or other control that initiates the resolution procedure. An Example: Letting the User Pick the Version examines the code of an application that lets the user select the document version to use.
How to Tell iOS That a Document Version Conflict Is Resolved
When your application or its users resolve a document version conflict by picking a version of a document, your application should complete the following steps:
If the chosen version is a conflict version, replace the current document file with the conflict-version document file.
To to this, call the
replaceItemAtURL:options:error:
method on theNSFileVersion
object representing the version, passing in the document’s current-file URL.If the chosen version is a conflict version, revert the document so that it displays the new data in the document file
To do this, call the
UIDocument
methodrevertToContentsOfURL:completionHandler:
on the document object, passing in the document’s current-file URL.Disassociate all conflict versions with the document’s file URL.
To do this, call the
NSFileVersion
class methodremoveOtherVersionsOfItemAtURL:error:
, passing in the document’s file URL.Mark each conflict version as resolved so that iOS doesn’t raise it again as a conflicting version.
To do this, set the
resolved
property of eachNSFileVersion
object representing a conflict version toYES
. This step should always be done last.Remove the resolved versions of the document.
For any versions you no longer need, call the
removeAndReturnError:
method ofNSFileVersion
to reclaim the storage for the file. Document revisions remain on the server until you delete them.
An Example: Letting the User Pick the Version
Our sample document-based application is a simple text editor. It would be difficult for such an application to locate and merge textual differences in conflicting versions of the document, and even if it did, the resulting document might not be what the user wants. The application could pick the document version with the most recent modification date, but then again there’s no way to be certain that is the version the user wants. A good conflict-resolution strategy in this case is to let the user, who is most familiar with the document’s contents, pick the version she or he wants.
You might recall the code shown in Listing 6-1 from Monitoring Document-State Changes and Handling Errors. This code shows the method of the document’s view controller that handles the UIDocumentStateChangedNotification
notification posted by UIDocument
when there is a change in document state. If the new document state is UIDocumentStateInConflict
, the view controller shows a Resolve Conflicts button in a custom status view. (It also sets the color of the status indicator to red.)
Listing 6-1 Detecting a conflict in document versions
-(void)documentStateChanged { |
UIDocumentState state = _document.documentState; |
[_statusView setDocumentState:state]; |
if (state & UIDocumentStateEditingDisabled) { |
[_textView resignFirstResponder]; |
} |
if (state & UIDocumentStateInConflict) { |
[self showConflictButton]; // <------ Shows "Resolve Conflicts" button |
} |
else { |
[self hideConflictButton]; |
[self dismissModalViewControllerAnimated:YES]; |
} |
} |
When the user taps the button, UIKit invokes the method in Listing 6-2. This method displays modally the view of a custom conflict-resolver view controller.
Listing 6-2 Showing the user interface for resolving document version conflicts
-(void)conflictButtonPushed |
{ |
ConflictResolverViewController* conflictResolver = [[ConflictResolverViewController alloc] |
initWithURL:_document.fileURL delegate:self]; |
[self presentViewController:conflictResolver animated:YES completion:nil]; |
[conflictResolver release]; |
} |
The ConflictResolverViewController
object creates a page view controller (UIPageViewController
object) that allows user to page between, and examine, the current-file document and each conflict-version document. In the tool bar of each document view is a Select Version button. If the user taps that button, one of the two custom delegation methods shown in Listing 6-3 is called, depending on whether the chosen document is the current-file document or a conflict-version document.
Listing 6-3 Resolving a document version conflict
-(void)conflictResolver:(ConflictResolverViewController *)conflictResolver |
didResolveWithFileVersion:(NSFileVersion *)fileVersion { |
[self dismissViewControllerAnimated:YES completion:nil]; |
[fileVersion replaceItemAtURL:_document.fileURL options:0 error:nil]; |
[NSFileVersion removeOtherVersionsOfItemAtURL:_document.fileURL error:nil]; |
[_document revertToContentsOfURL:_document.fileURL completionHandler:nil]; |
NSArray* conflictVersions = [NSFileVersion unresolvedConflictVersionsOfItemAtURL:_document.fileURL]; |
for (NSFileVersion* fileVersion in conflictVersions) { |
fileVersion.resolved = YES; |
} |
} |
-(void)conflictResolverDidResolveWithCurrentVersion:(ConflictResolverViewController*)conflictResolver { |
[self dismissViewControllerAnimated:YES completion:nil]; |
[NSFileVersion removeOtherVersionsOfItemAtURL:_document.fileURL error:nil]; |
NSArray* conflictVersions = [NSFileVersion unresolvedConflictVersionsOfItemAtURL:_document.fileURL]; |
for (NSFileVersion* fileVersion in conflictVersions) { |
fileVersion.resolved = YES; |
} |
} |
These methods illustrate the steps described in How to Tell iOS That a Document Version Conflict Is Resolved. If the chosen document is a conflict version, the delegate calls replaceItemAtURL:options:error:
on the passed-in NSFileVersion
object to replace the document file in the iCloud container directory with the chosen document. The delegate then enumerates the array containing NSFileVersion
objects representing all conflict versions of the document and sets the resolved
property of each object to YES
. It then asks NSFileVersion
to remove all other conflict versions of the document associated with the document’s file URL and calls revertToContentsOfURL:completionHandler:
to revert the displayed document to the new contents of the document file.
The second delegation method, invoked when the current document file is selected, is much simpler. It sets the resolved
property of all NSFileVersion
objects representing conflict versions to YES
and removes all conflict versions associated with the document file URL.
Copyright © 2012 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2012-09-19