Document Provider Extensions

通过iOS 8 app extensions,我们可以选择多种方式去分享我们app的功能。Document Provider extension是这些扩展之一,它允许我们的app和其它app进行文件传送、接收交互。

如果你曾无休止地为你的apps寻找云存储无缝衔接方式,以此实现文档共享,那么Document Provider简直就是为你设计的。( 好吧,这简直就是为我设计的。^-^ )

为了实现应用间共享数据,iOS 使用了两种单独的扩展。

•Document Picker:当另一个app启动iCloud file picker时呈现出的view controller。

•File Provider: 一个由NSFileCoordinator协调在host app与extension apps之间移动数据的非UI组件。NSFileCoordinator,正如其名,协调读取文件。它也允许多线程操作,例如host app及containing app,可以同时获取文件而不用彼此叨扰。


你还需要一个containing app 为这些扩展做一个大本营,但实际上,启动另外一个app和host app就好了。另外,你必须为host app及extensions app建立一个严格的定义接口,以此分享文件数据。

diagram

Document Providers 使用四中方式在应用间共享数据:Import,Open, Export, and Move。

share operations

接下来让我们一一认识它们:

1.Import:Import从提供者那里获得文件并拷贝到我们的host app。最经典的应用场景是在内容创建类应用中的使用。例如,像keynote、PowerPoint这样的演示制作应用,希望导入图片,视频,以及音频。它们希望拷贝一份这些数据以此保证它们随时可用。

2.Open:和import一样,open同样从文件提供者那里获得数据并导入我们的host app,只是不同的是,这些数据没有被拷贝一份至我们的host app,数据还在原处。例如,你或许在音乐播放器、视频播放器,再或者图像编辑器中使用该方式。

3.Export: Export使我们的host app可以保存文件至其它提供者。例如,这些提供者可能是常用的像Dropbox、iCloud Drive这样的云存储系统。host app可以通过export保存文件到提供者的存储空间。在接下来的编辑器例子中,当用户完成编辑,他们可以导出文件,然后稍后可以在其它app中打开这些文件。

4.Move: 除了host app不会持有一份儿文件的拷贝,其它Moving和export差不多。这或许是最不常用的操作,因为大多数iOS apps不是为了抛弃它们的数据才创建的。

这一章,你将创建一个相簿应用,应用中会用到文件获取器(document picker)和文件供应扩展(file provider extension)。该应用使用Amazon S3 (Simple Storage Service) 作为后台服务端。Amazon S3可以为你提供的任何存储文件作为一个模型,无论这些文件在云端、公司数据中心,或者在一台连接到Internet的电脑上。

该教程需要在Xcode 6和iOS 8环境下实践,同时苹果开发者账号也是必须的,因为你需要有效的iCloud授权,以此获得Document Picker。

注意:该章节学习需要有一定基础,仅仅适合由中级向高级进阶的开发者。文中会有一些操作我们不进行一步步详细操作讲解,例如在故事版中自动布局view controllers。所以在进行下文之前你最好确定自己对这些技能比较熟悉。

废话少说,开始吧

在这一节的资源文件中,找到我们的起始工程(戳这里)。在Xcode里打开该工程,但先不要着急运行它。在你运行之前,你必须做些修改才好。接下来我们将详细介绍怎样修改。

起始工程(Imgvue)是一个基于Amazon S3 (Simple Storage Service)为后台的图像管理应用。当然,当你针对document provider extension开发你自己的应用时,你可以使用其它云存储服务,像Dropbox(吐槽一下,以前我还真打算用这个来作为我电子书app的资源提供者,直到有天我突然发现在我们天朝如果不翻墙就再也访问不到它主页),Box,Google,Azure,etc。

Amazon很流行,并且提供它的SDK处理服务交互,除此还可以在非登录条件下在全球内获得数据。因此,对本章而言,Amazon是个理想的选择。

Imgvue提供巨大的,公开的空间存储图像。但也很明显,它不适合提交至App Store,因为它没有加强限制,并且对图像共享与上传没进行监管。但话又说回来,如果单单作为一个学习例子,它已经非常不错了。

设置S3

这个版本的Imgvue基于Amazon S3。Amazon提供了一个可以处理超复杂的请求签名过程的Objective-C框架。

使用该应用,你需要一个Amazon Web Services(AWS)账号,以及一个S3空间。S3根据你存储空间使用大小进行收费,所以注意了,在S3存储本文需要的图片资源可能会产生一些费用(大概每月几美分,直到你删除你的S3空间)。好消息是,如果你是第一次注册AWS,你将有资格在第一年里使用它们的免费套餐。

注意:在真实的应用中一定当心S3的收费,如果你在S3存储的图像有数以万计的人在使用,那么费用会有所增加。所以,当心了!

首先,进入http://aws.amazon.com/ ,登录或者创建一个新帐号。然后耐心操作几步骤,并且输入信用卡信息。但不要怕,本教程中接下来你将要上传的几个图片会收费很便宜,甚至完全免费(如果你使用免费套餐的话)。

Sign in 

然后在控制台(console),打开S3仪表板 (dashboard)

console

S3组织信息到buckets。点击Create Bucket按钮创建一个新的bucket。你的应用将放置图片至该bucket.

注意:完成以上操作非常重要,否则,你的应用将会崩溃,而且你为完成该章所做的所有努力都会付诸东流。

Simple Storage Service

接下来,配置你的空间。键入空间名字,然后选择你所在地区名。这里注意你起的名字必须是全球唯一的。可以试着使用包含你的名字或者你所在领域的字符串。

Bucket Name and Region

拷贝你的区间名并保存到安全的地方,很快你会再使用到它。完成这些后,点击创建按钮。

下一步你要做的是创建一个可以绑定你应用到AWS servers的安全证书。点击左上角Services按钮,再点击IAM打开个人中心,进入管理控制台。

IAM

点击Users标签,再点击Create New Users按钮。

Create New Users

键入你喜欢的username,比如ImgVue,然后点击Create按钮。

Enter Users Names

下一页,点击Show User Security Credentials来显示你的Access Key ID以及Secret Access Key。拷贝它们,并保存至安全位置,不久之后你会用到它们。点击Close按钮,再次点击Close按钮。

接下来你要做的是,允许你新建的用户可以访问你的新空间(bucket)。在用户列表中选择你新建的用户,滚动视图到User Policies部分,点击Attach User Policy。

Attach User Policy

在下一页面,选中Custom Policy单选按钮,然后点击Select。

Custom Policy

在Policy Name键入ImgVue_Bucket_Access,在Policy Document键入以下字符串(使用之前你键入的空间(bucket)名取代[BUCKET NAME]):

Policy Document

这允许你的用户可以访问你创建的空间(bucket),但没有给你的用户其它过多的权限。这样给用户有限的访问权限在Amazon Web Services是最好的选择。

完成以上操作,点击Apply Policy。好了,这里完成了S3设定,接下来回去编代码吧。

App frameworks

正如本章之前所提到的那样,app extensions在Container app和extensions app之间需要一些共享代码。Imgvue已经为此分解成三部分。

1.Imgvue:这是你的Container App.它一则用来列表缩略图显示你S3 bucket中的图片,二则允许用户从相册选取图片上传。

2.S3Kit framework:该框架打包Amazon提供的AWSS3.framework。AWSS3.framework是前iOS8时代的非模块静态框架,它不能直接使用Swift代码。因此,S3Kit framework在它们之间架起了一座友好大桥。S3Kit就像一个协调者,它提供了在Imgvue app ,extensions app,S3之间转移图片的逻辑。

3.UIWidgets:该框架包含一些Document Picker extension和host app要共享使用的缩略集合视图(collection views)以及其它一些屏幕视图。

注:Amazon提供的框架还不能让我们太兴奋,特别是,它的网络请求是同步的,并且它们以抛出异常来处理errors。因为这些原因,这些框架不适合用在商业app之中,不过,对于演示Document Providers是怎样工作的,它却足够了。

S3请求签名相当复杂,讲解它将偏离这章的目的,而且需要平添许多步骤。记得在building和running程序的时候,模拟器或者真机设备需要连接网络,否则程序会崩溃。

选择你的工程,选中S3Kit 目标,在General 标签的Deployment Info部分,确保Allow app extension API only是选中的。

Deployment Info

这确保该框架只用在iOS SDK中那些支持扩展的调用,例如,扩展并不需要获取UIApplication实例。在build extension将要使用的框架时,要时刻谨记这一点。

Setting up the app

在S3Kit framework中打开BucketInfo.swift,替换ACCESS_KEY_ID 和 SECRET_KEY为你从Amazon获取的值,设置BUCKET_NAME为你选择的bucket id。

因为该应用要用的iCloud,你需要在Xcode和Apple Provisioning Portal中心配置App ID和Bundle Identifier。在Imgvue应用的target的General标签栏,设置Bundle Identifier为一个像com.mydomain.Imgvue这样的唯一的Bundle ID。在下面的team一栏选择你所在已付费账号下的team。

identity

Build and run,你将看到一个清新而干净的屏幕页面;现在还没有图片。使用app中的Upload tab上传一些图片。

当你再次返回图片collection view,你的图片将会被从S3上面加载下来。

注:如果你得到缺少entitlement的错误信息,手动连接iTunes,创建一个provisioning profile,然后在 Build Settings\Code Signing\Provisioning Profile 配置你的工程使用它。

images

注意:你也可以在S3控制台验证图片是否上传成功,以及大量上传其它图片。

Adding the extension

目前为止效果已经很好了,但是现在只有Imgvue可以获取那些美丽的图片,而你希望其它应用同样可以得到这些图片。所以,Document Provider extensions来了。

在Xcode的工程导航栏选择Imgvue Xcode Project file,从editor菜单选择Add Target.继续选择iOS\Application Extension\Document Provider,然后点击Next。

Document Provider

你需要为你的工程起一个简短好记的名字,然后用户会在他们正在使用的应用中一眼看出你的应用支持扩展。

在我们这篇教程,为工程起名为S3Images。选中选择框Include a File Provider extension。

product name

当弹出激活新的schemes,单击Activate。

Activate scheme

现在为止,我们已经创建了两个folders,即targets和schemes。S3Images,文档选择器扩展,S3ImagesFileProvider,使用NSFileCoordinator连接你的文件系统。如果你仅仅计划实现导入导出功能,那么你不需要Provider extension。

Document Picker extension

Document Picker是一个允许你的应用选择文件以及与文件交互的视图控制器。默认情况下,应用启动Document Picker可以连接iCloud Drive。Document Picker允许用户从一列表文件位置选择你的扩展,触发你的Document Picker的界面进行加载。

Document Picker extension模板由一个故事版文件及DocumentPickerViewController管理类组成。若想从S3 bucket获得图片,你还需要一个collection view。

1.在S3Images 文件夹,打开MainInterface.storyboard。你将看到下图所示界面。

Document Picker View Controller

2.删除已经存在的 untitled.txt按钮。

3.拖拽一个Container View 到Document Picker View Controller的场景视图。

4.设置Container View的Auto Layout约束,使其宽高等同其父视图宽高,填充场景屏幕。居中Container View。

5.拖拽一个Navigation Controller到场景画布。

6.按住Control键,拖拽Container View到Navigation controller,然后选中Embed。现在Document Picker的子视图将边为navigation view。(这也是将一个navigation stack设置在Document Picker之内的方法。)

7.删除原视图容器中的无关视图控制器。

8.删除navigation controller的主table view controller,替代为Collection View Controller。

9.按住Control键,拖拽navigation controller到collection view controller,然后选中Root View Controller,设置Collection View Controller 为navigation view controller的主视图控制器。

10.在collection view controller 的Identity Inspector一栏,设置其类为ThumbnailCollectionViewController,设置其module为UIWidgets。这告诉storyboard,设置控制器的class为类似container app的隔离模型的类。

好了,暂停!现在最好呼吸点新鲜空气放松下。你的故事版现在看起来是这样子的:

storyboard

OK,小憩后,我们继续开始吧。

1.在collection view 中点击Collection View Cell原型。

2.在Identity Inspector一栏,设置custom class 为ThumnailCell,设置其module为UIWidgets。

3.在Attribute Inspector导航栏,设置Reuser Identifier为ThumbCell.

4.拖拽一个Image View到cell上面,使用约束确保其填充整个cell空间。

5.打开cell的Connection Inspector,连接imageView outlet到刚才那个新的image view。

6.在Collection View的Size Inspector,设置其宽高为80。

7.同样在Size Inspector,改变cells 和lines的Min.Spacing为0。

Size Inspector

现在ThumbnailViewController已经和S3的图片建立了连接,所以接下来你应该迫不及待要测试一下效果了吧。

确保extension可以连接这些模型,在S3Images工程的target's Build Phase中添加S3Kit.framework和UIWidgets.framework。

target's Build Phase

然后,在Imgvue和另外两个Document Provider的targets,在Capabilities标签栏,设置App Groups 的capability状态为enable。确保三个targets同样设置。

这需要你对共享容器进行授权设置,以此在你的container app与extension app间进行共享文件以及传送URLs。

Capabilities

现在,虽然extension app还不能做很多,但它已经可以在document picker中显示从s3获得图片缩略图了。为了显示这个document picker,你需要另外一个独立的host app。

Build and run,看看是不是一切OK。接下来,我们来看看那个host app。

DocTester

这章节中用到的另一个应用是DocTester,它是个准系统应用,其主要目的是做为你的host app。打开和导入功能,可以加载图片并在主视图显示。可以导出和移动已下载的图片并上传至服务器。

打开 DocTester.xcodeproj 设置这个app。

因为文件扩展意味着扩展iCloud 文件,一个host app 在启动之前需要进行iCloud enable设置。

首先,在target的General一栏,更新Bundle Identifier为你账号下面可以使用的app id,修改Team一栏为你的team id。

Bundle Identifier

然后在Capabilities一栏,设置iCloud 和iCloud documents为enable状态。

iCloud

现在在运行app之前还有最后一步,是最后一步哦,我保证!;-)

在Xcode中的menu菜单,选择Open Development Tool\iOS Simulator.在模拟器的设置中,登录iCloud。写的这里,你需要有一个有效的iCloud Drive 账号,并且设置Document Provider extensions为enable。

Sign in iCloud

现在开始运行DocTester。这样模拟器中将安装该应用,然后Imgvue可以使用它了。在Xcode,选择Product\Stop(CMD-Period.) 很好!

DocTester安装好,你可以测试Document Picker了。

Using the Document Picker

接下来,切换到Imgvue工程中,选择S3Images scheme并运行,这时模拟器将启动并显示一个apps列表。选择DocTester而不是Imgvue。

scheme

程序将启动,非常基本的应用!

DocTester

点击导入按钮。如果你已经设置了iCloud Drive,iCloud file chooser将出现。如果你没有设置,iOS将一步一步引导你进行设置。

iCloud Drive

切换到Imgvue,点击左上方的Locations按钮,使出现Document Provider extension 菜单。点击More...使extension为enable并添加它到菜单。

More

这取消了导入按钮,再次在屏幕的上方点击Import按钮。现在S3Images extension将出现在本地列表中,点击它,添加Document Picker extension。如下图所示,在选择视图中,窗口的上方有两个toolbar。上面一个,有Locations和Done按钮,只是iOS系统文件选择器的一部分,是不可设置的。第二个是container view的contained nav controller的navigation bar。稍后,你需要添加代码隐藏这个看起来怪异的家伙。

extension

现在点击图片还不会有任何反应。虽然你已经添加了Document Picker extension UI,你还没有创建file providing。接下来你需要添加它。

Troubleshooting

尽管一切应该正常了,可是仍有可能选择器不会出现。导致这种现象可能源于几种原因。特别的,如果extension app 崩溃,那么在Console app查看crash logs并debug。你也可以通过使用Command-/快捷键查看模拟器的logs。

下面几点需要检查:

• 确保你的网络连接是正常的。如果有网络相关错误,重启设备或者模拟器。

• 确保你有个iCloud account并且iCloud Drive设置为enable。

• App Id和Team设置在所有的targets上应该保持一致。同时,targets 下面的Team也需要关联App ID。

• DocTester 和  Imgvue targets需要设置iCloud Documents capabilities为enable。如果它可用,使用fix issues button。

• 检查你的target settings,看看在General tab是否存在问题列表。如果有,尝试下Fix Issue,或者尝试手动生成provisioning profile。

• Imgvue, S3Images, S3ImagesFileProvider targets都需要匹配的app group 授权。有时,在Capabilities pan的设置的extension没有更新到.entitlements或者.plist文件,所以一一检查它们。

• 有时,building和running没有全部更新host app。如果有些想要的改变缺失了,终止并重启host和container apps,然后re-run它们。

Import

document provider的四个操作中最简单的一个是Import,或者是"download and copy to my app"功能。

实现导入,extension app需要返回共享文件容器中文件的下载url,然后host app接受这个url。随后,当document picker消失的时候,host app保存文件到沙盒目录。

为了生成url,当用户选择一张图片,extension controller需要获得通知。ThumbnailCollectionViewController有一个代理方法可以实现这个功能。

在DocumentPickerViewController.swift文件的最上边,导入如下modules:

import modules

然后,通过添加ThumbnailDelegate和NSFileManagerDelegate声明DocumentPickerViewController实现这些代理方法。

classDocumentPickerViewController:UIDocumentPickerExtensionViewController,ThumbnailDelegate,NSFileManagerDelegate{

添加ThumbnailDelegate协议中需要实现的didSelectImage方法。

// MARK: ThumbnailDelegate

func  didSelectImage(image:S3Image) {let  imageUrl = image.url!

//1

let   filename = imageUrl.pathComponents.lastasString

//2

let   outUrl =documentStorageURL.URLByAppendingPathComponent(filename)

// 3

let   coordinator =NSFileCoordinator()

coordinator.coordinateWritingItemAtURL(outUrl,

options: .ForReplacing,

error:nil,

byAccessor: { newURLin

//4

let  fm =NSFileManager()

fm.delegate=self

fm.copyItemAtURL(imageUrl, toURL: newURL, error:nil)

})

// 5

dismissGrantingAccessToURL(outUrl)

}

该方法使用你从S3下载的图片的url并创建一个新的url传递给host app。让我们一步一步看来。

1.filename是path字符串的最后组成部分,path字符串的前部分是host app缓存图片的沙盒路径。

2.documentStorageURL是由UIDocumentPickerExtensionViewController类定义的特定路径。File Provider extension和host app均可访问。为image添加唯一的文件名后缀,可作为image的标识,这样之后provider extension就可以获得该文件了。

3.创建文件coordinator,以此确保可以访问到文件存储位置。coordinator可以写文件到文件存储路径,也可以覆盖掉文件存储路径下已经存在的任何文件。

4.accessor block使用新的URL而不是outUrl,是因为coordinator想要写文件到临时位置并在之后删除它。你创建了一个文件管理器,它从download路径拷贝文件到存储路径。

5.调用dismissGrantingAccessToURL关闭document picker,回到host app。

接下来,实现DocumentPickerViewController中的必要方法NSFileManagerDelegate:

// MARK: NSFileManagerDelegate

func  fileManager(fileManager:NSFileManager,

shouldProceedAfterError error:NSError,

copyingItemAtURL srcURL:NSURL,

toURL dstURL:NSURL) ->Bool{

return true

}

在文件路径下已经有老版本文件的时候,该方法允许文件替换。

最后,使用下面代码替换prepareForPresentationInMode()模板方法:

override func   prepareForPresentationInMode(mode:UIDocumentPickerMode) {

super.prepareForPresentationInMode(mode)

let  vc =self.childViewControllers;letnav = vc.first

as  UINavigationController

let  thumbController = nav.topViewController as  ThumbnailCollectionViewControllerthumbController.navigationController?.setNavigationBarHidden(

true, animated:false)

thumbController.delegate=self

}

你可能发现了,该方法有点类似viewDidLoad()。你设置了对ThumbnailCollectionViewController的引用,因此你可以设置picker的delegate。

使用DocTester再次build and run the extension。

嗯...选中一个图片没有任何反应?对啊,那是因为File Provider extension模板提供了空数据。接下来你要解决这个问题。

The File Provider extension

File Provider extension有几个目的:

• 提供和保护文件的URLs:即使文件不存在,file provider仍可以按照期望执行下载。

• 提供文件元数据(metadata):Document-based apps主要通过元数据查询进行文件系统交互(Mac比iOS更甚)。file provider可以在没有获得文件数据的前提下提供元数据。当host app 提供一个文件列表,而用户只想打开其中一个,这时该功能会很有用。

• 管理bookmark URLs:Bookmaks创建一个用户之后可以使用的文档URL,通过该URL实现状态恢复。如果extension/container app修改了文件或者清空了本地文件,该功能确保用户重启host app时文件可以到特定的状态。

严格说,最后两个要点以及超出了本章所讲范围,但如果你真感兴趣,那么再去看看WWDC 2014 session 234, “Building a Document-Based App”,你将可以得到更多细枝末节。

注:如果extension provides仅仅提供导入导出功能,那么你将不需要File Provider extension。Document Picker extension可以直接从存储器拷入拷出文件数据。

打开S3ImagesFileProvider中的FileProvider.swift文件,在文件头部导入S3Kit。

importS3Kit

现在,用以下代码替换掉startProvidingItemAtURL方法:

override func  startProvidingItemAtURL(url:NSURL,

completionHandler: ((error:NSError?) ->Void)?) {

let   fileManager =NSFileManager()

let   path = url.path!

if    fileManager.fileExistsAtPath(path) {//1

//if the file is already, just return

completionHandler?(error:nil)

return

}

//load the file data from Amazon S3letkey = url.lastPathComponent//2

let   bucket =BucketInfo()

let   fileData = bucket.load(key)!//3

var  error:NSError? =nil

var  fileError:NSError? =nilfileCoordinator.coordinateWritingItemAtURL(url,

options: .ForReplacing,

error: &error,

byAccessor: { newURLin//4

_= fileData.writeToURL(newURL, options: .AtomicWrite,

error: &fileError)

})

if   error !=nil{//5completionHandler?(error: error);

}else{

completionHandler?(error: fileError);

}}

该方法确保当host app 请求文件URL的时候image data是可以获得的。开始的时候,documentStorageURL驻留在container app的沙盒目录之外的共享文件夹下,因此,在方法结束的时候,应该确保文件已经存在。

1.如果文件已经存在于路径,方法执行callback并返回,结束执行方法。

2.如果期望路径下不存在文件,那么必须进行下载。S3 keys和这款app的文件名一样。因为Document Picker extension构建的URL含有文件名(字符串尾部组成),所以我们可以从其中提取出来。

3. synchronous load方法从S3下载文件。

4.file coordinator提供一个临时URL位置,因此文件数据可以被写入URL。如果URLs不同,file coordinator负责移除那些数据。通过指定更换,file coordinator知道其内容将被完全替换。

5.写入文件之后,completion handler被执行。如果写入过程中出现错误,返回error对象。

Reading the image in the host app

file provider通过NSFileCoordinator被访问。注意被DocTester的ViewController.swift回调调用的importImage函数。它看起来是这样子的(你不需要添加这些,它已经存在于DocTester了):

func  importImage(url :NSURL) {

let  fileCoordinator =NSFileCoordinator(filePresenter:nil)

// 1

fileCoordinator.coordinateReadingItemAtURL(url,options: .WithoutChanges,

error:nil) { newURLin

// 2

if let  data =NSData(contentsOfURL: newURL) {let   image =UIImage(data: data)self.imageView.image= image

}}}

这读取Document Picker extension-supplied URL并加载图片到app的imageView。下面我们详细讲解:

1、coordinateReadingItemAtURL获得沙盒URL读文件,当文件已经准备好可以被读了,执行accessor block。因为它必须拷贝文件到不同的位置(为了安全或多点接入之目的),它提供一个新的URL代表文件。

2.被提供的URL在accessor block内部可像一般URL那样使用。

现在选择S3Images,build and run。选择DocTester运行,选择Import然后选中一张图片。

瞧!picker将消失,app内显示一张图片。

select image

注:如果你遇到问题,从S3ImagesProvider target运行DocTester。

Export

这一节,Document Provider extensions传输图片文件到host app,但没有上传新图片。为了可以实现上传,Document Picker需要提供一个允许用户键入文件名的视图,而不是仅仅提供thumbnail chooser。

在S3Images target下打开MainInterface.storyboard文件。

1.添加一个新的View Controller到storyboard。

2.从 thumbnail controller向右拖拽一个segue到新建的controller。选中Push segue并设置它的identifier为upload。

upload

3.拖拽一个Text Field到view controller的view。像在the top和the top layout guide 之间设置vertical space那样设置它的horizontal space左右边缘各10pt。设置placeholder为Type new filename。

Type new filename

4.在视图上面添加新的按钮,设置它居中对齐,并设置它的上边缘到其上面的text field下边缘8pt。设置按钮的title为Upload。

Upload button

5.在S3Images工程中使用Cocoa Touch Class template新建文件,命其名为UploadViewController,设置它的superclass为UIViewController。

6.回到storyboard,在Identity Inspector设置新建的那个view controller的类为UploadViewController。

7.打开Assistant Editor来显示UploadViewController.swift。按住CTRL键,分别拖拽text field和button到UploadViewController.swift,在弹出的提示中分别输入textField和button,为它们命名。在从button CTRL拖拽一次,新建一个名为upload的IBAction。

Assistant Edit

上面就是你如何设置UI以允许用户设置文件名并上传图片的步骤。

接下来你需要编码来处理UI事件。完成以上步骤,再次击打键盘开始编码吧。Wow,曙光将近,你几乎就要成功了。

haha

你需要定义一个新的protocol代理,以此实现当用户设置name时通知Document Picker子类。在UploadViewController.swift上面添加如下代码:

protocolUploadControllerDelegate {

      func  nameChosen(name:String)

}

当传递了一个输入文本,nameChosen方法被执行。然后该方法需要在delegate对象中被执行。

在UploadViewController添加新的实例变量:

var delegate: UploadControllerDelegate?

现在重写viewWillAppear:方法设置delegate:

override func  viewWillAppear(animated:Bool) {

      super.viewWillAppear(animated)

     let  navController =parentViewController

     let  docPicker = navController?.parentViewControllerdelegate =           

      docPickerasDocumentPickerViewController

}

以上方法通过hierarchy找到Document Picker view controller,然后设置它为delegate。

添加以下代码,实现点击button时调用该delegate。

@IBAction func  upload(sender:AnyObject) {

        let name :NSString=textField.text

         if name.length>0{

              delegate?.nameChosen(name)

       }

}

以上对filename进行了非空校验。

回到DocumentPickerViewController.swift文件,为DocumentPickerViewController类添加UploadControllerDelegate协议。

classDocumentPickerViewController:UIDocumentPickerExtensionViewController,ThumbnailDelegate,NSFileManagerDelegate,UploadControllerDelegate{

添加如下代码实现delegate:

// MARK: UploadControllerDelegate

func nameChosen(name:String) {

let  justFilename = name.stringByDeletingPathExtensionletexportURL   documentStorageURL.URLByAppendingPathComponent(name).URLByAppendingPathExtension("jpg")

upload(name, outURL: exportURL)

}

上面代码获得文件名,格式化它,然后借助辅助方法进行上传。第一句,防止用户添加自己的文件名后缀。然后创建了一个JPG格式的URL。最后,调用之后你要实现的辅助方法:

func upload(name:String, outURL:NSURL) {

let access =

originalURL!.startAccessingSecurityScopedResource()//1ifaccess {

let fc =NSFileCoordinator()

varerror:NSError?

fc.coordinateReadingItemAtURL(originalURL!,//2

options: .ForUploading,

error: &error) { url in

let   bucket =BucketInfo()//3

let   data =NSData(contentsOfURL: url)!

bucket.uploadData(data, name: name) { errorin//4

if  error !=nil{

       println("error uploading:\(error)")  }  }

data.writeToURL(outURL, atomically:true)//5

self.dismissGrantingAccessToURL(outURL)//6}

originalURL!.stopAccessingSecurityScopedResource()

}}

下面一步步讲解:

1.首先,确保host app拥有合适的资格,因为它在它沙盒提供一个URL。

2.file coordinator确保文件可以读取,并在需要的时候提供新的文件URL。

3.BucketInfo是S3Kit下的辅助类,通过它可以获得云端存储。你使用它进行上传。

4.如果文件上传失败,没有复原方法,你只能再次上传。

5.因为Move和Export仅仅是拷贝文件到container app,这里同样拷贝文件到extension sandbox。只有host app可以通过移除它的备份文件进行Move操作。extension需要文件在路径处存在,否则会导致crash。

6.这里传递新的备份本地路径到host app。通过Document Picker,host app可以在这个位置更新文件。

Nice work!感觉如何?想知道更多吗?很好,因为你需要调用upload segue来展示upload chooser。

almost there

修改prepareForPresentationInMode,在Export或Move mode下检查其mode并显示upload界面。

document picker extension拥有相同的切入点。prepareForPresentationInMode用来检测mode,并分别为它们定制views。因为Export 和 Move mode上传文件到S3,upload segue被用来显示“enter file name”视图。如果是Import 或者Open mode,根据delegate的设置,thumbnail chooser视图将被显示。

在prepareForPresentationInMode(mode:)找到如下代码:

thumbController.delegate=self

使用以下代码替换它:

if   mode==.ExportToService|| mode==.MoveToService{

thumbController.performSegueWithIdentifier("upload",sender:nil)

}else{

thumbController.delegate=self }

export

Build and run S3Images target,选中DocTester app进行run。

选择Export,它将随机下载一张cat图片。在upload controller选中S3Images,键入名字,并按上传。当controller消失的时候,图片已经被上传到了S3 bucket。运行Imgvue app验证一下。太棒了!

upload

Opening and moving files

虽然有四种Document Provider运行方式:Import, Open, Export和Move。你仅仅实现了两个。因为app的实现仅仅提供上传、下载到S3两种选择,Move和Export被处理是一样的,Import和Open被处理是一样的。

Open、Move这些常用存取方法与Export、Import方法的不同之处是policy-based以及没有被storage provider的semantics强制实施。

例如,当打开或者导入一个文件,文件从S3被下载,移动到共享路径,并返回给host app文件的URL。

如果host app是要导入文件,它保存文件到沙盒,或者,像DocTester这样,仅仅加载数据到内存。

如果意图是打开URL并在host app的生命周期内使用它,那么你可以使用startAccessingSecurityScopedResource来持有该URL以此获得书签并通过app启动重新获取文件。

例如,在DocTester的ViewController.swift中被Document Picker回调的openImage函数就是这样做的。至少对于加载图片已经足够了。

func  openImage(url :NSURL) {

let   accessing = url.startAccessingSecurityScopedResource()//1

if  accessing {

    let   fileCoordinator =NSFileCoordinator(filePresenter:nil)

    fileCoordinator.coordinateReadingItemAtURL(url,options: .WithoutChanges,

error:nil) { newURL  in

     if   let  data =NSData(contentsOfURL: newURL) {

     let  image =UIImage(data: data)

     self.imageView.image= image

}}

url.stopAccessingSecurityScopedResource()//2}

}

如果host app在shared container中修改了URL,itemChangedAtURL将被调用。File Provider extension template配有存根此方法。

在S3ImagesFileProvide中打开FileProvider.swift文件,像如下那样替换template方法:

override func  itemChangedAtURL(url:NSURL) {

// 1

let   key = url.lastPathComponent

let   bucket =BucketInfo()

// 2

fileCoordinator.coordinateReadingItemAtURL(url,options: .ForUploading,

error:nil) { newURL  in

   var   error:NSError

   let    data =NSData(contentsOfURL: newURL)!

   bucket.uploadData(data,name: key,completion: { errorin

   // 3

   if(error !=nil) {

   println("error uploading updated file:\(error)")

}})} }

1.extensions始终使用filename作为S3 key。

2.NSFileCoordinator在读取器有可选的轻便的ForUploading属性。

3.上传更新文件到S3。

当存储文件在host app和providing app之间进行共享时,该方法被执行。也就是说,在Import或者Export mode,host app 拥有文件的一份儿拷贝,并且当它完成转移会停止与共享URL的交互。但是,在Open和Move  mode,文件依然在provider中,host app 只要需要会一直使用它。

你可以测试一下,使用DocTester build and run S3Images。打开一张图片到DocTester,然后点击改变。

这将绘制一个绿色正方形到图片之上并在shared container更新它。更新触发File Provider extension更新S3中备份儿。

现在运行Imgvue查看S3中修改的文件。再次导入一个不同的图片。Modify方法将继续更新在应用内的图片,但它将导致调用itemChangedAtURL,因此不更新S3的文件。

Modify

Summary

Document Provider extensions允许用户在自己的设备上面不同应用间共享自定义的云存储文件。一个特殊的共享路径在extensions 和 host apps之间被使用。存取到container被NSFileCoordinator控制。File Provider extension允许监听shared container。

这一章仅仅触及了File Provider extensions功能的冰山一角--它还可以提供placeholder信息,文件大小,以及在不下载整个文件的情况下展示图片缩略图。

想了解更多,看看2014 WWDC video,“Building a Document-based App”(https://developer.apple.com/videos/wwdc/2014/#234).

你还可以自定义Document Picker extension为只提供特定的文件类型,或者在四个模型的组合集合下操作。这被info.plist下NSExtension>NSExtensionAttributes控制。

UIDocumentPickerModes是一个含有所有支持模式的数组,UIDocumentPickerSupportedFileTypes是含有所有支持的UTType字符串的数组。默认为public.content。

另一个有趣的地方需要注意的是UIDocumentMenuViewController,它是一个允许用户从activated Document Picker extensions中选择的菜单。

关于该类比较好的是,你可以添加额外的non-document-pickers。例如,在UIWidgets module(used by the upload tab in the container app)下的UploadChooserViewController有一个菜单,用来从相册加载图片。

当然,extensions有一些限制,也并不是对每个app都适用。首先,extra Document Pickers必须被enabled,这需要用户在iCloud drive有登录到设备的iCloud account账号。

然后,host app需要document-based和拥有iCloud 文件权利。最后,用户必须导航到非直观的界面分别enable每个extension。

这需要大量工作,或许正因如此,Apple只建议像Dropbox, Box, Google Drive等这些为其它应用提供文件存储的app使用该extension。

对于你只是想操作自己文档的app,你可以直接使用iCloud和iTunes。此外,应用程序可以使用URL方案和支持UTTypes打开其它文件。在这种情况下,Action, Share,或者Photo Editing extension可能更合适。

否则,尽情享用Document Providers吧!

注:原文来自iOS 8 by Tutorials --Chapter 13:Document Provider Extensions2.


附Amazon云相关知识链接:

1、http://blog.csdn.net/awschina/article/details/17149515

2.http://docs.aws.amazon.com/zh_cn/AWSEC2/latest/UserGuide/ec2-connect-to-instance-linux.html

3.http://aws.amazon.com/cn/getting-started/?sc_ichannel=ha&sc_icountry=cn&sc_icampaign=ha_cn_GettingStarted&sc_icontent=ha_213&sc_idetail=ha_cn_213_1&sc_iplace=ha_cn_ed&sc_isegment=d&

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

推荐阅读更多精彩内容