(译)如何自定义UIDocument的子类

作者: Rim99 | 来源:发表于2015-12-05 12:45 被阅读553次

    本文节译自Apple iOS开发文档《Document-Based App Programming Guide for iOS》的Creating a Custom Document Object一节

    一个文档应用(document-based application)必须使用UIDocument子类的实例对象来管理文档数据。本节将讨论大多数情况必须复写的方法,并对其他可复写的方法给出建议。对于必须复写的loadFromContents:ofType:error:contentsForType:error:方法,本节将给出两个例子来解释如何使用NSDataNSFileWrapper来读写文档数据。本文最后的“将文档数据存储在文件包中“部分将会对后者进一步作出解释。

    你还可以复写本文并未涉及的UIDocument类的其它方法,以实现更多有关文档读取的功能。但这些方法的复写有更复杂的要求,应当尽可能避免。详情可查阅文档UIDocument Class Reference

    声明子类的接口

    在Xcode中添加新的Objective-C类,并赋予合适的类名称(建议保留 Document 字眼)。在子类接口文件中,添加新的属性以保留文档数据。

    在下例代码一中,文档数据是纯文本,因此只需要一个NSString属性就足够了(写入文档的文本将转化为NSData格式)。

    代码一 子类声明(NSData)

    @interface MyDocument : UIDocument {
    }
    @property(nonatomic, strong) NSString *documentText;
    @end
    

    下例代码二展示了另一个使用NSFileWrappr对象描述数据类型的app(本节代码样例均基于这两种app)。除了NSFileWrappr对象属性外,接口文件还添加了文本与图像的属性来描述文档内容。

    代码二 子类声明(NSFileWrapper)

    @interface ImageNotesDocument : UIDocument
     
    @property (nonatomic, strong) NSString* text;
    @property (nonatomic, strong) UIImage* image;
    @property (nonatomic, strong) NSFileWrapper *fileWrapper;
     
    @property (nonatomic, weak) id <ImageNotesDocumentDelegate> delegate;
    @end
     
    @protocol ImageNotesDocumentDelegate <NSObject>
    - (void)noteDocumentContentsUpdated:(ImageNotesDocument*)noteDocument;
    @end
    

    代码二还展示了其所使用的代理与协议。文档子类的实例对象所属的视图控制器将作为实例对象的代理,从而在文档发生改变时,得到noteDocumentContentsUpdated: messages方法的通知。代码四展示了noteDocumentContentsUpdated: messages方法的使用细节。

    加载文档数据

    当app依用户需求打开文档时,UIDocument会发送loadFromContents:ofType:error:方法读取文档内容,并将内容存储在一个实例对象中。这个实例对象既可以是NSData类,也可以是NSFileWrapper类。当复写这个方法时,你应当初始化文档对象的内部数据结构,并将传入的实例对象(the passed-in object)的内容写入其中。

    代码三将传入的NSData对象内容转换为字符串,并赋给了documentText属性。此外,当文档内容发生变动时,文档对象的代理(也就是其所属的视图控制器)还会得到通知。这是因为loadFromContents:ofType:error:方法除了会在打开文档时调用外,还会随着iCloud端发生变动时调用(revertToContentsOfURL:completionHandler:方法)。

    代码三 加载文档内容(NSData)

    - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError {
        if ([contents length] > 0) {
            self.documentText = [[NSString alloc] initWithData:(NSData *)contents encoding:NSUTF8StringEncoding];
        } else {
            self.documentText = @"";
        }
        if ([_delegate respondsToSelector:@selector(noteDocumentContentsUpdated:)]) {
            [_delegate noteDocumentContentsUpdated:self];
        }
        return YES;
    }
    

    如果app需要打开多种文档类型,可以设置typeName参数;不同的文档类型可能需要设置不同的打开方式。如果app在加载文档对象时,遇到错误,这个方法会返回NO。当然,你也可以设置返回一个NSError对象来描述所遇到的错误。

    代码四 加载文档内容(NSFileWrapper)

    - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError {
        self.fileWrapper = (NSFileWrapper *)contents;
        if ([_delegate respondsToSelector:@selector(noteDocumentContentsUpdated:)]) {
            [_delegate noteDocumentContentsUpdated:self];
        }
        return YES;
    }
    

    上例代码四并没有从NSFileWrapper中提取文本和图像内容,并赋给其对应属性。这些都在textimage属性的读取方法中自行完成(lazily done)。

    获得文档数据快照

    当文档关闭或自动保存时,UIDocument会向文档对象发送contentsForType:error:消息。你必须复写这个方法,将文档数据的快照(Snapshot)返回给UIDocument,然后写入文档文件中。代码五展示如何获得NSData类型的快照。

    代码五 返回数据快照(NSData)

    - (id)contentsForType:(NSString *)typeName error:(NSError **)outError {
        if (!self.documentText) {
            self.documentText = @"";
        }
        NSData *docData = [self.documentText dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];
        return docData;
    }
    

    如果在创建NSData对象之前documentText属性没有赋予任何字符串,该属性将会赋予一个空字符串。

    代码六展示如何获得NSFileWrapper类型的快照。通常,总体NSFileWrapper对象如果不存在,会由代码自动创建;其内含的文件对象如果不存在,会由代码根据textimage属性创建。然后代码会将文档数据的快照(Snapshot)返回给UIDocument,然后写入文档文件包(file package)中。
    文档文件包(file package)会在下一部分得到详细解释。

    代码六 返回数据快照(NSFileWrapper)

    - (id)contentsForType:(NSString *)typeName error:(NSError **)outError {
     
        if (self.fileWrapper == nil) {
            self.fileWrapper = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:nil];
        }
        NSDictionary *fileWrappers = [self.fileWrapper fileWrappers];
        if (([fileWrappers objectForKey:TextFileName] == nil) && (self.text != nil)) {
            NSData *textData = [self.text dataUsingEncoding:TextFileEncoding];
            NSFileWrapper *textFileWrapper = [[NSFileWrapper alloc] initRegularFileWithContents:textData];
            [textFileWrapper setPreferredFilename:TextFileName];
            [self.fileWrapper addFileWrapper:textFileWrapper];
        }
        if (([fileWrappers objectForKey:ImageFileName] == nil) && (self.image != nil)) {
            @autoreleasepool {
                NSData *imageData = UIImagePNGRepresentation(self.image);
                NSFileWrapper *imageFileWrapper = [[NSFileWrapper alloc] initRegularFileWithContents:imageData];
                [imageFileWrapper setPreferredFilename:ImageFileName];
                [self.fileWrapper addFileWrapper:imageFileWrapper];
            }
        }
        return  self.fileWrapper;
    }
    

    将文档数据存储在文件包中

    文件包内部存在一定的结构,其反映在NSFileWrapper类方法中。NSFileWrapper对象是文件系统节点的运行时代表(a runtime representation of a file-system node)。这个节点可以是目录,普通文档,也可以是符号链接。操作系统将文件系统节点视为一个单独、透明的整体,类似于bundle概念。

    文件包的结构 - 来自官方文档文件包的结构 - 来自官方文档

    你可以使用代码手动创建一级目录,并向其添加普通文件和子目录,这些都是NSFileWrapper对象。一级目录当中的NSFileWrapper对象具有PreferredFilename属性相互关联。现在,我们会过头来看看代码六中的部分代码:其所创建的文件包内有两个文件——文本文件和图片文件。

        if (self.fileWrapper == nil) {
            self.fileWrapper = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:nil];
        }
        
        NSDictionary *fileWrappers = [self.fileWrapper fileWrappers];
        
        if (([fileWrappers objectForKey:TextFileName] == nil) && (self.text != nil))  {
            NSData *textData = [self.text dataUsingEncoding:TextFileEncoding];
            NSFileWrapper *textFileWrapper = [[NSFileWrapper alloc] initRegularFileWithContents:textData];
            [textFileWrapper setPreferredFilename:TextFileName];
            [self.fileWrapper addFileWrapper:textFileWrapper];
        }
    

    这段代码会自动创建一级目录,根据textimage属性创建文本文件和图片文件,并赋予合适的名字,然后将其添加到一级目录的NSFileWrapper对象中。

    更多NSFileWrapper类的信息可以查阅NSFileWrapper Class Reference

    有关文档文件包所需的Info.plist属性可以看Exporting the Document UTI

    复写其他方法

    你可能会想复写的其他下列UIDocument类的方法:

    • disableEditingenableEditing。当文档接受来自iCloud端的更新、撤销修改或遇到其他情况导致用户修改文档并不安全时,UIDocument类会调用前者。你可以复写此方法来避免此时段的文档修改操作。当上述情况解除时,UIDocument类会调用后者。

      如果你不愿意复写这两个方法,你还可以利用通知中心观察文档状态的改变。如果文档状态是UIDocumentStateEditingDisabled,你应当避免修改操作直到文档状态发生变化。更多有关此话题的信息,可以查阅Monitoring Document-State Changes and Handling Errors

    • savingFileType这一方法默认返回fileType属性的值。如果当前文档需要存储为不同的文档类型,你应该复写此方法来替换文档类型UTI(file-type UTI)。例如Mac OS X系统中,RTF文件中加入图片时会存储为RTFD文件包类型。

    相关文章

      网友评论

        本文标题: (译)如何自定义UIDocument的子类

        本文链接:https://www.haomeiwen.com/subject/uhgshttx.html