zoukankan      html  css  js  c++  java
  • (译)如何自定义UIDocument的子类

    本文节译自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文件包类型。

  • 相关阅读:
    Maven关于web.xml中Servlet和Servlet映射的问题
    intellij idea的Maven项目运行报程序包找不到的错误
    修改Maven项目默认JDK版本
    刷题15. 3Sum
    刷题11. Container With Most Water
    刷题10. Regular Expression Matching
    刷题5. Longest Palindromic Substring
    刷题4. Median of Two Sorted Arrays
    刷题3. Longest Substring Without Repeating Characters
    刷题2. Add Two Numbers
  • 原文地址:https://www.cnblogs.com/rim99/p/5021366.html
Copyright © 2011-2022 走看看