iOS 文件导入、打开和导出

前言

最近公司的活比较少,空闲时间十分多,遍寻思着写一款APP。在这个过程中需要导入、打开和导出文件,于是研究了一下。

目前写的这个是本地阅读软件,后续会把在写这个APP用到的技术以及遇到的问题都整理出来,通过博客的形式分享给大家。

奇迹读书地址

可以先看上一篇文章《统一类型标识符概述》,CSDN地址简书地址

文件导入

要向APP导入文件,首先得注册支持的文件类型。

以TXT文件为例,选中项目->info->Document Types,添加一个文件类型。

Name:名称,字符串值,我这里填写“TXT File”,可以随便起一个自己喜欢的名字。

Types:文件类型,其实就是UTIs,我这里填“public.plain-text”,也可以填写多个由“,”分割开

Hand Rank:优先级,其值由Owner,Alternate,Default到None以此递减。这里我填写“Owner”最高优先级,能够使在导入文件的应用列表中显示靠前。

最终在Info.plist文件中的配置如下:

<key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeName</key>
            <string>TXT File</string>
            <key>LSHandlerRank</key>
            <string>Owner</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>public.plain-text</string>
            </array>
        </dict>
    </array>

至此,APP就拥有了导入文本文件(不仅仅是TXT文件,还包括其他如.h、.m等文本)的能力。因为UTIs具有类似编程语言里类的继承特性,“public.plain-text”这个UTI不仅仅表示TXT这一种文件,而是表示的文本文件,任何属于这个类型子文件类型都是能够识别、导入的。

导入特殊(不存在、自定义)类型文件

当需要导入一种不存在或者说自定义的文件时,必须先为这类文件申明UTIs,可以通过上一篇文章了解。

这里以“.abc”文件为例,选中项目->info->Exported Type Identifiers,点击“+”号来申明一个UTIs,在导出文件类型中申明的原因是让别的APP可以导入、识别此UTI。

Description:描述,其实可以随便写一个自己喜欢的描述,我这里填“ABC File”

Extensions:扩展,其实就是文件后缀名,我这里填写“abc”

Identifier:这就是真正的UTI字符串,按照UTI语法规定的要求填写(简单来说,只允许使用ASCII字符的子集,不能使用“_”、“:”等一些特殊字符,自定义不能以public、dyn域开始,以com域开始使用反向dns格式),我这里填写“com.demo.abc”

Mime Types:表示文件类型的一种形式,因为这是自定义类型,并不是系统不支持的一种存在且拥有Mime type的类型,所以我这里选择不填。

Confirms to:物理上、功能上属于哪种类型,因为想定义一种文本文件类型的UTI,所以这里物理上继承public.directory、功能上继承public.plain-text,以“,”分割开“public.directory, public.plain-text”。

Icons:图标资源名称,由于我没有画图标,这里选择不填

Reference URL:引用文档的URL,由于没有制作该URL,这里选择不填

最终在Info.plist文件中的配置如下:

<key>UTExportedTypeDeclarations</key>
    <array>
        <dict>
            <key>UTTypeConformsTo</key>
            <array>
                <string>public.directory</string>
                <string>public.plain-text</string>
            </array>
            <key>UTTypeDescription</key>
            <string>ABC File</string>
            <key>UTTypeIconFiles</key>
            <array/>
            <key>UTTypeIdentifier</key>
            <string>com.demo.abc</string>
            <key>UTTypeTagSpecification</key>
            <dict>
                <key>public.filename-extension</key>
                <array>
                    <string>abc</string>
                </array>
                <key>public.mime-type</key>
                <array/>
            </dict>
        </dict>
    </array>

至此,就申明了一个UTI,可以在Document Types中按照之前的方法配置导入UTI,这样就可以导入.abc文件了。

打开文件

当导入文件时,选择某APP打开,会调用该APP的UIApplicationDelegate协议申明的如下方法:

- (BOOL)application:(UIApplication *)application openURL:(nonnull NSURL *)url options:(nonnull NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options

打开文件,我选择使用UIDocumentInteractionController,注意实现其UIDocumentInteractionControllerDelegate代理方法,具体代码如下:

- (BOOL)application:(UIApplication *)application openURL:(nonnull NSURL *)url options:(nonnull NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
    NSString *urlType = @"file://";
    if ([url.absoluteString hasPrefix:urlType]) {
        RRLog(@"----收到文件:%@----", url);
        RRLog(@"----收到文件:%@----", url.scheme);
        
        // 去除URL协议
        NSString *sourcePath = url.resourceSpecifier;
        // url 字符串解码(因为可能存在中文或特殊字符)
        sourcePath = [sourcePath stringByRemovingPercentEncoding];
        
        // 建议移动到缓存目录中,或者使用后删除文件,避免产生垃圾文件
        NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
        NSString *targetPath = [cachePath stringByAppendingPathComponent:url.lastPathComponent];
        NSError *error = nil;
        [NSFileManager.defaultManager moveItemAtPath:sourcePath toPath:targetPath error:&error];
        
        if (error) {
            // 删除文件,避免产生垃圾文件
            [NSFileManager.defaultManager removeItemAtURL:url error:nil];
        } else {
            UIDocumentInteractionController *controller = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:targetPath]];
            
            // 自定义文件类型,一定要设置UTI
            NSString *extensionName = [[url.lastPathComponent componentsSeparatedByString:@"."] lastObject];
            if ([extensionName isEqualToString:@"abc"]) {
                controller.UTI = @"com.demo.abc";
            }
            
            controller.delegate = self;
            [controller presentPreviewAnimated:YES];
        }
        return YES;
    }
    
    //其它第三方处理
    return YES;
}

// 遵守协议,实现协议方法
#pragma mark - <UIDocumentInteractionControllerDelegate>
- (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller {
    return self.window.rootViewController;
}

打开文件需要注意的地方有以下几点:

如果要使用接收到的文件URL的路径字符串,一定要去除URL协议名(使用路径获取文件是不带URL协议名file://的),并且Unicode解码(URL会对中文和特殊字符进行Unicode编码)

使用后一定要删除文件,或者将文件移动到缓存目录、临时文件目录再使用(磁盘空间不足时系统会清理),不然就会产生垃圾文件(以后拿不到这个文件URL或者目录路径了)。

如果打开的文件是自定义类型,那么一定要配置、设置UTI。

导出文件

将文件导出、分享比较简单,使用UIActivityViewController:

    UIActivityViewController *share = [[UIActivityViewController alloc] initWithActivityItems:@[[NSURL fileURLWithPath:path]] applicationActivities:nil];
    
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        share.popoverPresentationController.sourceView = self.view;
        share.popoverPresentationController.sourceRect = CGRectMake(RR_SCREEN_WIDTH - 50, 0, 50, view.frame.size.height);
        share.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionAny;
    }
    
    [self presentViewController:share animated:YES completion:nil];

这里有一个问题需要注意,UIActivityViewController和UIAlertController的UIAlertControllerStyleActionSheet状态如果在ipad上运行会奔溃,因为在iPad中这两种控制器是以泡沫窗口的形式弹出,需要设置UIPopoverPresentationController的相关属性,如上述代码或者设置popoverPresentationController的barButtonItem。

后记

最后再分享一遍这个APP:

奇迹读书地址

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

推荐阅读更多精彩内容