(译)如何自定义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文件包类型。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,849评论 6 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 创建自定义文档对象 基于文档的应用程序必须具有代表和管理文档数据的UIDocument子类的实例。本章讨论了覆盖大...
    nicedayCoco阅读 1,413评论 0 3
  • 一天又过去了。宝贵的一天,还是一天复一天? 这一天和任何一天都没有区别,人性照样是贪嗔痴,我照样是一个小小的我。 ...
    青涩娃一枚阅读 497评论 0 0
  • 今天有个北京的同事过来深圳出差,跟她聊北京,她很惊讶我怎么知道这么清楚,我说我在北京呆了将近七年,自然很熟悉。说这...
    青柠左岸阅读 920评论 12 3