2

I have a UIDocument based app, without iCloud support. The user is able to create documents, and save them to app's Document directory.

I have also created few "sample" documents, and passing them with the app's bundle. I want the user to be able to open sample documents the same way as they can open their own documents:

NSArray *samplesContent = [[NSBundle mainBundle] URLsForResourcesWithExtension:@"doco" subdirectory:@"Samples"];

for (NSURL *sampleURL in samplesContent) {
    Doco *doco = [[Doco alloc] initWithFileURL:sampleURL];
    [doco disableEditing];

    [doco openWithCompletionHandler:^(BOOL success) {
        if (success) {
            [self.docos addObject:doco];

            if (self.docos.count >= samplesContent.count) {
                [self.tableView reloadData];
            }
        } else DLog(@"Failed to open %@", [sampleURL pathExtension]);
     }];
}

What I am seeing is this:

  1. On viewDidLoad, docs array gets populated with sample docs and displayed in the tableView the first time app launches. No issues here, all is smooth and nice.
  2. Then I back out of the view with the following code to close all open docs:

    int count = 0;
    
    for (Doco *doco in self.docos) {
        if (doco.documentState == UIDocumentStateNormal) {
            [doco closeWithCompletionHandler:nil];
            count++;
        }
    }
    
    DLog(@"Closed %i docs", count);
    
  3. When I open the view again, the array of docs should get populated again and tableView re-populated, but nothing happens.

The completion handler below never gets called, although the URL is pointing to the same file and it is valid:

[doco openWithCompletionHandler:^(BOOL success) {}

I do not have this issue for user generated docs stored in Documents, so my assumption is that it has something to do with auto-save, that gets called on read-only bundle and fails

But I am sort of stuck on this part, any help will be appreciated.

Sam Spencer
  • 8,492
  • 12
  • 76
  • 133
Andrei G.
  • 1,710
  • 1
  • 14
  • 23
  • Thought that a valid workaround would be to copy all sample docs to Documents, and open them from there...but this way i don't learn anything new :) i would prefer to understand the issue, Cheers for all helping comments! – Andrei G. Jun 25 '12 at 06:43
  • 1
    Check the completion of [doco closeWithCompletionHandler:nil]. Is the document closed successfully? – Basel Jun 25 '12 at 06:46
  • Just checked, it was not called at all....because i was dismissing view controller, BEFORE closing all docs. Now i see that they fail to close because it's trying to write to Bundle, however they open again successfully.. – Andrei G. Jun 25 '12 at 07:06
  • I am getting this `Foundation called mkdir("/var/mobile/Applications/BBBAA4C4-4824-4E67-86F4-34FC19AF1827/Doco.app/Samples/(A Document Being Saved By Doco)"), it didn't return 0, and errno was set to 1.` therefore close is not successful. – Andrei G. Jun 25 '12 at 07:09
  • My sample doc was updated without my knowledge, therefore it was trying to save changes unsuccessfully...now i make sure it does not get updated and it gets closed without issues. reloading them again work nice and fast. Looks like issue is resolved. – Andrei G. Jun 25 '12 at 07:21

2 Answers2

2

If UIDocument is updated it will try to save changes on close. Since UIDocument was loaded from read-only bundle, I had to make sure that it does not get updated, otherwise close block returns success=NO, and document is not closed...

CodeBender
  • 35,668
  • 12
  • 125
  • 132
Andrei G.
  • 1,710
  • 1
  • 14
  • 23
2

The problem has already been identified, but I think it's worth describing a couple of simple solutions since including sample documents in an app's bundle is not uncommon.

So the problem is that the sample document is trying to save changes when it is closed, but saving cannot succeed in a read-only app bundle.

I think there are two main solutions here:

  1. Copy the sample document into the Documents directory, where it can be treated like any other document and saved successfully (if you want user edits to the sample document to be saved, use this approach).

  2. Prevent the document from trying to save (for read-only sample documents).

So here are some simple examples...


1. Copy sample documents into Documents directory

On first launch (or indeed whenever you decide to 'refresh' the sample document), use NSFileManager to copy the file into place:

- (void)refreshSampleDocuments
{
    NSArray *sampleFromURLs = [[NSBundle mainBundle] URLsForResourcesWithExtension:@"doc" subdirectory:@"Samples"];

    for (NSURL *sampleFromURL in sampleFromURLs) {

        NSString *sampleFilename = [sampleFromURL lastPathComponent];
        NSURL *sampleToURL = [[self documentsDirectoryURL] URLByAppendingPathComponent:sampleFilename];

        // ...
        // Do some checks to make sure you won't write over any user documents!
        // ....

        NSError *error;
        BOOL copySuccessful = [[NSFileManager defaultManager] copyItemAtURL:sampleFromURL toURL:sampleToURL error:&error];

        if (!copySuccessful) {
            // Handle error...
        }
    }
}

2. Prevent sample documents from trying to save

This approach is much simpler (for read-only documents), and easier than trying to prevent updates wherever they might occur in the document.

When closeWithCompletionHandler: is called on UIDocument, autosaveWithCompletionHandler: is invoked to ensure the document file is saved before closing. This in turn invokes hasUnsavedChanges to decide whether a save is necessary. So if hasUnsavedChanges returns NO, then any invocation of the autosaving mechanism will result in no changes being written out.

(N.B. manually calling saveToURL:forSaveOperation:completionHandler: will still force a save, regardless of what hasUnsavedChanges returns.)

So in your UIDocument subclass, override hasUnsavedChanges to return NO if the document is read-only.

@interface MyDocument : UIDocument

@property(nonatomic, getter = isReadOnly) BOOL readOnly;

@end


@implementation MyDocument

- (BOOL)hasUnsavedChanges
{
    return [self isReadOnly] ? NO : [super hasUnsavedChanges];
}

@end

Community
  • 1
  • 1
Stuart
  • 36,683
  • 19
  • 101
  • 139